UNPKG

@workday/canvas-kit-docs

Version:

Documentation components of Canvas Kit components

287 lines (205 loc) 11.1 kB
import { ExampleCodeBlock, SymbolDoc, Specifications, StorybookStatusIndicator, } from '@workday/canvas-kit-docs'; import Basic from './examples/Basic'; import Heading from './examples/Heading'; import AlternatePanel from './examples/Variant'; import ExternalControl from './examples/ExternalControl'; import RightOrigin from './examples/RightOrigin'; import AlwaysOpen from './examples/AlwaysOpen'; import OnStateTransition from './examples/OnStateTransition'; # Canvas Kit Side Panel <StorybookStatusIndicator type="new" /> `SidePanel` is a collapsible container that anchors to the left or right side of the screen. It uses the model pattern for state management and is fully accessible. [> Workday Design Reference](https://design.workday.com/components/containers/side-panel) ## Installation ```sh yarn add @workday/canvas-kit-labs-react ``` ## Migrating from Preview If you're migrating from `@workday/canvas-kit-preview-react/side-panel`, here are the key API changes: ### Import Changes ```tsx // Before (preview-react) import {SidePanel, useSidePanel} from '@workday/canvas-kit-preview-react/side-panel'; // After (labs-react) import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; ``` ### Hook API Changes | Preview (`useSidePanel`) | Labs (`useSidePanelModel`) | | -------------------------------- | ------------------------------------------------------- | | `initialExpanded: boolean` | `initialTransitionState: 'expanded' \| 'collapsed'` | | `origin: 'left' \| 'right'` | `origin: 'start' \| 'end'` | | Returns `expanded: boolean` | Returns `model.state.transitionState` | | Returns `setExpanded(bool)` | Use `model.events.expand()` / `model.events.collapse()` | | Returns `panelProps` to spread | Props applied automatically via `elemPropsHook` | | Returns `labelProps` to spread | Use `id={model.state.labelId}` on label element | | Returns `controlProps` to spread | Props applied automatically to `SidePanel.ToggleButton` | ### Component API Changes | Preview | Labs | | ---------------------------------------------- | ------------------------------------------------- | | `<SidePanel {...panelProps}>` | `<SidePanel model={model}>` or just `<SidePanel>` | | `<SidePanel.ToggleButton {...controlProps} />` | `<SidePanel.ToggleButton />` | | `<Heading {...labelProps}>` | `<Heading id={model.state.labelId}>` | | `expanded` prop on SidePanel | Managed by model's `transitionState` | | `touched` prop on SidePanel | Managed internally | | `onExpandedChange` callback | Use `onStateTransition` and derive expanded state | | `onStateTransition` on component | `onStateTransition` on model config | ### Code Migration Example ```tsx // Before (preview-react) const {expanded, panelProps, labelProps, controlProps} = useSidePanel({ initialExpanded: false, }); <SidePanel {...panelProps} origin="right" onExpandedChange={exp => console.log(exp)}> <SidePanel.ToggleButton {...controlProps} /> <Heading {...labelProps}>Panel Title</Heading> {expanded && <Content />} </SidePanel>; // After (labs-react) const model = useSidePanelModel({ initialTransitionState: 'collapsed', origin: 'end', onStateTransition: state => { const isExpanded = state === 'expanded' || state === 'expanding'; console.log(isExpanded); }, }); <SidePanel model={model}> <SidePanel.ToggleButton /> <Heading id={model.state.labelId}>Panel Title</Heading> {model.state.transitionState === 'expanded' && <Content />} </SidePanel>; ``` ### Checking Expanded State ```tsx // Before (preview-react) if (expanded) { /* ... */ } // After (labs-react) - for exact state if (model.state.transitionState === 'expanded') { /* ... */ } // After (labs-react) - including animation states const isExpanded = model.state.transitionState === 'expanded' || model.state.transitionState === 'expanding'; ``` ## Usage ### Basic Example `SidePanel` is composed of three parts: - The panel container (with an optional `model` prop) - A heading (`SidePanel.Heading`) for the panel that is visually hidden when the panel is collapsed - A toggle button (`SidePanel.ToggleButton`) to control the expand / collapse states Bidirectional support is built into `SidePanel`. As seen in the example below, CSS Flexbox flips the page layout and the panel's contents. `SidePanel` also has logic to flip the position and direction of the `ToggleButton` as well as the direction of the expand / collapse animation. If you're using CSS Flexbox for layouts and using the provided components, you shouldn't have to provide any custom logic or styling for bidirectional support. <ExampleCodeBlock code={Basic} /> ### Hidden Name `SidePanel`'s `<section>` element container should always have an accessible name to help screen reader users understand the purpose of the panel. For this reason, we recommend using the `SidePanel.Heading` component and setting the `hidden` prop to `true`. This will visually hide the heading while keeping it accessible to screen readers. <ExampleCodeBlock code={Heading} /> ### Alternate Variant `SidePanel` has one variant, `alternate`, which you can supply as a top-level prop. Default depth of `alternate` variant is 5, if `alternate` SidePanel has an overlay behavior the depth 6 should be used (this case is covered in the Examples section). <ExampleCodeBlock code={AlternatePanel} /> ### External Control Sometimes you'll want to control `SidePanel`'s expand / collapse behavior from outside the component. You can use the model's events (`model.events.expand()` and `model.events.collapse()`) to programmatically control the panel. #### Notes about accessibility When using external controls, be mindful of accessibility: - Use `aria-pressed` on toggle buttons to indicate the current state - The `SidePanel.ToggleButton` inside the panel automatically receives the correct ARIA attributes - External buttons should have their own accessible labels (don't rely on `aria-labelledby` pointing to the panel's label) In the following example, we use the model's `transitionState` to determine the button's pressed state and call `model.events.expand()` or `model.events.collapse()` on click. <ExampleCodeBlock code={ExternalControl} /> ### Right Origin By default, `SidePanel` uses a `start` origin (left in LTR, right in RTL). This sets the `ToggleButton`'s position and direction as well as the direction of the animation. You can set the origin to `"end"` to flip these. The origin uses logical properties (`start`/`end`) for proper bidirectional support. <ExampleCodeBlock code={RightOrigin} /> ### Always Open If you do not need `SidePanel`'s expand / collapse behavior, you can simply omit the `ToggleButton`. <ExampleCodeBlock code={AlwaysOpen} /> ### Deriving Expanded State If you need a simple boolean `expanded` state (similar to the preview-react `onExpandedChange` callback), you can derive it from the `transitionState` using the `onStateTransition` callback on the model. ### onStateTransition The `onStateTransition` callback is called whenever the panel's transition state changes. This includes all four states: `expanding`, `expanded`, `collapsing`, and `collapsed`. You can pass this callback directly to the `SidePanel` component or to the `useSidePanelModel` hook. The transition flow is: 1. **Collapsing**: `expanded``collapsing``collapsed` 2. **Expanding**: `collapsed``expanding``expanded` This is useful for: - Triggering side effects when the panel state changes - Syncing the panel state with external state management - Animating child components based on the transition state <ExampleCodeBlock code={OnStateTransition} /> ### Accessibility `SidePanel` renders a `<section>` element with an accessible name provided by `aria-labelledby`, which references the `SidePanel.Heading` component. This ensures screen reader users understand the purpose of the panel. #### Panel and Heading - The `SidePanel.Heading` provides the accessible name for the panel via `aria-labelledby` - When the panel is collapsed, the heading is automatically hidden visually but remains accessible to screen readers - Use the `hidden` prop on `SidePanel.Heading` if you want the heading always visually hidden #### Toggle Button - `SidePanel.ToggleButton` automatically includes `aria-controls` (references the panel's `id`), `aria-pressed` (indicates current state), and `aria-describedby` (references the panel's heading) - Developers must provide a static `aria-label` string on `SidePanel.ToggleButton` to describe the button's purpose (e.g., "Collapse View"). Avoid using ambiguous terms like "Toggle" in the label. Since `aria-pressed` communicates the state, avoid dynamically updating `aria-label` - The button includes a Tooltip with customizable text via `tooltipTextExpand` and `tooltipTextCollapse` props (defaults: "Expand View" and "Collapse View") - For optimal keyboard navigation, place `SidePanel.ToggleButton` as the first focusable element in the panel ## Component API <SymbolDoc name="SidePanel" fileName="/labs-react/" /> ## Hooks ### useSidePanelModel The `useSidePanelModel` hook creates a model for managing the SidePanel's state and events. You can pass this model to the `SidePanel` component, or let the component create one internally. ```tsx import {useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; // Create a model with custom configuration const model = useSidePanelModel({ initialTransitionState: 'collapsed', origin: 'end', onStateTransition: state => console.log('State:', state), }); // Access state model.state.transitionState; // 'expanded' | 'expanding' | 'collapsed' | 'collapsing' model.state.panelId; // unique ID for the panel model.state.labelId; // unique ID for the label // Trigger events model.events.expand(); // Set to expanded (no animation) model.events.collapse(); // Set to collapsed (no animation) model.events.handleAnimationStart(); // Start expand/collapse animation ``` <SymbolDoc name="useSidePanelModel" fileName="/labs-react/" /> ### useSidePanelContainer The `useSidePanelContainer` elemProps hook provides the necessary props for the SidePanel container element, including `id`, `aria-labelledby`, and `onTransitionEnd`. <SymbolDoc name="useSidePanelContainer" fileName="/labs-react/" /> ### useSidePanelToggleButtonElemProps The `useSidePanelToggleButtonElemProps` elemProps hook provides ARIA attributes for the toggle button, including `aria-controls`, `aria-expanded`, and `aria-labelledby`. <SymbolDoc name="useSidePanelToggleButtonElemProps" fileName="/labs-react/" /> ## Specifications <Specifications file="SidePanelLabs.spec.ts" name="Side Panel" />