Backstage UI: Add VisuallyHidden component.
Adds a new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers.
|
||||
@@ -0,0 +1,51 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { VisuallyHiddenSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
visuallyHiddenPropDefs,
|
||||
visuallyHiddenUsageSnippet,
|
||||
visuallyHiddenDefaultSnippet,
|
||||
visuallyHiddenExampleUsageSnippet,
|
||||
} from './visually-hidden.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="VisuallyHidden"
|
||||
description="Visually hides content while keeping it accessible to screen readers."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="start"
|
||||
py={4}
|
||||
preview={<VisuallyHiddenSnippet story="Default" />}
|
||||
code={visuallyHiddenDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={visuallyHiddenUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={visuallyHiddenPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Example Usage
|
||||
|
||||
Here's an example of providing screen reader context for a list of links in a footer.
|
||||
|
||||
<Snippet
|
||||
align="start"
|
||||
py={4}
|
||||
preview={<VisuallyHiddenSnippet story="ExampleUsage" />}
|
||||
code={visuallyHiddenExampleUsageSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming component="VisuallyHidden" />
|
||||
|
||||
<ChangelogComponent component="visually-hidden" />
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const visuallyHiddenPropDefs: Record<string, PropDef> = {
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const visuallyHiddenUsageSnippet = `import { VisuallyHidden } from '@backstage/ui';
|
||||
|
||||
<VisuallyHidden>
|
||||
This content is visually hidden but accessible to screen readers
|
||||
</VisuallyHidden>`;
|
||||
|
||||
export const visuallyHiddenDefaultSnippet = `<Flex direction="column" gap="4">
|
||||
<Text as="p">
|
||||
This text is followed by a paragraph that is visually hidden but
|
||||
accessible to screen readers. Try using a screen reader to hear it, or
|
||||
inspect the DOM to see it's there.
|
||||
</Text>
|
||||
<VisuallyHidden>
|
||||
This content is visually hidden but accessible to screen readers
|
||||
</VisuallyHidden>
|
||||
</Flex>`;
|
||||
|
||||
export const visuallyHiddenExampleUsageSnippet = `<Flex direction="column" gap="4">
|
||||
<VisuallyHidden>
|
||||
<Text as="h2">Footer links</Text>
|
||||
</VisuallyHidden>
|
||||
<Text as="p">
|
||||
<a href="#">About us</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Jobs</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Terms and Conditions</a>
|
||||
</Text>
|
||||
</Flex>`;
|
||||
@@ -29,6 +29,7 @@ import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPa
|
||||
import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories';
|
||||
import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories';
|
||||
import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories';
|
||||
import * as VisuallyHiddenStories from '../../../packages/ui/src/components/visuallyHidden/VisuallyHidden.stories';
|
||||
|
||||
// Helper function to create snippet components
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -74,3 +75,6 @@ export const HeaderSnippet = createSnippetComponent(HeaderStories);
|
||||
export const HeaderPageSnippet = createSnippetComponent(HeaderPageStories);
|
||||
export const TableSnippet = createSnippetComponent(TableStories);
|
||||
export const TagGroupSnippet = createSnippetComponent(TagGroupStories);
|
||||
export const VisuallyHiddenSnippet = createSnippetComponent(
|
||||
VisuallyHiddenStories,
|
||||
);
|
||||
|
||||
@@ -182,6 +182,11 @@ export const components: Page[] = [
|
||||
slug: 'tooltip',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'VisuallyHidden',
|
||||
slug: 'visually-hidden',
|
||||
status: 'alpha',
|
||||
},
|
||||
];
|
||||
|
||||
export type ScreenSize = {
|
||||
|
||||
@@ -743,6 +743,11 @@ export const componentDefinitions: {
|
||||
readonly arrow: 'bui-TooltipArrow';
|
||||
};
|
||||
};
|
||||
readonly VisuallyHidden: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-VisuallyHidden';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -1534,4 +1539,13 @@ export interface UtilityProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
rowSpan?: Responsive<Columns | 'full'>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const VisuallyHidden: (props: VisuallyHiddenProps) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export interface VisuallyHiddenProps extends ComponentProps<'div'> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer tokens, base, components, utilities;
|
||||
|
||||
@layer components {
|
||||
.bui-VisuallyHidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(100%);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { VisuallyHidden } from './VisuallyHidden';
|
||||
import { Text } from '../Text';
|
||||
import { Flex } from '../Flex';
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/VisuallyHidden',
|
||||
component: VisuallyHidden,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'Visually hides content while keeping it accessible to screen readers. Commonly used for descriptive labels, and other screen-reader-only content.',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof VisuallyHidden>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Text as="p">
|
||||
This text is followed by a paragraph that is visually hidden but
|
||||
accessible to screen readers. Try using a screen reader to hear it, or
|
||||
inspect the DOM to see it's there.
|
||||
</Text>
|
||||
<VisuallyHidden>
|
||||
This content is visually hidden but accessible to screen readers
|
||||
</VisuallyHidden>
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const ExampleUsage: Story = {
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<VisuallyHidden>
|
||||
<Text as="h2">Footer links</Text>
|
||||
</VisuallyHidden>
|
||||
<Text as="p">
|
||||
<a href="#">About us</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Jobs</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Terms and Conditions</a>
|
||||
</Text>
|
||||
<Text as="p" variant="body-small" color="secondary">
|
||||
(Screen readers hear: "Footer links" followed by the list of links)
|
||||
</Text>
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { VisuallyHiddenProps } from './types';
|
||||
import styles from './VisuallyHidden.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* Visually hides content while keeping it accessible to screen readers.
|
||||
* Useful for descriptive labels and other screen-reader-only content.
|
||||
*
|
||||
* Note: This component is for content that should ALWAYS remain visually hidden.
|
||||
* For skip links that become visible on focus, use a different approach.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const VisuallyHidden = (props: VisuallyHiddenProps) => {
|
||||
const { classNames, cleanedProps } = useStyles('VisuallyHidden', props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { VisuallyHidden } from './VisuallyHidden';
|
||||
export type { VisuallyHiddenProps } from './types';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
/**
|
||||
* Properties for {@link VisuallyHidden}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface VisuallyHiddenProps extends ComponentProps<'div'> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
@@ -52,6 +52,7 @@ export * from './components/Link';
|
||||
export * from './components/Select';
|
||||
export * from './components/Skeleton';
|
||||
export * from './components/Switch';
|
||||
export * from './components/VisuallyHidden';
|
||||
|
||||
// Types
|
||||
export * from './types';
|
||||
|
||||
@@ -395,4 +395,9 @@ export const componentDefinitions = {
|
||||
arrow: 'bui-TooltipArrow',
|
||||
},
|
||||
},
|
||||
VisuallyHidden: {
|
||||
classNames: {
|
||||
root: 'bui-VisuallyHidden',
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<string, ComponentDefinition>;
|
||||
|
||||
Reference in New Issue
Block a user