From 21cc1ca5853ea533e01ae81976188bde7bb19857 Mon Sep 17 00:00:00 2001 From: William Stanton <40372497+williamjstanton@users.noreply.github.com> Date: Mon, 13 Jan 2025 05:00:50 -0800 Subject: [PATCH 1/2] docs: Refreshing preview SidePanel storybook examples (#3056) Update preview SidePanel examples to provide better accessibility guidance. - Added a semantic list markup inside the navigation - Demonstrate a "compact view" of the nav that used icon buttons with Tooltips, and the Canvas Menu component for fly-out menus. - Updated to use new style tokens in examples [category:Documentation] Co-authored-by: William Stanton --- .../side-panel/stories/SidePanel.mdx | 31 +- .../stories/examples/AlwaysOpen.tsx | 53 ++-- .../side-panel/stories/examples/Basic.tsx | 74 ++--- .../stories/examples/ExternalControl.tsx | 70 +++-- .../stories/examples/HiddenName.tsx | 29 +- .../stories/examples/OnExpandedChange.tsx | 28 +- .../stories/examples/OnStateTransition.tsx | 33 ++- .../stories/examples/RightOrigin.tsx | 79 +++--- .../side-panel/stories/examples/Variant.tsx | 72 ++--- .../react/_examples/stories/mdx/SidePanel.mdx | 19 +- .../mdx/examples/SidePanelWithNavigation.tsx | 268 ++++++++++-------- .../mdx/examples/SidePanelWithOverlay.tsx | 48 +++- 12 files changed, 500 insertions(+), 304 deletions(-) diff --git a/modules/preview-react/side-panel/stories/SidePanel.mdx b/modules/preview-react/side-panel/stories/SidePanel.mdx index 08c4433878..5489fd132e 100644 --- a/modules/preview-react/side-panel/stories/SidePanel.mdx +++ b/modules/preview-react/side-panel/stories/SidePanel.mdx @@ -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 `
` 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. @@ -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. + ### Right Origin diff --git a/modules/preview-react/side-panel/stories/examples/AlwaysOpen.tsx b/modules/preview-react/side-panel/stories/examples/AlwaysOpen.tsx index 931a7776a8..cf519a1095 100644 --- a/modules/preview-react/side-panel/stories/examples/AlwaysOpen.tsx +++ b/modules/preview-react/side-panel/stories/examples/AlwaysOpen.tsx @@ -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 ( - + - - - + + + Tasks Panel - + + + + This is the main content section. + + ); }; diff --git a/modules/preview-react/side-panel/stories/examples/Basic.tsx b/modules/preview-react/side-panel/stories/examples/Basic.tsx index 71bb143e58..3915880a6d 100644 --- a/modules/preview-react/side-panel/stories/examples/Basic.tsx +++ b/modules/preview-react/side-panel/stories/examples/Basic.tsx @@ -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( - expanded ? 'expanded' : 'collapsed' - ); - - const expandedContent = ( - - - - - - Tasks Panel - - - ); return ( - - + + - {panelState === 'expanded' ? ( - expandedContent - ) : ( - - )} + + - -

Toggle the content direction

- + + + Toggle the content direction + + Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'} diff --git a/modules/preview-react/side-panel/stories/examples/ExternalControl.tsx b/modules/preview-react/side-panel/stories/examples/ExternalControl.tsx index 54dcf971da..fccaa618dd 100644 --- a/modules/preview-react/side-panel/stories/examples/ExternalControl.tsx +++ b/modules/preview-react/side-panel/stories/examples/ExternalControl.tsx @@ -5,9 +5,45 @@ 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( @@ -15,9 +51,8 @@ export const ExternalControl = () => { ); return ( - + { console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`); @@ -25,30 +60,19 @@ export const ExternalControl = () => { onStateTransition={setPanelState} > {panelState === 'expanded' && ( - - + + Tasks Panel - + )} - -

Control the panel externally

- - Toggle Side Panel + + + Control the panel externally + + + Show Side Panel
diff --git a/modules/preview-react/side-panel/stories/examples/HiddenName.tsx b/modules/preview-react/side-panel/stories/examples/HiddenName.tsx index d24b31b300..dfbb12c618 100644 --- a/modules/preview-react/side-panel/stories/examples/HiddenName.tsx +++ b/modules/preview-react/side-panel/stories/examples/HiddenName.tsx @@ -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 ( - + { console.log(`expanded prop is: ${expanded ? 'true' : 'false'}`); @@ -17,10 +33,13 @@ export const HiddenName = () => { }} > - + Hidden Title + + + Side Panel with a hidden title text. + + ); }; diff --git a/modules/preview-react/side-panel/stories/examples/OnExpandedChange.tsx b/modules/preview-react/side-panel/stories/examples/OnExpandedChange.tsx index bcaf4b047a..9b9cb7fdfc 100644 --- a/modules/preview-react/side-panel/stories/examples/OnExpandedChange.tsx +++ b/modules/preview-react/side-panel/stories/examples/OnExpandedChange.tsx @@ -1,6 +1,22 @@ import * as React from 'react'; import {Flex} from '@workday/canvas-kit-react/layout'; import {SidePanel, useSidePanel} from '@workday/canvas-kit-preview-react/side-panel'; +import {Text} from '@workday/canvas-kit-react/text'; +import {AccessibleHide} from '@workday/canvas-kit-react/common'; +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 OnExpandedChange = () => { const {expanded, panelProps, labelProps, controlProps} = useSidePanel(); @@ -10,15 +26,15 @@ export const OnExpandedChange = () => { }; return ( - + - + Hidden Title - -

Side panel is {expanded ? 'expanded' : 'collapsed'}.

+ + + Side panel is {expanded ? 'expanded' : 'collapsed'}. +
); diff --git a/modules/preview-react/side-panel/stories/examples/OnStateTransition.tsx b/modules/preview-react/side-panel/stories/examples/OnStateTransition.tsx index b5e933d1f0..b7bfc12b05 100644 --- a/modules/preview-react/side-panel/stories/examples/OnStateTransition.tsx +++ b/modules/preview-react/side-panel/stories/examples/OnStateTransition.tsx @@ -5,27 +5,42 @@ import { useSidePanel, SidePanelTransitionStates, } from '@workday/canvas-kit-preview-react/side-panel'; +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 OnStateTransition = () => { const {panelProps, labelProps, controlProps} = useSidePanel(); - const [transitionState, setTransitionState] = React.useState( - 'expanded' - ); + const [transitionState, setTransitionState] = + React.useState('expanded'); const handleStateTransition = (transition: SidePanelTransitionStates) => { setTransitionState(transition); }; return ( - + - + Hidden Title - -

Side panel is {transitionState}.

+ + + Side panel is {transitionState}. +
); diff --git a/modules/preview-react/side-panel/stories/examples/RightOrigin.tsx b/modules/preview-react/side-panel/stories/examples/RightOrigin.tsx index 0718d6be68..772cabce7d 100644 --- a/modules/preview-react/side-panel/stories/examples/RightOrigin.tsx +++ b/modules/preview-react/side-panel/stories/examples/RightOrigin.tsx @@ -1,45 +1,49 @@ import * as React from 'react'; import {SecondaryButton} from '@workday/canvas-kit-react/button'; -import { - SidePanel, - useSidePanel, - SidePanelTransitionStates, -} from '@workday/canvas-kit-preview-react/side-panel'; +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 {CanvasProvider, styled} from '@workday/canvas-kit-react/common'; +import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {CanvasProvider} from '@workday/canvas-kit-react/common'; +import {createStyles, px2rem} from '@workday/canvas-kit-styling'; +import {system} from '@workday/canvas-tokens-web'; + // local helper hook for setting content direction; import {useDirection} from './useDirection'; -const StyledSidePanel = styled(SidePanel)({ - marginLeft: 'auto', -}); +const stylesOverride = { + viewport: createStyles({ + height: px2rem(320), + }), + panelContainer: createStyles({ + marginLeft: 'auto', + }), + panel: createStyles({ + alignItems: 'center', + justifyContent: 'flex-end', + paddingY: system.space.x4, + paddingX: system.space.x4, + }), + main: createStyles({ + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + flex: 1, + flexBasis: 'auto', + }), +}; const RightPanel = () => { const {expanded, panelProps, labelProps, controlProps} = useSidePanel(); - const [panelState, setPanelState] = React.useState( - expanded ? 'expanded' : 'collapsed' - ); - - const expandedContent = ( - - - Tasks Panel - - - ); return ( - + - {panelState === 'expanded' ? ( - expandedContent - ) : ( - - )} - + +
+ ); }; @@ -48,17 +52,12 @@ export const RightOrigin = () => { return ( - - -

Toggle the content direction

- + + + + Toggle the content direction + + Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'} diff --git a/modules/preview-react/side-panel/stories/examples/Variant.tsx b/modules/preview-react/side-panel/stories/examples/Variant.tsx index 47a73aee7a..b3a0ad3eca 100644 --- a/modules/preview-react/side-panel/stories/examples/Variant.tsx +++ b/modules/preview-react/side-panel/stories/examples/Variant.tsx @@ -1,54 +1,54 @@ import * as React from 'react'; import {SecondaryButton} from '@workday/canvas-kit-react/button'; -import { - SidePanel, - useSidePanel, - SidePanelTransitionStates, -} from '@workday/canvas-kit-preview-react/side-panel'; +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 {CanvasProvider} from '@workday/canvas-kit-react/common'; +import {createStyles, px2rem} from '@workday/canvas-kit-styling'; +import {base, system} from '@workday/canvas-tokens-web'; + // local helper hook for setting content direction; import {useDirection} from './useDirection'; +const stylesOverride = { + viewport: createStyles({ + height: px2rem(320), + backgroundColor: base.soap100, + }), + panel: createStyles({ + alignItems: 'center', + paddingY: system.space.x4, + paddingX: system.space.x4, + }), + main: createStyles({ + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + flex: 1, + flexBasis: 'auto', + }), +}; + export const AlternatePanel = () => { const {direction, toggleDirection} = useDirection(); const {expanded, panelProps, labelProps, controlProps} = useSidePanel(); - const [panelState, setPanelState] = React.useState( - expanded ? 'expanded' : 'collapsed' - ); - - const expandedContent = ( - - - Alternate Panel - - - ); return ( - - + + - {panelState === 'expanded' ? ( - expandedContent - ) : ( - - )} + + + - -

Toggle the content direction

- + + + Toggle the content direction + + Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'} diff --git a/modules/react/_examples/stories/mdx/SidePanel.mdx b/modules/react/_examples/stories/mdx/SidePanel.mdx index ca451b51ea..b0cf59f65c 100644 --- a/modules/react/_examples/stories/mdx/SidePanel.mdx +++ b/modules/react/_examples/stories/mdx/SidePanel.mdx @@ -6,7 +6,24 @@ import {WithOverlay} from './examples/SidePanelWithOverlay'; # Canvas Kit Examples -## Side Panel With Navigation +## Side Panel As Navigation + +This example demonstrates SidePanel as a navigation system that can be reduced into a "compact view" +to save horizontal space on screen. It combines semantic HTML nested list markup with Canvas Kit's +`` component to create an accordion. When in compact view, the Canvas Kit `` +component is used to create the fly-out menus. Both of these components are implemented according to +the [W3C ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/) to provide a +wide range of support for accessibility. + +- The `` uses the `as` prop to render with a semantic HTML `