From c1dbf299a02d730601f60a3d76b3757ee951cd1f Mon Sep 17 00:00:00 2001 From: "manuel.carrera" Date: Mon, 5 Jan 2026 08:56:26 -0700 Subject: [PATCH 1/3] feat: Promote SidePanel from Labs to Main --- ...ePanelLabs.spec.tsx => SidePanel.spec.tsx} | 2 +- modules/codemod/lib/v5/maps.ts | 2 +- modules/labs-react/index.ts | 1 - modules/labs-react/side-panel/README.md | 7 - modules/labs-react/side-panel/index.ts | 3 - .../labs-react/side-panel/lib/SidePanel.tsx | 131 ------ .../labs-react/side-panel/spec/SSR.spec.tsx | 13 - .../labs-react/side-panel/spec/tsconfig.json | 3 - .../side-panel/stories/tsconfig.json | 3 - modules/react/side-panel/.npmignore | 2 - .../{labs-react => react}/side-panel/LICENSE | 0 modules/react/side-panel/README.md | 175 +------- modules/react/side-panel/index.ts | 2 + modules/react/side-panel/lib/SidePanel.tsx | 372 ++++++------------ .../react/side-panel/lib/SidePanelHeading.tsx | 52 +++ .../side-panel/lib/SidePanelToggleButton.tsx | 22 +- .../side-panel/lib/useSidePanelModel.ts | 0 modules/react/side-panel/spec/SSR.spec.tsx | 6 +- .../react/side-panel/spec/SidePanel.spec.tsx | 60 --- .../side-panel/stories/SidePanel.mdx | 175 ++++---- .../side-panel/stories/SidePanel.stories.ts | 4 +- .../stories/examples/AlwaysOpen.tsx | 8 +- .../side-panel/stories/examples/Basic.tsx | 17 +- .../stories/examples/ExternalControl.tsx | 19 +- .../side-panel/stories/examples/Heading.tsx} | 9 +- .../stories/examples/OnStateTransition.tsx | 7 +- .../stories/examples/RightOrigin.tsx | 17 +- .../side-panel/stories/examples/Variant.tsx | 17 +- .../stories/examples/useDirection.ts | 0 .../stories/testingCypress.stories.tsx | 2 +- .../stories/testingVisual.stories.tsx | 0 .../visual-testing/SidePanel.stories.tsx | 98 ----- 32 files changed, 339 insertions(+), 890 deletions(-) rename cypress/component/{SidePanelLabs.spec.tsx => SidePanel.spec.tsx} (96%) delete mode 100644 modules/labs-react/side-panel/README.md delete mode 100644 modules/labs-react/side-panel/index.ts delete mode 100644 modules/labs-react/side-panel/lib/SidePanel.tsx delete mode 100644 modules/labs-react/side-panel/spec/SSR.spec.tsx delete mode 100644 modules/labs-react/side-panel/spec/tsconfig.json delete mode 100644 modules/labs-react/side-panel/stories/tsconfig.json delete mode 100644 modules/react/side-panel/.npmignore rename modules/{labs-react => react}/side-panel/LICENSE (100%) create mode 100644 modules/react/side-panel/lib/SidePanelHeading.tsx rename modules/{labs-react => react}/side-panel/lib/SidePanelToggleButton.tsx (90%) rename modules/{labs-react => react}/side-panel/lib/useSidePanelModel.ts (100%) delete mode 100644 modules/react/side-panel/spec/SidePanel.spec.tsx rename modules/{labs-react => react}/side-panel/stories/SidePanel.mdx (51%) rename modules/{labs-react => react}/side-panel/stories/SidePanel.stories.ts (91%) rename modules/{labs-react => react}/side-panel/stories/examples/AlwaysOpen.tsx (85%) rename modules/{labs-react => react}/side-panel/stories/examples/Basic.tsx (79%) rename modules/{labs-react => react}/side-panel/stories/examples/ExternalControl.tsx (85%) rename modules/{labs-react/side-panel/stories/examples/HiddenName.tsx => react/side-panel/stories/examples/Heading.tsx} (82%) rename modules/{labs-react => react}/side-panel/stories/examples/OnStateTransition.tsx (87%) rename modules/{labs-react => react}/side-panel/stories/examples/RightOrigin.tsx (84%) rename modules/{labs-react => react}/side-panel/stories/examples/Variant.tsx (81%) rename modules/{labs-react => react}/side-panel/stories/examples/useDirection.ts (100%) rename modules/{labs-react => react}/side-panel/stories/testingCypress.stories.tsx (98%) rename modules/{labs-react => react}/side-panel/stories/testingVisual.stories.tsx (100%) delete mode 100644 modules/react/side-panel/stories/visual-testing/SidePanel.stories.tsx diff --git a/cypress/component/SidePanelLabs.spec.tsx b/cypress/component/SidePanel.spec.tsx similarity index 96% rename from cypress/component/SidePanelLabs.spec.tsx rename to cypress/component/SidePanel.spec.tsx index c4500a6779..a012098673 100644 --- a/cypress/component/SidePanelLabs.spec.tsx +++ b/cypress/component/SidePanel.spec.tsx @@ -3,7 +3,7 @@ import { AsAside, AsDiv, FirstFocusable, -} from '@workday/canvas-kit-labs-react/side-panel/stories/testingCypress.stories'; +} from '@workday/canvas-kit-react/side-panel/stories/testingCypress.stories'; describe('Side Panel', () => { const name = /Accessible Label Name/i; diff --git a/modules/codemod/lib/v5/maps.ts b/modules/codemod/lib/v5/maps.ts index 4ffdf65419..4a7fac72dc 100644 --- a/modules/codemod/lib/v5/maps.ts +++ b/modules/codemod/lib/v5/maps.ts @@ -67,7 +67,7 @@ export const sourceMap: { '@workday/canvas-kit-labs-react-menu': '@workday/canvas-kit-labs-react/menu', '@workday/canvas-kit-labs-react-pagination': '@workday/canvas-kit-labs-react/pagination', '@workday/canvas-kit-labs-react-select': '@workday/canvas-kit-labs-react/select', - '@workday/canvas-kit-labs-react-side-panel': '@workday/canvas-kit-labs-react/side-panel', + '@workday/canvas-kit-labs-react-side-panel': '@workday/canvas-kit-react/side-panel', '@workday/canvas-kit-labs-react-tabs': '@workday/canvas-kit-labs-react/tabs', }; diff --git a/modules/labs-react/index.ts b/modules/labs-react/index.ts index 4ffbb4905e..ed625199bf 100644 --- a/modules/labs-react/index.ts +++ b/modules/labs-react/index.ts @@ -1,3 +1,2 @@ export * from './ai-ingress-button'; -export * from './side-panel'; export * from './version'; diff --git a/modules/labs-react/side-panel/README.md b/modules/labs-react/side-panel/README.md deleted file mode 100644 index 713d778e10..0000000000 --- a/modules/labs-react/side-panel/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Canvas Kit Side Panel - -View the -[documentation for Side Panel](https://workday.github.io/canvas-kit/?path=/docs/labs-side-panel--docs) -on Storybook. - -[> Workday Design Reference](https://design.workday.com/components/containers/side-panel) diff --git a/modules/labs-react/side-panel/index.ts b/modules/labs-react/side-panel/index.ts deleted file mode 100644 index 9816e34c88..0000000000 --- a/modules/labs-react/side-panel/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './lib/SidePanel'; -export * from './lib/SidePanelToggleButton'; -export * from './lib/useSidePanelModel'; diff --git a/modules/labs-react/side-panel/lib/SidePanel.tsx b/modules/labs-react/side-panel/lib/SidePanel.tsx deleted file mode 100644 index c39271ef5e..0000000000 --- a/modules/labs-react/side-panel/lib/SidePanel.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import * as React from 'react'; -import {createContainer, createElemPropsHook} from '@workday/canvas-kit-react/common'; -import {createStencil, handleCsProp, px2rem} from '@workday/canvas-kit-styling'; -import {system} from '@workday/canvas-tokens-web'; -import {useSidePanelModel} from './useSidePanelModel'; -import {SidePanelToggleButton} from './SidePanelToggleButton'; - -/** - * Adds the necessary props to the SidePanel container element. - * This includes the `id` and `aria-labelledby` attributes for accessibility. - */ -export const useSidePanelContainer = createElemPropsHook(useSidePanelModel)(({state, events}) => { - return { - id: state.panelId, - 'aria-labelledby': state.labelId, - onTransitionEnd: events.handleAnimationEnd, - }; -}); - -export type SidePanelVariant = 'standard' | 'alternate'; - -export interface SidePanelProps { - /** - * The width of the component (in `px` if it's a `number`) when it is collapsed. - * - * @default 64 - */ - collapsedWidth?: number | string; - /** - * The width of the component (in `px` if it's a `number`) when it is expanded. - * - * @default 320 - */ - expandedWidth?: number | string; - /** - * The style variant of the side panel. 'standard' uses a lighter gray background (`system.color.bg.alt.softer`), no depth. 'alternate' uses a white background with depth (`system.color.bg.default` and level 5 depth). - * - * @default 'standard' - */ - variant?: SidePanelVariant; - children?: React.ReactNode; -} - -export const panelStencil = createStencil({ - vars: { - expandedWidth: '', - collapsedWidth: '', - }, - base: () => ({ - overflow: 'hidden', - position: 'relative', - height: '100%', - outline: `${px2rem(1)} solid transparent`, - transition: 'width ease-out 200ms, max-width ease-out 200ms', - }), - modifiers: { - variant: { - alternate: { - backgroundColor: system.color.bg.default, - boxShadow: system.depth[5], - }, - standard: { - backgroundColor: system.color.bg.alt.softer, - }, - }, - expanded: { - expanded: ({expandedWidth}) => ({ - width: expandedWidth, - maxWidth: expandedWidth, - }), - collapsed: ({collapsedWidth}) => ({ - width: collapsedWidth, - maxWidth: collapsedWidth, - }), - expanding: ({expandedWidth}) => ({ - width: expandedWidth, - maxWidth: expandedWidth, - }), - collapsing: ({collapsedWidth}) => ({ - width: collapsedWidth, - maxWidth: collapsedWidth, - }), - }, - }, -}); - -export const SidePanel = createContainer('section')({ - displayName: 'SidePanel', - modelHook: useSidePanelModel, - elemPropsHook: useSidePanelContainer, - subComponents: { - /** - * `SidePanel.ToggleButton` is a control that toggles between expanded and collapsed states. - * It must be used within the `SidePanel` component as a child. For accessibility purposes, - * it should be the first focusable element in the panel. - * - * The button automatically receives `aria-controls`, `aria-expanded`, and `aria-labelledby` - * attributes from the model. - */ - ToggleButton: SidePanelToggleButton, - }, -})( - ( - { - collapsedWidth = 64, - expandedWidth = 320, - variant = 'standard', - children, - ...elemProps - }: SidePanelProps, - Element, - model - ) => { - return ( - - {children} - - ); - } -); diff --git a/modules/labs-react/side-panel/spec/SSR.spec.tsx b/modules/labs-react/side-panel/spec/SSR.spec.tsx deleted file mode 100644 index aa7697380f..0000000000 --- a/modules/labs-react/side-panel/spec/SSR.spec.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @jest-environment node - */ -import React from 'react'; -import {renderToString} from 'react-dom/server'; -import {SidePanel} from '@workday/canvas-kit-labs-react/side-panel'; - -describe('SidePanel', () => { - it('should render on a server without crashing', () => { - const ssrRender = () => renderToString(); - expect(ssrRender).not.toThrow(); - }); -}); diff --git a/modules/labs-react/side-panel/spec/tsconfig.json b/modules/labs-react/side-panel/spec/tsconfig.json deleted file mode 100644 index e7f236e49a..0000000000 --- a/modules/labs-react/side-panel/spec/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../tsconfig.spec.json" -} diff --git a/modules/labs-react/side-panel/stories/tsconfig.json b/modules/labs-react/side-panel/stories/tsconfig.json deleted file mode 100644 index 4fb6aa9472..0000000000 --- a/modules/labs-react/side-panel/stories/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../tsconfig.stories.json" -} diff --git a/modules/react/side-panel/.npmignore b/modules/react/side-panel/.npmignore deleted file mode 100644 index 5b45f6604c..0000000000 --- a/modules/react/side-panel/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -tsconfig.json -yarn.lock diff --git a/modules/labs-react/side-panel/LICENSE b/modules/react/side-panel/LICENSE similarity index 100% rename from modules/labs-react/side-panel/LICENSE rename to modules/react/side-panel/LICENSE diff --git a/modules/react/side-panel/README.md b/modules/react/side-panel/README.md index deee331c63..713d778e10 100644 --- a/modules/react/side-panel/README.md +++ b/modules/react/side-panel/README.md @@ -1,174 +1,7 @@ # Canvas Kit Side Panel -A component that provides a container for navigation elements. It provides options to be opened from -either the left or right side of the screen. +View the +[documentation for Side Panel](https://workday.github.io/canvas-kit/?path=/docs/labs-side-panel--docs) +on Storybook. -## Installation - -```sh -yarn add @workday/canvas-kit-react -``` - -# Side Panel - -## Usage - -```tsx -import * as React from 'react'; -import {SecondaryButton, TertiaryButton} from '@workday/canvas-kit-react/button'; -import {SidePanel} from '@workday/canvas-kit-react/side-panel'; - -interface SidePanelState { - open: boolean; -} - -class SidePanelExample extends React.Component<{}, SidePanelState> { - public state = { - open: true, - }; - public render() { - const {open} = this.state; - return ( - - {open ? ( - Add New - ) : ( - - - - )} -
    -
  • {open && Home}
  • -
  • {open && Favorites}
  • -
  • {open && Items}
  • -
-
- ); - } - - private onClick = () => { - this.setState({ - open: !this.state.open, - }); - }; - - private handleBreakpoint = (open: boolean) => { - this.setState({ - open: open, - }); - }; -} -``` - -## Static Properties - -#### `OpenDirection: SidePanelOpenDirection` - -```tsx - -``` - -#### `BackgroundColor: SidePanelBackgroundColor` - -```tsx - -``` - ---- - -## Component Props - -### Required - -#### `open: boolean` - -> Determines if the side panel is open or closed. - ---- - -### Optional - -#### `onBreakpointChange: (aboveBreakpoint: boolean) => void;` - -> A function that is called when the screen size changes and reaches `breakpoint`. For example, if -> the user has their window at 1000px of width, and then resizes, this will get called when the -> window size reaches the value of the `breakpoint` prop. A boolean for whether the current window -> size is above or below the breakpoint is provided so you can control `open` based on the change. - ---- - -#### `onToggleClick: () => void` - -> Callback that handles clicking toggle button to open or close the side panel. The toggle button -> will only show if this prop is defined. - ---- - -#### `header: string | React.ReactNode` - -> Custom title or element to display as a header to the side panel. - ---- - -#### `closeNavigationLabel: string` - -> Aria label for closing the navigation - ---- - -#### `openNavigationLabel: string` - -> Aria label for opening the navigation - ---- - -#### `backgroundColor: SidePanelBackgroundColor` - -> Determines the background color of the side panel when it's `open` - -`SidePanelBackgroundColor.White` or `SidePanelBackgroundColor.Gray` or -`SidePanelBackgroundColor.Transparent` - -Default: `SidePanelBackgroundColor.White` - ---- - -#### `openDirection: SidePanelOpenDirection` - -> Determines from what side the side panel opens - -`SidePanelOpenDirection.Left` or `SidePanelOpenDirection.Right` - -Default: `SidePanelOpenDirection.Left` - ---- - -#### `padding: CanvasSpaceValues` - -> Adjust padding of the side panel when it's open. - -Default: `24px` - ---- - -#### `breakpoint: number` - -> The width at which the window size must be in order for `onBreakPointChange` to fire. Default -> value based on Ipad Landscape: -> https://responsivedesign.is/develop/browser-feature-support/media-queries-for-common-device-breakpoints/ - -Default: `768px` - -#### `openWidth: number` - -> Determines the width of the side panel when it's open. - -Default: `300px` +[> Workday Design Reference](https://design.workday.com/components/containers/side-panel) diff --git a/modules/react/side-panel/index.ts b/modules/react/side-panel/index.ts index 43624000b4..9816e34c88 100644 --- a/modules/react/side-panel/index.ts +++ b/modules/react/side-panel/index.ts @@ -1 +1,3 @@ export * from './lib/SidePanel'; +export * from './lib/SidePanelToggleButton'; +export * from './lib/useSidePanelModel'; diff --git a/modules/react/side-panel/lib/SidePanel.tsx b/modules/react/side-panel/lib/SidePanel.tsx index 865056069b..741d20b2dc 100644 --- a/modules/react/side-panel/lib/SidePanel.tsx +++ b/modules/react/side-panel/lib/SidePanel.tsx @@ -1,277 +1,137 @@ import * as React from 'react'; -import styled from '@emotion/styled'; - -import {CanvasSystemIcon} from '@workday/design-assets-types'; -import {colors, space, CanvasSpaceValues} from '@workday/canvas-kit-react/tokens'; -import {TertiaryButton, TertiaryButtonProps} from '@workday/canvas-kit-react/button'; -import {chevronLeftIcon, chevronRightIcon} from '@workday/canvas-system-icons-web'; -import {Heading} from '@workday/canvas-kit-react/text'; - +import {createContainer, createElemPropsHook} from '@workday/canvas-kit-react/common'; +import {createStencil, handleCsProp, px2rem} from '@workday/canvas-kit-styling'; +import {system} from '@workday/canvas-tokens-web'; +import {useSidePanelModel} from './useSidePanelModel'; +import {SidePanelToggleButton} from './SidePanelToggleButton'; +import {SidePanelHeading} from './SidePanelHeading'; /** - * @deprecated ⚠️ `SidePanelProps` in Main has been deprecated and will be removed in a future major version. Please use [`SidePanel` in Preview](https://workday.github.io/canvas-kit/?path=/docs/preview-side-panel--docs) instead. + * Adds the necessary props to the SidePanel container element. + * This includes the `id` and `aria-labelledby` attributes for accessibility. */ -export interface SidePanelProps extends React.HTMLAttributes { - /** - * If true, set the SidePanel to the open state. - * @default false; - */ - open: boolean; - /** - * The function called when the toggle button is clicked. The toggle button is only shown if this prop is defined. - */ - onToggleClick?: () => void; - /** - * The text or element to display as the SidePanel header. - */ - header?: string | React.ReactNode; - /** - * The side from which the SidePanel opens. Accepts `Left` or `Right`. - * @default SidePanelOpenDirection.Left - */ - openDirection?: SidePanelOpenDirection; - /** - * The function called when the window width changes and reaches a width equivalent to `breakpoint`. For example, if the window is resized from a width of `1000px`, this will be called when the window reaches a width equivalent to `breakpoint`. The `aboveBreakpoint` argument passed to the callback function indicates whether the current window width is above or below `breakpoint` so you can control `open` based on the change. - */ - onBreakpointChange?: (aboveBreakpoint: boolean) => void; - /** - * The padding of the SidePanel when it's open. - */ - padding?: CanvasSpaceValues; - /** - * The window width at which the SidePanel triggers `onBreakPointChange`. - * @default 768px - */ - breakpoint?: number; - /** - * The width of the SidePanel when it's open. - * @default 300px - */ - openWidth?: number; +export const useSidePanelContainer = createElemPropsHook(useSidePanelModel)(({state, events}) => { + return { + id: state.panelId, + 'aria-labelledby': state.labelId, + onTransitionEnd: events.handleAnimationEnd, + }; +}); + +export type SidePanelVariant = 'standard' | 'alternate'; + +export interface SidePanelProps { /** - * The background color of the SidePanel when it's open. - * @default SidePanelBackgroundColor.White + * The width of the component (in `px` if it's a `number`) when it is collapsed. + * + * @default 64 */ - backgroundColor?: SidePanelBackgroundColor; + collapsedWidth?: number | string; /** - * The `aria-label` that describes closing the navigation. - * @default 'close navigation' + * The width of the component (in `px` if it's a `number`) when it is expanded. + * + * @default 320 */ - closeNavigationAriaLabel?: string; + expandedWidth?: number | string; /** - * The `aria-label` that describes opening the navigation. - * @default 'open navigation' + * The style variant of the side panel. 'standard' uses a lighter gray background (`system.color.bg.alt.softer`), no depth. 'alternate' uses a white background with depth (`system.color.bg.default` and level 5 depth). + * + * @default 'standard' */ - openNavigationAriaLabel?: string; + variant?: SidePanelVariant; + children?: React.ReactNode; } -/** - * @deprecated ⚠️ `SidePanelState` in Main has been deprecated and will be removed in a future major version. - */ -export interface SidePanelState { - screenSize: number; -} - -/** - * @deprecated ⚠️ `SidePanelOpenDirection` in Main has been deprecated and will be removed in a future major version. - */ -export enum SidePanelOpenDirection { - Left, - Right, -} - -/** - * @deprecated ⚠️ `SidePanelBackgroundColor` in Main has been deprecated and will be removed in a future major version. - */ -export enum SidePanelBackgroundColor { - White, - Transparent, - Gray, -} - -const closedWidth = space.xxl; - -const SidePanelContainer = styled('div')< - Pick ->( - { +export const panelStencil = createStencil({ + vars: { + expandedWidth: '', + collapsedWidth: '', + }, + base: () => ({ overflow: 'hidden', + position: 'relative', height: '100%', - boxSizing: 'border-box', - display: 'flex', - flexDirection: 'column', - transition: 'width 200ms ease', - position: 'absolute', - }, - ({open}) => ({ - alignItems: open ? undefined : 'center', - boxShadow: open ? undefined : '0 8px 16px -8px rgba(0, 0, 0, 0.16)', - }), - ({open, backgroundColor}) => { - let openBackgroundColor; - - switch (backgroundColor) { - case SidePanelBackgroundColor.Transparent: - openBackgroundColor = 'transparent'; - break; - case SidePanelBackgroundColor.Gray: - openBackgroundColor = colors.soap100; - break; - case SidePanelBackgroundColor.White: - default: - openBackgroundColor = colors.frenchVanilla100; - break; - } - - return { - backgroundColor: open ? openBackgroundColor : colors.frenchVanilla100, - }; - }, - ({open, openWidth}) => ({ - width: open ? openWidth : closedWidth, + outline: `${px2rem(1)} solid transparent`, + transition: 'width ease-out 200ms, max-width ease-out 200ms', }), - ({open, padding}) => ({ - padding: open ? padding || space.m : `${space.s} 0`, - }), - ({openDirection}) => ({ - right: openDirection === SidePanelOpenDirection.Right ? space.zero : undefined, - left: openDirection === SidePanelOpenDirection.Left ? space.zero : undefined, - }) -); - -const ChildrenContainer = styled('div')>( - { - transition: 'none', - zIndex: 1, // show above SidePanelFooter when screen is small vertically - }, - ({open, openWidth}) => ({ - width: open ? openWidth : closedWidth, - }) -); - -const ToggleButton = styled(TertiaryButton, {shouldForwardProp: prop => prop !== 'openDirection'})< - TertiaryButtonProps & Pick ->( - { - position: 'absolute', - bottom: space.s, + modifiers: { + variant: { + alternate: { + backgroundColor: system.color.bg.default, + boxShadow: system.depth[5], + }, + standard: { + backgroundColor: system.color.bg.alt.softer, + }, + }, + expanded: { + expanded: ({expandedWidth}) => ({ + width: expandedWidth, + maxWidth: expandedWidth, + }), + collapsed: ({collapsedWidth}) => ({ + width: collapsedWidth, + maxWidth: collapsedWidth, + }), + expanding: ({expandedWidth}) => ({ + width: expandedWidth, + maxWidth: expandedWidth, + }), + collapsing: ({collapsedWidth}) => ({ + width: collapsedWidth, + maxWidth: collapsedWidth, + }), + }, }, - ({openDirection}) => ({ - right: openDirection === SidePanelOpenDirection.Left ? space.s : '', - left: openDirection === SidePanelOpenDirection.Right ? space.s : '', - }) -); - -const SidePanelFooter = styled('div')>( - { - position: 'absolute', - bottom: '0', - height: 120, - left: 0, - background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.0001) 0%, #FFFFFF 100%)', +}); + +export const SidePanel = createContainer('section')({ + displayName: 'SidePanel', + modelHook: useSidePanelModel, + elemPropsHook: useSidePanelContainer, + subComponents: { + /** + * `SidePanel.ToggleButton` is a control that toggles between expanded and collapsed states. + * It must be used within the `SidePanel` component as a child. For accessibility purposes, + * it should be the first focusable element in the panel. + * + * The button automatically receives `aria-controls`, `aria-expanded`, and `aria-labelledby` + * attributes from the model. + */ + ToggleButton: SidePanelToggleButton, + /** + * `SidePanel.Heading` is a styled heading that provides the accessible name for the SidePanel. + * The heading's `id` is automatically linked to the panel's `aria-labelledby` attribute. + * By default, the heading is hidden when the panel is collapsed. + */ + Heading: SidePanelHeading, }, - ({open, openWidth}) => ({ - width: open ? openWidth : space.xxl, - }) -); - -/** - * @deprecated ⚠️ `SidePanel` in Main has been deprecated and will be removed in a future major version. Please use [`SidePanel` in Preview](https://workday.github.io/canvas-kit/?path=/docs/preview-side-panel--docs) instead. - */ -export class SidePanel extends React.Component { - static OpenDirection = SidePanelOpenDirection; - static BackgroundColor = SidePanelBackgroundColor; - - constructor(props: SidePanelProps) { - super(props); - this.handleResize = this.handleResize.bind(this); - } - - state = { - screenSize: typeof window !== 'undefined' ? window.innerWidth : 0, - }; - - public componentDidMount() { - window.addEventListener('resize', this.handleResize); - } - public componentWillUnmount() { - window.removeEventListener('resize', this.handleResize); - } - - public render() { - const { - backgroundColor = SidePanelBackgroundColor.White, - openNavigationAriaLabel = 'open navigation', - closeNavigationAriaLabel = 'close navigation', - openDirection = SidePanelOpenDirection.Left, - breakpoint = 768, - openWidth = 300, - header, - onToggleClick, - open, - padding, - onBreakpointChange, +})( + ( + { + collapsedWidth = 64, + expandedWidth = 320, + variant = 'standard', + children, ...elemProps - } = this.props; - + }: SidePanelProps, + Element, + model + ) => { return ( - - - {header && open ? ( - - {header} - - ) : null} - {this.props.children} - - - {onToggleClick && ( - - )} - - + {children} + ); } - - private handleResize = () => { - if (!this.props.onBreakpointChange || !this.props.breakpoint) { - return; - } - - if (window.innerWidth > this.props.breakpoint && !this.props.open) { - this.props.onBreakpointChange(true); - } - if (window.innerWidth <= this.props.breakpoint && this.props.open) { - this.props.onBreakpointChange(false); - } - }; - - private onToggleClick = () => { - if (this.props.onToggleClick) { - this.props.onToggleClick(); - } - }; - - private toggleButtonDirection = ( - open: boolean, - openDirection: SidePanelOpenDirection - ): CanvasSystemIcon => { - if (openDirection !== SidePanelOpenDirection.Right) { - return open ? chevronLeftIcon : chevronRightIcon; - } else { - return open ? chevronRightIcon : chevronLeftIcon; - } - }; -} +); diff --git a/modules/react/side-panel/lib/SidePanelHeading.tsx b/modules/react/side-panel/lib/SidePanelHeading.tsx new file mode 100644 index 0000000000..e7912d5325 --- /dev/null +++ b/modules/react/side-panel/lib/SidePanelHeading.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; + +import { + createElemPropsHook, + createSubcomponent, + ExtractProps, +} from '@workday/canvas-kit-react/common'; +import {Heading} from '@workday/canvas-kit-react/text'; +import {createStencil, handleCsProp} from '@workday/canvas-kit-styling'; +import {system} from '@workday/canvas-tokens-web'; +import {useSidePanelModel} from './useSidePanelModel'; + +export interface SidePanelHeadingProps extends ExtractProps { + children?: React.ReactNode; +} + +export const sidePanelHeadingStencil = createStencil({ + base: { + padding: system.space.x4, + }, +}); + +/** + * Adds the necessary props to the SidePanelHeading subcomponent. + * This sets the `id` to the `labelId` from the model for accessibility purposes, + * and hides the heading when the panel is not expanded. + */ +export const useSidePanelHeading = createElemPropsHook(useSidePanelModel)(({state}) => { + return { + id: state.labelId, + hidden: state.transitionState !== 'expanded', + }; +}); + +/** + * `SidePanel.Heading` is a styled heading component that provides the accessible name + * for the SidePanel. The heading's `id` is automatically set to the model's `labelId`, + * which is used by the panel's `aria-labelledby` attribute. + * + * The heading is automatically hidden when the panel is collapsed. + */ +export const SidePanelHeading = createSubcomponent(Heading)({ + displayName: 'SidePanel.Heading', + modelHook: useSidePanelModel, + elemPropsHook: useSidePanelHeading, +})(({size = 'small', children, ...elemProps}, Element) => { + return ( + + {children} + + ); +}); diff --git a/modules/labs-react/side-panel/lib/SidePanelToggleButton.tsx b/modules/react/side-panel/lib/SidePanelToggleButton.tsx similarity index 90% rename from modules/labs-react/side-panel/lib/SidePanelToggleButton.tsx rename to modules/react/side-panel/lib/SidePanelToggleButton.tsx index 9dec00fd14..69299c4790 100644 --- a/modules/labs-react/side-panel/lib/SidePanelToggleButton.tsx +++ b/modules/react/side-panel/lib/SidePanelToggleButton.tsx @@ -120,27 +120,25 @@ export const sidePanelToggleButtonStencil = createStencil({ ], }); -export const useSidePanelToggleButtonElemProps = createElemPropsHook(useSidePanelModel)( - ({state}) => { - return { - 'aria-controls': state.panelId, - 'aria-expanded': state.transitionState === 'expanded', - 'aria-labelledby': state.labelId, - }; - } -); +export const useSidePanelToggleButton = createElemPropsHook(useSidePanelModel)(({state}) => { + return { + 'aria-controls': state.panelId, + 'aria-pressed': state.transitionState === 'expanded', + 'aria-describedby': state.labelId, + }; +}); export const SidePanelToggleButton = createSubcomponent('button')({ displayName: 'SidePanel.ToggleButton', modelHook: useSidePanelModel, - elemPropsHook: useSidePanelToggleButtonElemProps, + elemPropsHook: useSidePanelToggleButton, })( ( { variant = undefined, icon = transformationImportIcon, - tooltipTextExpand = 'Expand', - tooltipTextCollapse = 'Collapse', + tooltipTextExpand, + tooltipTextCollapse, tooltipProps, ...elemProps }: SidePanelToggleButtonProps, diff --git a/modules/labs-react/side-panel/lib/useSidePanelModel.ts b/modules/react/side-panel/lib/useSidePanelModel.ts similarity index 100% rename from modules/labs-react/side-panel/lib/useSidePanelModel.ts rename to modules/react/side-panel/lib/useSidePanelModel.ts diff --git a/modules/react/side-panel/spec/SSR.spec.tsx b/modules/react/side-panel/spec/SSR.spec.tsx index 86b54a86f0..b2a70bbc47 100644 --- a/modules/react/side-panel/spec/SSR.spec.tsx +++ b/modules/react/side-panel/spec/SSR.spec.tsx @@ -3,11 +3,11 @@ */ import React from 'react'; import {renderToString} from 'react-dom/server'; -import {SidePanel} from '../'; +import {SidePanel} from '@workday/canvas-kit-react/side-panel'; -describe('Modal', () => { +describe('SidePanel', () => { it('should render on a server without crashing', () => { - const ssrRender = () => renderToString(); + const ssrRender = () => renderToString(); expect(ssrRender).not.toThrow(); }); }); diff --git a/modules/react/side-panel/spec/SidePanel.spec.tsx b/modules/react/side-panel/spec/SidePanel.spec.tsx deleted file mode 100644 index 7f01c6b0ff..0000000000 --- a/modules/react/side-panel/spec/SidePanel.spec.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import * as React from 'react'; -import {screen, render, fireEvent} from '@testing-library/react'; - -import {SidePanel} from '../lib/SidePanel'; - -window.resizeBy = (x: number, y: number) => { - // @ts-ignore - window.innerWidth = x; - // @ts-ignore - window.innerHeight = y; - window.dispatchEvent(new Event('resize')); -}; - -describe('SidePanel', () => { - const cb = jest.fn(); - beforeEach(() => { - window.resizeBy(1280, 1024); - }); - afterEach(() => { - cb.mockReset(); - }); - - it('should call "onToggleClick" when toggle button is clicked', () => { - render( - - Hello World - - ); - - fireEvent.click(screen.getByRole('button')); - expect(cb).toHaveBeenCalledTimes(1); - }); - - it('should call "onBreakpointChange" when below the breakpoint and side panel is open', () => { - const mockFunction = jest.fn(); - render( - - Hello World - - ); - window.resizeBy(10, 10); - expect(mockFunction).toHaveBeenCalledTimes(1); - }); - - it('should call onBreakpointChange when above the breakpoint and side panel is closed', () => { - const mockFunction = jest.fn(); - render( - - Hello World - - ); - window.resizeBy(1000, 10); - expect(mockFunction).toHaveBeenCalledTimes(1); - }); - - it('should spread extra props to containing element', () => { - const {container} = render(); - expect(container.firstChild).toHaveAttribute('data-propspread', 'test'); - }); -}); diff --git a/modules/labs-react/side-panel/stories/SidePanel.mdx b/modules/react/side-panel/stories/SidePanel.mdx similarity index 51% rename from modules/labs-react/side-panel/stories/SidePanel.mdx rename to modules/react/side-panel/stories/SidePanel.mdx index 4fcb1d5f1d..d5b294f528 100644 --- a/modules/labs-react/side-panel/stories/SidePanel.mdx +++ b/modules/react/side-panel/stories/SidePanel.mdx @@ -1,8 +1,13 @@ -import {ExampleCodeBlock, SymbolDoc, Specifications, StorybookStatusIndicator} from '@workday/canvas-kit-docs'; +import { + ExampleCodeBlock, + SymbolDoc, + Specifications, + StorybookStatusIndicator, +} from '@workday/canvas-kit-docs'; import * as SidePanelStories from './SidePanel.stories'; import {Basic} from './examples/Basic'; -import {HiddenName} from './examples/HiddenName'; +import {Heading} from './examples/Heading'; import {AlternatePanel} from './examples/Variant'; import {ExternalControl} from './examples/ExternalControl'; import {RightOrigin} from './examples/RightOrigin'; @@ -21,12 +26,13 @@ the model pattern for state management and is fully accessible. ## Installation ```sh -yarn add @workday/canvas-kit-labs-react +yarn add @workday/canvas-kit-react ``` ## Migrating from Preview -If you're migrating from `@workday/canvas-kit-preview-react/side-panel`, here are the key API changes: +If you're migrating from `@workday/canvas-kit-preview-react/side-panel`, here are the key API +changes: ### Import Changes @@ -34,33 +40,33 @@ If you're migrating from `@workday/canvas-kit-preview-react/side-panel`, here ar // 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'; +// After (react) +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-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 | +| Preview (`useSidePanel`) | Main (`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 | -|---------|------| -| `` | `` or just `` | -| `` | `` | -| `` | `` | -| `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 | +| Preview | Main | +| ---------------------------------------------- | ------------------------------------------------- | +| `` | `` or just `` | +| `` | `` | +| `` | `` | +| `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 @@ -70,17 +76,17 @@ const {expanded, panelProps, labelProps, controlProps} = useSidePanel({ initialExpanded: false, }); - console.log(exp)}> + console.log(exp)}> Panel Title {expanded && } - +; -// After (labs-react) +// After (react) const model = useSidePanelModel({ initialTransitionState: 'collapsed', origin: 'end', - onStateTransition: (state) => { + onStateTransition: state => { const isExpanded = state === 'expanded' || state === 'expanding'; console.log(isExpanded); }, @@ -88,23 +94,27 @@ const model = useSidePanelModel({ - Panel Title + Panel Title {model.state.transitionState === 'expanded' && } - +; ``` ### 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'; +if (expanded) { + /* ... */ +} + +// After (react) - for exact state +if (model.state.transitionState === 'expanded') { + /* ... */ +} + +// After (react) - including animation states +const isExpanded = + model.state.transitionState === 'expanded' || model.state.transitionState === 'expanding'; ``` ## Usage @@ -114,14 +124,9 @@ const isExpanded = model.state.transitionState === 'expanded' || `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 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 -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 @@ -132,13 +137,12 @@ logic or styling for bidirectional support. ### Hidden Name -`SidePanel` must always have an accessible label for both the HTML `
` 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. +`SidePanel`'s `
` 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. - + ### Alternate Variant @@ -151,8 +155,8 @@ used (this case is covered in the Examples section). ### 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. +component. You can use the model's events (`model.events.expand()` and `model.events.collapse()`) to +programmatically control the panel. #### Notes about accessibility @@ -171,9 +175,9 @@ state and call `model.events.expand()` or `model.events.collapse()` on click. ### 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. +`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. @@ -186,29 +190,56 @@ If you do not need `SidePanel`'s expand / collapse behavior, you can simply omit ### 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. +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. +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 +### Accessibility + +`SidePanel` renders a `
` 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 - + ## Hooks @@ -218,42 +249,42 @@ The `useSidePanelModel` hook creates a model for managing the SidePanel's state 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'; +import {useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; // Create a model with custom configuration const model = useSidePanelModel({ initialTransitionState: 'collapsed', origin: 'end', - onStateTransition: (state) => console.log('State:', state), + 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 +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.expand(); // Set to expanded (no animation) +model.events.collapse(); // Set to collapsed (no animation) model.events.handleAnimationStart(); // Start expand/collapse animation ``` - + ### useSidePanelContainer The `useSidePanelContainer` elemProps hook provides the necessary props for the SidePanel container element, including `id`, `aria-labelledby`, and `onTransitionEnd`. - + -### useSidePanelToggleButtonElemProps +### useSidePanelToggleButton -The `useSidePanelToggleButtonElemProps` elemProps hook provides ARIA attributes for the toggle -button, including `aria-controls`, `aria-expanded`, and `aria-labelledby`. +The `useSidePanelToggleButton` elemProps hook provides ARIA attributes for the toggle +button, including `aria-controls`, `aria-pressed`, and `aria-describedby`. - + ## Specifications - + diff --git a/modules/labs-react/side-panel/stories/SidePanel.stories.ts b/modules/react/side-panel/stories/SidePanel.stories.ts similarity index 91% rename from modules/labs-react/side-panel/stories/SidePanel.stories.ts rename to modules/react/side-panel/stories/SidePanel.stories.ts index 0889b21885..9bf8f61548 100644 --- a/modules/labs-react/side-panel/stories/SidePanel.stories.ts +++ b/modules/react/side-panel/stories/SidePanel.stories.ts @@ -1,7 +1,7 @@ import {Meta, StoryObj} from '@storybook/react'; import mdxDoc from './SidePanel.mdx'; -import {SidePanel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel} from '@workday/canvas-kit-react/side-panel'; // Examples import {Basic as BasicExample} from './examples/Basic'; import {HiddenName as HiddenNameExample} from './examples/HiddenName'; @@ -12,7 +12,7 @@ import {AlwaysOpen as AlwaysOpenExample} from './examples/AlwaysOpen'; import {OnStateTransition as OnStateTransitionExample} from './examples/OnStateTransition'; export default { - title: 'Labs/Side Panel (New)', + title: 'Components/Containers/Side Panel (New)', component: SidePanel, tags: ['autodocs'], parameters: { diff --git a/modules/labs-react/side-panel/stories/examples/AlwaysOpen.tsx b/modules/react/side-panel/stories/examples/AlwaysOpen.tsx similarity index 85% rename from modules/labs-react/side-panel/stories/examples/AlwaysOpen.tsx rename to modules/react/side-panel/stories/examples/AlwaysOpen.tsx index 70deb1c292..20900c6c52 100644 --- a/modules/labs-react/side-panel/stories/examples/AlwaysOpen.tsx +++ b/modules/react/side-panel/stories/examples/AlwaysOpen.tsx @@ -1,8 +1,8 @@ import {AccentIcon} from '@workday/canvas-kit-react/icon'; import {rocketIcon} from '@workday/canvas-accent-icons-web'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {Flex} from '@workday/canvas-kit-react/layout'; -import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {Text} from '@workday/canvas-kit-react/text'; import {system} from '@workday/canvas-tokens-web'; import {createStyles, px2rem} from '@workday/canvas-kit-styling'; @@ -37,9 +37,7 @@ export const AlwaysOpen = () => { - - Tasks Panel - + Tasks Panel diff --git a/modules/labs-react/side-panel/stories/examples/Basic.tsx b/modules/react/side-panel/stories/examples/Basic.tsx similarity index 79% rename from modules/labs-react/side-panel/stories/examples/Basic.tsx rename to modules/react/side-panel/stories/examples/Basic.tsx index 8241f3c862..a07a5ce969 100644 --- a/modules/labs-react/side-panel/stories/examples/Basic.tsx +++ b/modules/react/side-panel/stories/examples/Basic.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import {SecondaryButton} from '@workday/canvas-kit-react/button'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {Flex} from '@workday/canvas-kit-react/layout'; -import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {Text} from '@workday/canvas-kit-react/text'; import {AccentIcon} from '@workday/canvas-kit-react/icon'; import {rocketIcon} from '@workday/canvas-accent-icons-web'; import {createStyles, px2rem} from '@workday/canvas-kit-styling'; @@ -34,20 +34,17 @@ export const Basic = () => { return ( - + {model.state.transitionState === 'expanded' && ( )} - + Tasks Panel diff --git a/modules/labs-react/side-panel/stories/examples/ExternalControl.tsx b/modules/react/side-panel/stories/examples/ExternalControl.tsx similarity index 85% rename from modules/labs-react/side-panel/stories/examples/ExternalControl.tsx rename to modules/react/side-panel/stories/examples/ExternalControl.tsx index 5e0d226249..07b9521437 100644 --- a/modules/labs-react/side-panel/stories/examples/ExternalControl.tsx +++ b/modules/react/side-panel/stories/examples/ExternalControl.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {Flex} from '@workday/canvas-kit-react/layout'; -import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {Text} from '@workday/canvas-kit-react/text'; import {SecondaryButton} from '@workday/canvas-kit-react/button'; import {createStyles, px2rem} from '@workday/canvas-kit-styling'; import {system} from '@workday/canvas-tokens-web'; @@ -48,14 +48,13 @@ export const ExternalControl = () => { return ( - - {model.state.transitionState === 'expanded' && ( - - - Tasks Panel - - - )} + + + Tasks Panel + diff --git a/modules/labs-react/side-panel/stories/examples/HiddenName.tsx b/modules/react/side-panel/stories/examples/Heading.tsx similarity index 82% rename from modules/labs-react/side-panel/stories/examples/HiddenName.tsx rename to modules/react/side-panel/stories/examples/Heading.tsx index 8d6a7343c1..4ef8b924d1 100644 --- a/modules/labs-react/side-panel/stories/examples/HiddenName.tsx +++ b/modules/react/side-panel/stories/examples/Heading.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-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'; @@ -28,8 +28,11 @@ export const HiddenName = () => { return ( - - Hidden Title + + diff --git a/modules/labs-react/side-panel/stories/examples/OnStateTransition.tsx b/modules/react/side-panel/stories/examples/OnStateTransition.tsx similarity index 87% rename from modules/labs-react/side-panel/stories/examples/OnStateTransition.tsx rename to modules/react/side-panel/stories/examples/OnStateTransition.tsx index b5d7b3cd80..70002203df 100644 --- a/modules/labs-react/side-panel/stories/examples/OnStateTransition.tsx +++ b/modules/react/side-panel/stories/examples/OnStateTransition.tsx @@ -4,7 +4,7 @@ import { SidePanel, useSidePanelModel, SidePanelTransitionStates, -} from '@workday/canvas-kit-labs-react/side-panel'; +} from '@workday/canvas-kit-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'; @@ -36,7 +36,10 @@ export const OnStateTransition = () => { return ( - + Hidden Title diff --git a/modules/labs-react/side-panel/stories/examples/RightOrigin.tsx b/modules/react/side-panel/stories/examples/RightOrigin.tsx similarity index 84% rename from modules/labs-react/side-panel/stories/examples/RightOrigin.tsx rename to modules/react/side-panel/stories/examples/RightOrigin.tsx index b4882f1bef..729253888f 100644 --- a/modules/labs-react/side-panel/stories/examples/RightOrigin.tsx +++ b/modules/react/side-panel/stories/examples/RightOrigin.tsx @@ -1,7 +1,7 @@ import {SecondaryButton} from '@workday/canvas-kit-react/button'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {Flex} from '@workday/canvas-kit-react/layout'; -import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {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'; @@ -37,15 +37,12 @@ const RightPanel = () => { return ( - + - + Tasks Panel ); diff --git a/modules/labs-react/side-panel/stories/examples/Variant.tsx b/modules/react/side-panel/stories/examples/Variant.tsx similarity index 81% rename from modules/labs-react/side-panel/stories/examples/Variant.tsx rename to modules/react/side-panel/stories/examples/Variant.tsx index bf5e7c6f7e..377000b262 100644 --- a/modules/labs-react/side-panel/stories/examples/Variant.tsx +++ b/modules/react/side-panel/stories/examples/Variant.tsx @@ -1,7 +1,7 @@ import {SecondaryButton} from '@workday/canvas-kit-react/button'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {Flex} from '@workday/canvas-kit-react/layout'; -import {Heading, Text} from '@workday/canvas-kit-react/text'; +import {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'; @@ -35,15 +35,12 @@ export const AlternatePanel = () => { - + - + Alternate Panel diff --git a/modules/labs-react/side-panel/stories/examples/useDirection.ts b/modules/react/side-panel/stories/examples/useDirection.ts similarity index 100% rename from modules/labs-react/side-panel/stories/examples/useDirection.ts rename to modules/react/side-panel/stories/examples/useDirection.ts diff --git a/modules/labs-react/side-panel/stories/testingCypress.stories.tsx b/modules/react/side-panel/stories/testingCypress.stories.tsx similarity index 98% rename from modules/labs-react/side-panel/stories/testingCypress.stories.tsx rename to modules/react/side-panel/stories/testingCypress.stories.tsx index 80f07fcb17..9d449f33b8 100644 --- a/modules/labs-react/side-panel/stories/testingCypress.stories.tsx +++ b/modules/react/side-panel/stories/testingCypress.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-labs-react/side-panel'; +import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; import {PrimaryButton} from '@workday/canvas-kit-react/button'; export default { diff --git a/modules/labs-react/side-panel/stories/testingVisual.stories.tsx b/modules/react/side-panel/stories/testingVisual.stories.tsx similarity index 100% rename from modules/labs-react/side-panel/stories/testingVisual.stories.tsx rename to modules/react/side-panel/stories/testingVisual.stories.tsx diff --git a/modules/react/side-panel/stories/visual-testing/SidePanel.stories.tsx b/modules/react/side-panel/stories/visual-testing/SidePanel.stories.tsx deleted file mode 100644 index fbb7b2867d..0000000000 --- a/modules/react/side-panel/stories/visual-testing/SidePanel.stories.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import {ComponentStatesTable, StaticStates} from '@workday/canvas-kit-react/testing'; -import { - SidePanel, - SidePanelBackgroundColor, - SidePanelOpenDirection, -} from '@workday/canvas-kit-react/side-panel'; -import {space} from '@workday/canvas-kit-react/tokens'; - -export default { - title: 'Testing/Containers/Side Panel', - component: SidePanel, - parameters: { - chromatic: { - disable: false, - }, - }, -}; - -export const SidePanelStates = { - render: () => ( - - console.log('click toggle button'), - }, - }, - { - label: 'With open direction from right', - props: { - header: 'Navigation', - open: true, - backgroundColor: SidePanelBackgroundColor.Gray, - onToggleClick: () => console.log('click toggle button'), - openDirection: SidePanelOpenDirection.Right, - }, - }, - { - label: 'With custom padding', - props: { - header: 'Navigation', - open: true, - backgroundColor: SidePanelBackgroundColor.Gray, - onToggleClick: () => console.log('click toggle button'), - padding: space.xxs, - }, - }, - { - label: 'With custom open width', - props: { - header: 'Navigation', - open: true, - backgroundColor: SidePanelBackgroundColor.Gray, - onToggleClick: () => console.log('click toggle button'), - openWidth: 350, - }, - }, - { - label: 'When closed', - props: { - header: 'Navigation', - open: false, - onToggleClick: () => console.log('click toggle button'), - }, - }, - ]} - columnProps={[{label: 'Default', props: {}}]} - > - {props => ( -
- - Side Panel Content - -
- )} -
-
- ), -}; From 5622a8a938fa3c27aac16142c7e87d8393ee130b Mon Sep 17 00:00:00 2001 From: "manuel.carrera" Date: Mon, 5 Jan 2026 09:07:21 -0700 Subject: [PATCH 2/3] fix: Clean up stories and docs --- modules/codemod/lib/v5/maps.ts | 2 +- modules/react/side-panel/stories/SidePanel.mdx | 4 ++-- modules/react/side-panel/stories/SidePanel.stories.ts | 6 +++--- modules/react/side-panel/stories/examples/Heading.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/codemod/lib/v5/maps.ts b/modules/codemod/lib/v5/maps.ts index 4a7fac72dc..4ffdf65419 100644 --- a/modules/codemod/lib/v5/maps.ts +++ b/modules/codemod/lib/v5/maps.ts @@ -67,7 +67,7 @@ export const sourceMap: { '@workday/canvas-kit-labs-react-menu': '@workday/canvas-kit-labs-react/menu', '@workday/canvas-kit-labs-react-pagination': '@workday/canvas-kit-labs-react/pagination', '@workday/canvas-kit-labs-react-select': '@workday/canvas-kit-labs-react/select', - '@workday/canvas-kit-labs-react-side-panel': '@workday/canvas-kit-react/side-panel', + '@workday/canvas-kit-labs-react-side-panel': '@workday/canvas-kit-labs-react/side-panel', '@workday/canvas-kit-labs-react-tabs': '@workday/canvas-kit-labs-react/tabs', }; diff --git a/modules/react/side-panel/stories/SidePanel.mdx b/modules/react/side-panel/stories/SidePanel.mdx index d5b294f528..c50b7e2d2b 100644 --- a/modules/react/side-panel/stories/SidePanel.mdx +++ b/modules/react/side-panel/stories/SidePanel.mdx @@ -62,7 +62,7 @@ import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel | ---------------------------------------------- | ------------------------------------------------- | | `` | `` or just `` | | `` | `` | -| `` | `` | +| `` | `Panel Title` | | `expanded` prop on SidePanel | Managed by model's `transitionState` | | `touched` prop on SidePanel | Managed internally | | `onExpandedChange` callback | Use `onStateTransition` and derive expanded state | @@ -94,7 +94,7 @@ const model = useSidePanelModel({ - Panel Title + Panel Title {model.state.transitionState === 'expanded' && } ; ``` diff --git a/modules/react/side-panel/stories/SidePanel.stories.ts b/modules/react/side-panel/stories/SidePanel.stories.ts index 9bf8f61548..8b3b914c31 100644 --- a/modules/react/side-panel/stories/SidePanel.stories.ts +++ b/modules/react/side-panel/stories/SidePanel.stories.ts @@ -4,7 +4,7 @@ import mdxDoc from './SidePanel.mdx'; import {SidePanel} from '@workday/canvas-kit-react/side-panel'; // Examples import {Basic as BasicExample} from './examples/Basic'; -import {HiddenName as HiddenNameExample} from './examples/HiddenName'; +import {Heading as HeadingExample} from './examples/Heading'; import {AlternatePanel as AlternatePanelExample} from './examples/Variant'; import {ExternalControl as ExternalControlExample} from './examples/ExternalControl'; import {RightOrigin as RightOriginExample} from './examples/RightOrigin'; @@ -27,8 +27,8 @@ type Story = StoryObj; export const Basic: Story = { render: BasicExample, }; -export const HiddenName: Story = { - render: HiddenNameExample, +export const Heading: Story = { + render: HeadingExample, }; export const AlternatePanel: Story = { render: AlternatePanelExample, diff --git a/modules/react/side-panel/stories/examples/Heading.tsx b/modules/react/side-panel/stories/examples/Heading.tsx index 4ef8b924d1..3d1dfb0399 100644 --- a/modules/react/side-panel/stories/examples/Heading.tsx +++ b/modules/react/side-panel/stories/examples/Heading.tsx @@ -18,7 +18,7 @@ const stylesOverride = { }), }; -export const HiddenName = () => { +export const Heading = () => { const model = useSidePanelModel({ onStateTransition: state => { console.log(`state is: ${state}`); From 677f16d3cbfbb409f14eb76ceb9add8d9673ff58 Mon Sep 17 00:00:00 2001 From: "manuel.carrera" Date: Mon, 5 Jan 2026 14:43:38 -0700 Subject: [PATCH 3/3] fix: Add tooltip prop --- .../react/side-panel/lib/SidePanelHeading.tsx | 11 ++- .../side-panel/lib/SidePanelToggleButton.tsx | 12 ++- .../side-panel/stories/examples/Basic.tsx | 2 +- .../stories/examples/ExternalControl.tsx | 2 +- .../side-panel/stories/examples/Heading.tsx | 1 + .../stories/examples/OnStateTransition.tsx | 2 +- .../stories/examples/RightOrigin.tsx | 2 +- .../side-panel/stories/examples/Variant.tsx | 1 + .../stories/testingCypress.stories.tsx | 96 ------------------- 9 files changed, 24 insertions(+), 105 deletions(-) delete mode 100644 modules/react/side-panel/stories/testingCypress.stories.tsx diff --git a/modules/react/side-panel/lib/SidePanelHeading.tsx b/modules/react/side-panel/lib/SidePanelHeading.tsx index e7912d5325..a224b683ed 100644 --- a/modules/react/side-panel/lib/SidePanelHeading.tsx +++ b/modules/react/side-panel/lib/SidePanelHeading.tsx @@ -5,12 +5,17 @@ import { createSubcomponent, ExtractProps, } from '@workday/canvas-kit-react/common'; -import {Heading} from '@workday/canvas-kit-react/text'; +import {Heading, TypeLevelProps} from '@workday/canvas-kit-react/text'; import {createStencil, handleCsProp} from '@workday/canvas-kit-styling'; import {system} from '@workday/canvas-tokens-web'; import {useSidePanelModel} from './useSidePanelModel'; -export interface SidePanelHeadingProps extends ExtractProps { +export interface SidePanelHeadingProps extends Omit, 'size'> { + /** + * The size of the heading. + * @default 'small' + */ + size?: TypeLevelProps['size']; children?: React.ReactNode; } @@ -45,7 +50,7 @@ export const SidePanelHeading = createSubcomponent(Heading)({ elemPropsHook: useSidePanelHeading, })(({size = 'small', children, ...elemProps}, Element) => { return ( - + {children} ); diff --git a/modules/react/side-panel/lib/SidePanelToggleButton.tsx b/modules/react/side-panel/lib/SidePanelToggleButton.tsx index 69299c4790..43a2e2c13a 100644 --- a/modules/react/side-panel/lib/SidePanelToggleButton.tsx +++ b/modules/react/side-panel/lib/SidePanelToggleButton.tsx @@ -14,10 +14,15 @@ import {system} from '@workday/canvas-tokens-web'; export interface SidePanelToggleButtonProps extends ExtractProps { /** * The tooltip text to expand the side panel + * @deprecated Use */ tooltipTextExpand?: string; /** - * The tooltip text to collapse the side panel + * Provides an accessible label to the button. This text **should not** convey visual state but rather what it does like "control data panel." + */ + tooltipText?: string; + /** + * The tooltip text to collapse the side panel. Optional text for when the side panel is in a collapsed state. */ tooltipTextCollapse?: string; tooltipProps?: Omit; @@ -140,6 +145,7 @@ export const SidePanelToggleButton = createSubcomponent('button')({ tooltipTextExpand, tooltipTextCollapse, tooltipProps, + tooltipText, ...elemProps }: SidePanelToggleButtonProps, Element, @@ -150,13 +156,15 @@ export const SidePanelToggleButton = createSubcomponent('button')({ type="muted" {...tooltipProps} title={ - model.state.transitionState === 'collapsed' ? tooltipTextExpand : tooltipTextCollapse + tooltipText || + (model.state.transitionState === 'collapsed' ? tooltipTextCollapse : tooltipTextExpand) } > { {model.state.transitionState === 'expanded' && ( diff --git a/modules/react/side-panel/stories/examples/ExternalControl.tsx b/modules/react/side-panel/stories/examples/ExternalControl.tsx index 07b9521437..1910c6f6d1 100644 --- a/modules/react/side-panel/stories/examples/ExternalControl.tsx +++ b/modules/react/side-panel/stories/examples/ExternalControl.tsx @@ -50,7 +50,7 @@ export const ExternalControl = () => { Tasks Panel diff --git a/modules/react/side-panel/stories/examples/Heading.tsx b/modules/react/side-panel/stories/examples/Heading.tsx index 3d1dfb0399..c3e52b6192 100644 --- a/modules/react/side-panel/stories/examples/Heading.tsx +++ b/modules/react/side-panel/stories/examples/Heading.tsx @@ -31,6 +31,7 @@ export const Heading = () => { diff --git a/modules/react/side-panel/stories/examples/OnStateTransition.tsx b/modules/react/side-panel/stories/examples/OnStateTransition.tsx index 70002203df..c8634d2529 100644 --- a/modules/react/side-panel/stories/examples/OnStateTransition.tsx +++ b/modules/react/side-panel/stories/examples/OnStateTransition.tsx @@ -38,7 +38,7 @@ export const OnStateTransition = () => { Hidden Title diff --git a/modules/react/side-panel/stories/examples/RightOrigin.tsx b/modules/react/side-panel/stories/examples/RightOrigin.tsx index 729253888f..b4fddc6ea6 100644 --- a/modules/react/side-panel/stories/examples/RightOrigin.tsx +++ b/modules/react/side-panel/stories/examples/RightOrigin.tsx @@ -39,7 +39,7 @@ const RightPanel = () => { Tasks Panel diff --git a/modules/react/side-panel/stories/examples/Variant.tsx b/modules/react/side-panel/stories/examples/Variant.tsx index 377000b262..8d935d1b3f 100644 --- a/modules/react/side-panel/stories/examples/Variant.tsx +++ b/modules/react/side-panel/stories/examples/Variant.tsx @@ -38,6 +38,7 @@ export const AlternatePanel = () => { Alternate Panel diff --git a/modules/react/side-panel/stories/testingCypress.stories.tsx b/modules/react/side-panel/stories/testingCypress.stories.tsx deleted file mode 100644 index 9d449f33b8..0000000000 --- a/modules/react/side-panel/stories/testingCypress.stories.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import * as React from 'react'; -import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; -import {PrimaryButton} from '@workday/canvas-kit-react/button'; - -export default { - title: 'Testing/Labs/Side Panel/Cypress', - component: SidePanel, -}; - -const Container = props => { - return
; -}; - -const label = `Accessible Label Name`; - -export const Default = () => { - const model = useSidePanelModel(); - return ( - - - - - - - ); -}; -export const AsDiv = () => { - const model = useSidePanelModel(); - return ( - - - - - - - ); -}; -export const AsAside = () => { - const model = useSidePanelModel(); - return ( - - - - - -
Main Content
-
- ); -}; - -export const FirstFocusable = () => { - const model = useSidePanelModel(); - const Header = props => { - return ( -
- ); - }; - - return ( - -
- Open -
- - - -

Panel Name

- - Another Button -
-
-
- ); -};