UNPKG

@workday/canvas-kit-docs

Version:

Documentation components of Canvas Kit components

256 lines (181 loc) 9.58 kB
import {ExampleCodeBlock, SymbolDoc, Specifications, StorybookStatusIndicator} from '@workday/canvas-kit-docs'; import Basic from './examples/Basic'; import HiddenName from './examples/HiddenName'; 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) - An accessible name (using `model.state.labelId` on a visible or hidden element) - A toggle button (`SidePanel.ToggleButton`) to control the expand / collapse states The component automatically handles: - ARIA attributes (`aria-labelledby`, `aria-controls`, `aria-expanded`) - Transition states (`expanding`, `expanded`, `collapsing`, `collapsed`) - CSS transitions for smooth animations 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` must always have an accessible label for both the HTML `<section>` container and the `ToggleButton`. The label element must have `id={model.state.labelId}` to properly connect it to the panel and toggle button via `aria-labelledby`. The label can be visually hidden using `AccessibleHide` which relies on CSS properties to hide text visually while keeping it available for screen readers. <ExampleCodeBlock code={HiddenName} /> ### 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} /> ## 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" />