Skip to content

Commit

Permalink
Merge edf126d into 9a6f57a
Browse files Browse the repository at this point in the history
  • Loading branch information
williamjstanton authored Nov 19, 2024
2 parents 9a6f57a + edf126d commit d8ce2bd
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 304 deletions.
31 changes: 28 additions & 3 deletions modules/preview-react/side-panel/stories/SidePanel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ logic or styling for bidirecitonal support.

### Hidden Name

`SidePanel` should always have an accessible label. Often this is the heading element with an `id`
attribute. However, as seen in the example below, you can apply a `hidden` attribute to your label
if you do not want it to be visible.
`SidePanel` must always have an accessible label for both the HTML `<section>` container and the
`ToggleButton`. The `labelProps` component must always be present in the DOM in order for the
`panelProps` and `controlProps` component labels to be assigned properly. A `hidden` attribute can
be applied to the `labelProps` component. In the example below, we are demonstrating the
`AccessibleHide` component that relies on CSS properties to visually hide text for screen readers
only.

<ExampleCodeBlock code={HiddenName} />

Expand All @@ -63,6 +66,28 @@ used (this case is covered in the Examples section).
Sometimes you'll want to control `SidePanel`'s' expand / collapse behavior from outside the
component. In that case, you can use the `controlProps` supplied by the `useSidePanel` hook.

#### Notes about accessibility

The `controlProps` object delivers ARIA attributes to a component under the following assumptions:

1. The control is an icon button that does not already have an accessible name
2. The control appears at (or near) the top of the expandable content in the `SidePanel`

Spreading the `controlProps` onto an external control can introduce serious accessibility issues:

- `aria-labelledby` HTML `id` reference may become invalid when the SidePanel is collapsed, or when
the `labelProps` component isn't present in the DOM.
- `aria-labelledby` will change the name of `controlProps` component to the title of the
`SidePanel`. (This may be undesirable. For example, the "Show Side Panel" button will be
overwritten with the "Tasks Panel" heading.)
- `aria-expanded` won't make sense to screen reader users when the expanded `SidePanel` content
isn't logically following the control.
- `aria-controls` is unsupported by screen readers and will not allow users to navigate to the
controlled content.

In the following example, the `controlProps` click handler function is passed down to the
`SecondaryButton` and a toggle state was added to the button using the `aria-pressed` property.

<ExampleCodeBlock code={ExternalControl} />

### Right Origin
Expand Down
53 changes: 36 additions & 17 deletions modules/preview-react/side-panel/stories/examples/AlwaysOpen.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
import * as React from 'react';
import {styled} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
import {AccentIcon} from '@workday/canvas-kit-react/icon';
import {rocketIcon} from '@workday/canvas-accent-icons-web';
import {SidePanel, useSidePanel} from '@workday/canvas-kit-preview-react/side-panel';
import {Flex} from '@workday/canvas-kit-react/layout';
import {Text} from '@workday/canvas-kit-react/text';
import {Heading, Text} from '@workday/canvas-kit-react/text';
import {base, system} from '@workday/canvas-tokens-web';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

const StyledAccentIcon = styled(AccentIcon)({
marginRight: space.s,
});
const stylesOverride = {
accentIcon: createStyles({
marginRight: system.space.x4,
}),
pageContainer: createStyles({
gap: system.space.x4,
height: px2rem(320),
}),
panelContainer: createStyles({
alignItems: 'center',
paddingY: system.space.x4,
paddingX: system.space.x4,
}),
panelHeading: createStyles({
color: base.licorice500,
}),
mainContent: createStyles({
alignItems: 'center',
justifyContent: 'center',
flexBasis: 'auto',
flex: 1,
}),
};

export const AlwaysOpen = () => {
const {panelProps, labelProps} = useSidePanel();

return (
<Flex gap="s" height={320}>
<Flex cs={stylesOverride.pageContainer}>
<SidePanel {...panelProps}>
<Flex alignItems="center" paddingY="s" paddingX="s">
<StyledAccentIcon icon={rocketIcon} />
<Text
as="h3"
typeLevel="body.large"
color="licorice500"
fontWeight="bold"
{...labelProps}
>
<Flex cs={stylesOverride.panelContainer}>
<AccentIcon icon={rocketIcon} cs={stylesOverride.accentIcon} />
<Heading size="small" cs={stylesOverride.panelHeading} {...labelProps}>
Tasks Panel
</Text>
</Heading>
</Flex>
</SidePanel>
<Flex as="main" cs={stylesOverride.mainContent}>
<Text as="p" typeLevel="body.large">
This is the main content section.
</Text>
</Flex>
</Flex>
);
};
74 changes: 40 additions & 34 deletions modules/preview-react/side-panel/stories/examples/Basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,60 @@ import {
SidePanelTransitionStates,
} from '@workday/canvas-kit-preview-react/side-panel';
import {Flex} from '@workday/canvas-kit-react/layout';
import {Text} from '@workday/canvas-kit-react/text';
import {Heading, Text} from '@workday/canvas-kit-react/text';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {AccentIcon} from '@workday/canvas-kit-react/icon';
import {rocketIcon} from '@workday/canvas-accent-icons-web';
// local helper hook for setting content direction;
import {useDirection} from './useDirection';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system, base} from '@workday/canvas-tokens-web';

const stylesOverride = {
viewPortContainer: createStyles({
height: px2rem(320),
}),
panel: createStyles({
alignItems: 'center',
paddingY: system.space.x4,
paddingX: system.space.x4,
}),
accentIcon: createStyles({
marginInlineEnd: system.space.x4,
}),
mainContent: createStyles({
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flex: 1,
flexBasis: 'auto',
}),
};

export const Basic = () => {
const {direction, toggleDirection} = useDirection();
const {expanded, panelProps, labelProps, controlProps} = useSidePanel();
const [panelState, setPanelState] = React.useState<SidePanelTransitionStates>(
expanded ? 'expanded' : 'collapsed'
);

const expandedContent = (
<Flex alignItems="center" paddingY="s" paddingX="s">
<Flex marginInlineEnd="s">
<AccentIcon icon={rocketIcon} />
</Flex>
<Text as="h3" typeLevel="body.large" color="licorice500" fontWeight="bold" {...labelProps}>
Tasks Panel
</Text>
</Flex>
);

return (
<CanvasProvider theme={{canvas: {direction}}}>
<Flex height={320}>
<SidePanel {...panelProps} onStateTransition={setPanelState}>
<Flex cs={stylesOverride.viewPortContainer}>
<SidePanel {...panelProps}>
<SidePanel.ToggleButton {...controlProps} />
{panelState === 'expanded' ? (
expandedContent
) : (
<Text hidden {...labelProps}>
<Flex cs={stylesOverride.panel}>
{expanded && (
<Flex cs={stylesOverride.accentIcon}>
<AccentIcon icon={rocketIcon} />
</Flex>
)}
<Heading size="small" {...labelProps} hidden={!expanded ? true : undefined}>
Tasks Panel
</Text>
)}
</Heading>
</Flex>
</SidePanel>
<Flex
as="main"
alignItems="center"
justifyContent="center"
flexDirection="column"
flex={1}
flexBasis="auto"
>
<p>Toggle the content direction</p>
<SecondaryButton onClick={toggleDirection} role="button">
<Flex as="main" cs={stylesOverride.mainContent}>
<Text as="p" typeLevel="body.large">
Toggle the content direction
</Text>
<SecondaryButton onClick={toggleDirection}>
Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'}
</SecondaryButton>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,74 @@ import {
SidePanelTransitionStates,
} from '@workday/canvas-kit-preview-react/side-panel';
import {Flex} from '@workday/canvas-kit-react/layout';
import {Text} from '@workday/canvas-kit-react/text';
import {Heading, Text} from '@workday/canvas-kit-react/text';
import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {base, system} from '@workday/canvas-tokens-web';

const stylesOverride = {
viewport: createStyles({
height: px2rem(320),
}),
panel: createStyles({
alignItems: 'center',
paddingY: system.space.x4,
paddingX: system.space.x4,
}),
panelHeading: createStyles({
color: base.licorice500,
}),
main: createStyles({
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flex: 1,
flexBasis: 'auto',
}),
};

/*
* NOTE TO DEV:
* Spreading the `controlProps` onto an external control creates serious accessibility issues.
* - `aria-labelledby` id reference is invalid when the SidePanel is collapsed
* - `aria-labelledby` will change the name of "Toggle Side Panel" button to "Tasks Panel"
* - `aria-expanded` won't make sense to screen reader users when the expanded SidePanel content isn't following the control
* - `aria-controls` is unsupported by screen readers and will not allow users to navigate to the controlled content
*
* SOLUTION:
* - Pass the `controlProps` click handler function down to the external control component.
* - Add a toggle state to Button components with `aria-pressed` for screen readers,
* - OR use a similar toggle input like Checkbox or Switch.
*/
export const ExternalControl = () => {
const {expanded, panelProps, labelProps, controlProps} = useSidePanel({initialExpanded: false});
const [panelState, setPanelState] = React.useState<SidePanelTransitionStates>(
expanded ? 'expanded' : 'collapsed'
);

return (
<Flex height={320}>
<Flex cs={stylesOverride.viewport}>
<SidePanel
as="aside"
{...panelProps}
onExpandedChange={expanded => {
console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`);
}}
onStateTransition={setPanelState}
>
{panelState === 'expanded' && (
<Flex alignItems="center" paddingY="s" paddingX="s">
<Text
as="h3"
typeLevel="body.large"
color="licorice500"
fontWeight="bold"
{...labelProps}
>
<Flex cs={stylesOverride.panel}>
<Heading size="small" cs={stylesOverride.panelHeading} {...labelProps}>
Tasks Panel
</Text>
</Heading>
</Flex>
)}
</SidePanel>
<Flex
as="main"
alignItems="center"
justifyContent="center"
flexDirection="column"
flex={1}
flexBasis="auto"
>
<p>Control the panel externally</p>
<SecondaryButton {...controlProps} role="button">
Toggle Side Panel
<Flex as="main" cs={stylesOverride.main}>
<Text as="p" typeLevel="body.large">
Control the panel externally
</Text>
<SecondaryButton onClick={controlProps.onClick} aria-pressed={expanded}>
Show Side Panel
</SecondaryButton>
</Flex>
</Flex>
Expand Down
29 changes: 24 additions & 5 deletions modules/preview-react/side-panel/stories/examples/HiddenName.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import * as React from 'react';
import {SidePanel, useSidePanel} from '@workday/canvas-kit-preview-react/side-panel';
import {Flex} from '@workday/canvas-kit-react/layout';
import {AccessibleHide} from '@workday/canvas-kit-react/common';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

const stylesOverride = {
viewport: createStyles({
height: px2rem(320),
}),
main: createStyles({
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flex: 1,
flexBasis: 'auto',
}),
};

export const HiddenName = () => {
const {panelProps, labelProps, controlProps} = useSidePanel();

return (
<Flex height={320}>
<Flex cs={stylesOverride.viewport}>
<SidePanel
as="aside"
{...panelProps}
onExpandedChange={expanded => {
console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`);
Expand All @@ -17,10 +33,13 @@ export const HiddenName = () => {
}}
>
<SidePanel.ToggleButton {...controlProps} />
<span hidden {...labelProps}>
Side Panel with a hidden label
</span>
<AccessibleHide {...labelProps}>Hidden Title</AccessibleHide>
</SidePanel>
<Flex as="main" cs={stylesOverride.main}>
<Text as="p" typeLevel="body.large">
Side Panel with a hidden title text.
</Text>
</Flex>
</Flex>
);
};
Loading

0 comments on commit d8ce2bd

Please sign in to comment.