From 790029808493aae9db7537f8569c1fb776070da2 Mon Sep 17 00:00:00 2001 From: hipuku Date: Fri, 16 Oct 2026 13:04:33 +1100 Subject: [PATCH 1/3] docs(common): move the common README into Storybook The common package README held reference documentation that was not reachable from Storybook, where the rest of the documentation lives. Adds two pages under Hooks and Utilities: - Unique IDs: useUniqueId, generateUniqueId, setUniqueSeed and resetUniqueIdCount, which were only described in passing on the Accessibility and Testing guides. - Bidirectionality: isElementRTL and the logical-properties guidance, which had no Storybook page at all. Uses the existing RTL example, which nothing referenced. Adds ExtractProps to the existing Compound Component Utilities page, beside createComponent, which was already documented there. The README keeps Installation, CanvasProvider and Focus rings, and points at Storybook for everything that moved. Theming already had its own guide and is not duplicated. Fixes the heading for isElementRTL, which the README called useElementRTL. The body and the example both used isElementRTL, and the Bidirectionality section linked to an #iselementrtl anchor the wrong heading never produced. useIsRTL was deprecated in #3477. Refs #2496 --- modules/react/common/README.md | 254 ++---------------- .../common/stories/mdx/Bidirectionality.mdx | 66 +++++ .../mdx/CompoundComponentUtilities.mdx | 62 +++++ .../react/common/stories/mdx/UniqueIds.mdx | 97 +++++++ 4 files changed, 243 insertions(+), 236 deletions(-) create mode 100644 modules/react/common/stories/mdx/Bidirectionality.mdx create mode 100644 modules/react/common/stories/mdx/UniqueIds.mdx diff --git a/modules/react/common/README.md b/modules/react/common/README.md index 9b6851ebdb..abf39893a3 100644 --- a/modules/react/common/README.md +++ b/modules/react/common/README.md @@ -88,253 +88,35 @@ MyStory.decorators = [CanvasProviderDecorator]; Use [:focus-visible](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible), which will match when the user triggers focus via -## RTL +## RTL and bidirectionality Use -[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) -to support RTL in styling. +[CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) +for styling, and `isElementRTL` for keyboard handlers that need the content direction of an element. + +See +[Hooks and Utilities / Bidirectionality](https://workday.github.io/canvas-kit/?path=/docs/hooks-and-utilities-bidirectionality--docs) +in Storybook. --- ## Theming -Theming documentation has its own README. You can find it [here](./lib/theming/README.md). +Theming documentation has its own README. You can find it [here](./lib/theming/README.md), and the +guide is in +[Guides / Theming](https://workday.github.io/canvas-kit/?path=/docs/guides-theming--docs). --- -## Bidirectionality - -We strongly encourage the use of -[CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) -for styling. For keyboard events, the right arrow should still move a cursor to the right of the -cursor's current position. For left-to-right languages, this means "move to the next item" while in -right-to-left languages, this means "move to the previous item". The directionality of an element -can be retrieved from the [isElementRTL](#iselementrtl) function. This should be done inside an -event handler. - -## `useElementRTL` - -Returns `true` if the element has a content direction of `rtl`. This is most useful in event -handlers like an `onKeyDown`. - -```ts -onKeyDown(event: React.KeyboardEvent) { - const isRTL = isElementRTL(event.currentTarget); - - if (event.key === 'ArrowRight') { - if (isRTL) { - // Right arrow moves to the right even on RTL languages, but "right" doesn't mean - //"advance" in RTL. Previous is to the right of the current in RTL. - model.events.goToPreviousRow(); - } else { - model.events.goToNextRow(); - } - } -} -``` - -## Component Functions - -### `createComponent` - -`createComponent` is a convenience function that makes it easier to create components that -[forward refs](https://reactjs.org/docs/forwarding-refs.html) and take an `as` prop. The `as` prop -allows you to change the default element rendered by the component. - -```tsx -interface MyComponentProps { - prop1: string; - prop2: number; - children: React.ReactNode; -} - -export const MyComponent = createComponent('div')({ - displayName: 'MyComponent', // the component's name in the React dev tools - Component: (({ prop1, prop2, children, ...elemProps }: MyComponentProps), ref, as) => { - return {children} - }) -}) -``` - -Using a component created via `createComponent`: - -```tsx - - Content - -``` - -`MyComponent` is returning a type that looks like `ElementComponent<'div', MyComponentProps>`. The -`'div'` tells you what the default element is of the element component. - -Notice that `MyComponentProps` does not extend `React.HTMLAttributes`. The `as` prop -changes the interface of the component to match that of the `aside` element which is a -`React.HTMLAttributes`. For this reason, the interface cannot be known statically ahead -of time. Components using `createComponent` are starting to not export the prop interface. For this -reason, [ExtractProps](#extractprops) was created. - -The first parameter of `createComponent` might not be defined. If this is the case, the type -returned is a `Component` instead of `ElementComponent`. `Component`s do not have any HTML -attributes associated with them. This usually means the component doesn't return a real element. -This is used for compound components that do not require an element for accessibility reasons and -only provide a common model for the subcomponents. - -### `ExtractProps` - -v5 introduces components that could take an `as` prop, which means the underlying element that is -rendered can change. Component props that use the `as` prop no longer extend an HTML attribute -interface because the interface is determined by the value of the `as` prop. +## Component functions -`ExtractProps` can be used in place of importing a prop interface directly. If you wish to extend a -Canvas Kit component by extending it's props, use `ExtractProps`. - -```tsx -interface MyNewComponentProps extends ExtractProps { - foo: string; -} - -const MyNewComponent = ({foo, ...props}: MyNewComponentProps) => { - return My Content; -}; -``` - -`ExtractProps` will return an type that includes the props of the component passed to it as well as -any HTML attributes associated with the component. `ExtractProps` takes an optional second argument. -If the second argument is omitted, like in the example above, the HTML attributes will be extracted -from the first argument of `createComponent`, which is the default element. In the case of -`MyComponent`, that would be `'div'`. You can tell by hovering over the `MyComponent` which will -show the type as `ElementComponent<'div', MyComponentProps>`. - -The second argument should be used if you plan to change the base element of the component using the -`as` prop. This is required because Typescript has no way of knowing what the props will be outside -an `as` declaration. Here's an example of the `MyNewComponent` that renders `MyComponent` as an -`aside`. - -```tsx -interface MyNewComponentProps extends ExtractProps { - foo: string; -} - -const MyNewComponent = ({foo, ...props}: MyNewComponentProps) => { - return ( - - My Content - - ); -}; -``` - -Note the `aside` used in both the props interface as well as the `as` in the JSX. This is useful if -your component uses props that come from the HTML attribute interface of a component, like `onClick` -that isn't explicitly declared in the original component prop interface like `MyComponentProps`. In -this example, `onClick` doesn't exist on `MyComponent`, but since `MyComponent` is an -`ElementComponent`, the `onClick` will be forwarded to the underlying `div` element. - -If we inspect the `MyNewComponentProps` type, it will return the following: - -```tsx -MyComponentProps & React.HTMLAttributes; -``` +`createComponent`, `ExtractProps`, `createModelHook`, `createContainer`, `createSubcomponent` and +`createElemPropsHook` are documented in +[Hooks and Utilities / Compound Components](https://workday.github.io/canvas-kit/?path=/docs/hooks-and-utilities-compound-components--docs). -Inside Canvas Kit components, we use `ExtractProps` when we need to extend from the interfaces of -other components, but since our components are also dynamic based on the `as` props, we cannot -declare our HTML attribute interface yet. `ExtractProps` can take `never` as the second parameter -for this case, which means to only take the declared props of a component and not include the HTML -attributes. - -```tsx -interface MyNewComponentProps extends ExtractProps { - foo: string; -} - -const MyNewComponent = createComponent('aside')( - displayName: 'MyNewComponent', - Component: (({foo, ...props}: MyNewComponentProps, ref, as) => { - return ( - - My Content - - ); - }; -}); -``` - -If the component is a `Component` and not an `ElementComponent`, only the prop interface will ever -be returned since there is not HTML attribute interface associated with `Component`. - -## Common Hooks - -### useUniqueId - -A hook to generate a unique identifier for an element – most commonly used for accessibility. The -hook will generate a unique id the first render and always return the same id every render. This -uses [generateUniqueId](#generateuniqueid) internally. You should use `useUniqueId` when you need to -create a unique ID within a component's render loop. Otherwise, you should opt for -`generateUniqueId`. - -```tsx -const MyComponent = () => { - const id = useUniqueId(); - - return
Hello!
; -}; -``` - -If you wish to support user-defined ids, `useUniqueId` allows an optional id to override. This -provides a safe and easy way of handling id overrides without conditional hooks. - -```tsx -const MyComponent = ({id}) => { - const localId = useUniqueId(id); - - return
Hello!
; -}; -``` - -## Utility Functions - -### generateUniqueId - -Generates a unique and HTML5 compliant identifier every time it is called. Internally it uses a 4 -character random seed starting with a letter. This seed is unique to each instance of this package -meaning different versions of Canvas Kit on the page will have a different seed. Each call will use -a Base 36 string (10 numbers + 26 letters) based on an incremented number. The incremented number -always starts at 0 and can be reset for testing purposes using -[resetUniqueIdCount](#resetuniqueidcount). [setUniqueSeed](#setuniqueseed) can also be used for -testing or server side rendering to get the same results during hydration. - -```ts -const id1 = generateUniqueId(); // vi1e0 -const id2 = generateUniqueId(); // vi1e1 -``` - -### setUniqueSeed - -Update the seed used by the id generator. This is useful for snapshot tests to help stabilize ids -generated each run. This could also be used for server-side hydration - if you choose the same seed -for server and set that on the client before components are rendered, the ids generated will be the -same. - -For snapshot testing, this will help stabilize snapshot tests. Use in conjunction with -[resetUniqueIdCount](#resetuniqueidcount). - -```ts -// set in a script tag from the server -setUniqueSeed(window.__ID_SEED); // set in a script tag from the server - -// jest setup -before(() => { - setUniqueSeed('a'); -}); -``` - -### resetUniqueIdCount +--- -This should only be called for tests in `beforeEach` for snapshot tests. Use in conjunction with -[setUniqueSeed](#setuniqueseed). +## Unique IDs -```ts -beforeEach(() => { - resetUniqueIdCount(); -}); -``` +`useUniqueId`, `generateUniqueId`, `setUniqueSeed` and `resetUniqueIdCount` are documented in +[Hooks and Utilities / Unique IDs](https://workday.github.io/canvas-kit/?path=/docs/hooks-and-utilities-unique-ids--docs). diff --git a/modules/react/common/stories/mdx/Bidirectionality.mdx b/modules/react/common/stories/mdx/Bidirectionality.mdx new file mode 100644 index 0000000000..e19efc65a4 --- /dev/null +++ b/modules/react/common/stories/mdx/Bidirectionality.mdx @@ -0,0 +1,66 @@ +import {Meta} from '@storybook/blocks'; +import {ExampleCodeBlock} from '@workday/canvas-kit-docs'; + +import {RTL} from './examples/RTL'; + + + +# Bidirectionality + +Canvas Kit supports right-to-left (RTL) content direction. Set the direction once on +`CanvasProvider` and style with logical properties, so the same markup works in both directions. + +## Installation + +```sh +yarn add @workday/canvas-kit-react +``` + +## Styling + +Use +[CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) +rather than physical ones: `paddingInlineStart` instead of `paddingLeft`, `marginInlineEnd` instead +of `marginRight`. The browser resolves them against the content direction, so no direction-specific +stylesheet is needed. + +Where a style has to differ by direction, such as mirroring a directional icon, use the +[`:dir()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) pseudo-class rather than branching +in JavaScript. + + + +## Keyboard events + +Logical properties cover styling, but they do not cover key handling. The right arrow key should +always move the cursor to the right of its current position. In a left-to-right language that means +"move to the next item"; in a right-to-left language it means "move to the previous one". So a +keyboard handler has to know the direction of the element it is on. + +### isElementRTL + +Returns `true` when an element has a content direction of `rtl`. It reads the computed style of the +element it is given, so it is meant for event handlers rather than render functions. + +```tsx +import {isElementRTL} from '@workday/canvas-kit-react/common'; + +onKeyDown(event: React.KeyboardEvent) { + const isRTL = isElementRTL(event.currentTarget); + + if (event.key === 'ArrowRight') { + if (isRTL) { + // Right arrow still moves to the right, but "right" is not "advance" in RTL: + // the previous row is to the right of the current one. + model.events.goToPreviousRow(); + } else { + model.events.goToNextRow(); + } + } +} +``` + +
+ +> **Note:** `useIsRTL` is deprecated and will be removed in a future major version. Use +> `isElementRTL`, or the `:dir()` pseudo-class where the decision is a styling one. diff --git a/modules/react/common/stories/mdx/CompoundComponentUtilities.mdx b/modules/react/common/stories/mdx/CompoundComponentUtilities.mdx index 15cdead929..73baf5544b 100644 --- a/modules/react/common/stories/mdx/CompoundComponentUtilities.mdx +++ b/modules/react/common/stories/mdx/CompoundComponentUtilities.mdx @@ -35,6 +35,68 @@ up components with `ref` forwarding. +# ExtractProps + +Components that accept an `as` prop do not extend an HTML attribute interface directly, because the +interface depends on what `as` is set to. `ExtractProps` recovers that type, so a component can be +extended without importing its prop interface. + +```tsx +interface MyNewComponentProps extends ExtractProps { + foo: string; +} + +const MyNewComponent = ({foo, ...props}: MyNewComponentProps) => { + return My Content; +}; +``` + +With one argument, the HTML attributes are taken from the default element given to +`createComponent`. For a component typed `ElementComponent<'div', MyComponentProps>`, that is +`'div'`. + +## Changing the element + +Pass the element as a second argument when the component will be rendered as something else. +TypeScript cannot infer it from the `as` prop alone, so the element has to appear in both places. + +```tsx +interface MyNewComponentProps extends ExtractProps { + foo: string; +} + +const MyNewComponent = ({foo, ...props}: MyNewComponentProps) => { + return ( + + My Content + + ); +}; +``` + +This matters for props that come from the HTML attribute interface rather than the component's own, +such as `onClick`. `onClick` is not declared on `MyComponentProps`, but because `MyComponent` is an +`ElementComponent` it is forwarded to the underlying element. The resulting type is: + +```tsx +MyComponentProps & React.HTMLAttributes; +``` + +## Declared props only + +Pass `never` as the second argument to take only a component's declared props, with no HTML +attributes. Canvas Kit uses this internally when extending one dynamic component from another, where +the attribute interface is not yet known. + +```tsx +interface MyNewComponentProps extends ExtractProps { + foo: string; +} +``` + +For a `Component` rather than an `ElementComponent`, only the prop interface is ever returned, since +there is no HTML attribute interface associated with it. + # createModelHook When building compound components that might have some internal state or events, you typically want diff --git a/modules/react/common/stories/mdx/UniqueIds.mdx b/modules/react/common/stories/mdx/UniqueIds.mdx new file mode 100644 index 0000000000..8907f8901f --- /dev/null +++ b/modules/react/common/stories/mdx/UniqueIds.mdx @@ -0,0 +1,97 @@ +import {Meta} from '@storybook/blocks'; + + + +# Unique IDs + +Accessible markup needs IDs to connect elements to one another: a label to its field, a heading to +the region it names, a control to the element it describes. Those IDs have to be unique on the page +and stable across renders. Canvas Kit generates them for you. + +## Installation + +```sh +yarn add @workday/canvas-kit-react +``` + +## useUniqueId + +Generates a unique, HTML5-compliant ID that is stable across renders, and uses `React.useId()` where +it is available. It takes an optional ID: pass one in and it is returned unchanged, so a component +can accept an ID from a consumer and fall back to a generated one without branching. + +```tsx +import {useUniqueId} from '@workday/canvas-kit-react/common'; + +// Pairing a heading with the table it names, where `` is not wanted +const tableHeadingId = useUniqueId(); + +Pizza Toppings +{/* table rows */}
+``` + +Because it accepts an ID, it is safe to call unconditionally: + +```tsx +const MyField = ({id, ...elemProps}) => { + // uses `id` when given, generates one when not + const fieldId = useUniqueId(id); + return ; +}; +``` + +> **Note:** In React 18, `useId()` generates IDs containing colons, such as `:r0:`, which are not +> valid in CSS selectors. `useUniqueId` rewrites them with guillemets, `«r0»`, matching the format +> React is moving to. + +## generateUniqueId + +Returns a new ID string each time it is called. It is the generator behind `useUniqueId`, exported +for the cases where an ID is needed outside a render, where hook rules do not apply. + +```tsx +import {generateUniqueId} from '@workday/canvas-kit-react/common'; + +const id = generateUniqueId(); +``` + +Each ID is a four-character random seed followed by an incrementing Base 36 counter. The seed is +unique per copy of the package, so two versions of Canvas Kit on one page will not collide. + +Prefer `useUniqueId` inside components: `generateUniqueId` is not stable across renders. + +## setUniqueSeed + +Replaces the random seed with one of your choosing. Two uses: + +- **Snapshot tests**, so the IDs in a snapshot are the same on every run. +- **Server-side rendering**, so IDs generated on the server match the ones generated on the client + during hydration. Set the same seed in both places before anything renders. + +```tsx +import {setUniqueSeed} from '@workday/canvas-kit-react/common'; + +// from a script tag written by the server +setUniqueSeed(window.__ID_SEED); +``` + +## resetUniqueIdCount + +Resets the counter to zero. Intended for tests, in a `beforeEach`, alongside `setUniqueSeed`. + +```tsx +import {setUniqueSeed, resetUniqueIdCount} from '@workday/canvas-kit-react/common'; + +beforeEach(() => { + setUniqueSeed('a'); // a static seed + resetUniqueIdCount(); // reset the counter before every test +}); +``` + +See [Guides / Testing](https://workday.github.io/canvas-kit/?path=/docs/guides-testing--docs) for +how this fits into a test setup, and +[Guides / Accessibility / Overview](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-overview--docs) +for where generated IDs are needed. + +> **Note:** `uniqueId` is deprecated and will be removed in a future major version. Use +> `useUniqueId` instead. From 0016d8d3e66e1292e9f35174388a74661e37d0f8 Mon Sep 17 00:00:00 2001 From: hipuku Date: Fri, 16 Oct 2026 13:35:52 +1100 Subject: [PATCH 2/3] docs(common): Address review on the README move Rewrites the table of contents. It pointed at eight anchors this PR removed, and it was already stale before that: it listed Input Provider sections that do not exist and omitted Focus rings and RTL. It now matches the headings in the file. Corrects the ArrowRight example to goToPrevious and goToNext. The old README used goToPreviousRow and goToNextRow, but collection/lib/keyUtils binds those to ArrowUp and ArrowDown, and ArrowLeft and ArrowRight to goToPrevious and goToNext. The example was moved verbatim, so the error came with it. Qualifies the server-side rendering note on setUniqueSeed. A shared seed alone does not align IDs: they are the seed plus an incrementing counter, so both sides must also start from the same count and generate in the same order. --- modules/react/common/README.md | 35 +++++-------------- .../common/stories/mdx/Bidirectionality.mdx | 14 ++++---- .../react/common/stories/mdx/UniqueIds.mdx | 17 +++++---- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/modules/react/common/README.md b/modules/react/common/README.md index abf39893a3..7b3a114d49 100644 --- a/modules/react/common/README.md +++ b/modules/react/common/README.md @@ -14,28 +14,11 @@ Includes: - [Installation](#installation) - [CanvasProvider](#canvasprovider) - [Storybook Decorator](#storybook-decorator) - - [Input Provider](#input-provider) - - [Definitions](#definitions) - - [Usage](#usage) - - [Static Properties](#static-properties) - - [`InputTypes`](#inputtypes) - - [Component Props](#component-props) - - [Required](#required) - - [Optional](#optional) - - [`provideIntent: boolean`](#provideintent-boolean) - - [`container: HTMLElement | React.RefObject`](#container-htmlelement--reactrefobjecthtmlelement) - - [Storybook Decorator](#storybook-decorator-1) + - [Focus rings](#focus-rings) + - [RTL and bidirectionality](#rtl-and-bidirectionality) - [Theming](#theming) - - [Bidirectionality](#bidirectionality) - - [Component Functions](#component-functions) - - [`createComponent`](#createcomponent) - - [`ExtractProps`](#extractprops) - - [Common Hooks](#common-hooks) - - [useUniqueId](#useuniqueid) - - [Utility Functions](#utility-functions) - - [generateUniqueId](#generateuniqueid) - - [setUniqueSeed](#setuniqueseed) - - [resetUniqueIdCount](#resetuniqueidcount) + - [Component functions](#component-functions) + - [Unique IDs](#unique-ids) ## CanvasProvider @@ -46,8 +29,8 @@ can find more information in the [theming documentation](./lib/theming/README.md **We strongly encourage you to use this in your application to wrap all Canvas components.** ```tsx -import * as React from 'react'; -import {CanvasProvider} from '@workday/canvas-kit-react/common'; +import * as React from "react"; +import { CanvasProvider } from "@workday/canvas-kit-react/common"; {/* All your components containing any Canvas components */}; ``` @@ -60,7 +43,7 @@ stories in a `CanvasProvider` automatically. Add this decorator to your `/.storybook/preview.js` configuration file to apply to all stories: ```js -import {CanvasProviderDecorator} from '../utils/storybook'; +import { CanvasProviderDecorator } from "../utils/storybook"; export const decorators = [CanvasProviderDecorator]; ``` @@ -68,10 +51,10 @@ export const decorators = [CanvasProviderDecorator]; Or, add it to stories individually: ```js -import {CanvasProviderDecorator} from '../../../../utils/storybook'; +import { CanvasProviderDecorator } from "../../../../utils/storybook"; export default { - title: 'MyComponent', + title: "MyComponent", component: MyComponent, decorators: [CanvasProviderDecorator], }; diff --git a/modules/react/common/stories/mdx/Bidirectionality.mdx b/modules/react/common/stories/mdx/Bidirectionality.mdx index e19efc65a4..58096ceea2 100644 --- a/modules/react/common/stories/mdx/Bidirectionality.mdx +++ b/modules/react/common/stories/mdx/Bidirectionality.mdx @@ -1,7 +1,7 @@ -import {Meta} from '@storybook/blocks'; -import {ExampleCodeBlock} from '@workday/canvas-kit-docs'; +import { Meta } from "@storybook/blocks"; +import { ExampleCodeBlock } from "@workday/canvas-kit-docs"; -import {RTL} from './examples/RTL'; +import { RTL } from "./examples/RTL"; @@ -50,11 +50,11 @@ onKeyDown(event: React.KeyboardEvent) { if (event.key === 'ArrowRight') { if (isRTL) { - // Right arrow still moves to the right, but "right" is not "advance" in RTL: - // the previous row is to the right of the current one. - model.events.goToPreviousRow(); + // Right arrow still moves to the right, but "right" is not "advance" in + // RTL: the previous item is to the right of the current one. + model.events.goToPrevious(); } else { - model.events.goToNextRow(); + model.events.goToNext(); } } } diff --git a/modules/react/common/stories/mdx/UniqueIds.mdx b/modules/react/common/stories/mdx/UniqueIds.mdx index 8907f8901f..e80e3219b2 100644 --- a/modules/react/common/stories/mdx/UniqueIds.mdx +++ b/modules/react/common/stories/mdx/UniqueIds.mdx @@ -1,4 +1,4 @@ -import {Meta} from '@storybook/blocks'; +import { Meta } from "@storybook/blocks"; @@ -33,7 +33,7 @@ const tableHeadingId = useUniqueId(); Because it accepts an ID, it is safe to call unconditionally: ```tsx -const MyField = ({id, ...elemProps}) => { +const MyField = ({ id, ...elemProps }) => { // uses `id` when given, generates one when not const fieldId = useUniqueId(id); return ; @@ -50,7 +50,7 @@ Returns a new ID string each time it is called. It is the generator behind `useU for the cases where an ID is needed outside a render, where hook rules do not apply. ```tsx -import {generateUniqueId} from '@workday/canvas-kit-react/common'; +import { generateUniqueId } from "@workday/canvas-kit-react/common"; const id = generateUniqueId(); ``` @@ -66,10 +66,13 @@ Replaces the random seed with one of your choosing. Two uses: - **Snapshot tests**, so the IDs in a snapshot are the same on every run. - **Server-side rendering**, so IDs generated on the server match the ones generated on the client - during hydration. Set the same seed in both places before anything renders. + during hydration. The seed is only half of it: IDs are the seed plus an incrementing counter, so + the two sides must also start from the same count and generate in the same order. Prefer + `useUniqueId` inside components, which uses `React.useId()` where it is available and does not + depend on either. ```tsx -import {setUniqueSeed} from '@workday/canvas-kit-react/common'; +import { setUniqueSeed } from "@workday/canvas-kit-react/common"; // from a script tag written by the server setUniqueSeed(window.__ID_SEED); @@ -80,10 +83,10 @@ setUniqueSeed(window.__ID_SEED); Resets the counter to zero. Intended for tests, in a `beforeEach`, alongside `setUniqueSeed`. ```tsx -import {setUniqueSeed, resetUniqueIdCount} from '@workday/canvas-kit-react/common'; +import { setUniqueSeed, resetUniqueIdCount } from "@workday/canvas-kit-react/common"; beforeEach(() => { - setUniqueSeed('a'); // a static seed + setUniqueSeed("a"); // a static seed resetUniqueIdCount(); // reset the counter before every test }); ``` From ed5569dc48ce6ccb7992af70803e3c7ebf99d79a Mon Sep 17 00:00:00 2001 From: hipuku Date: Fri, 16 Oct 2026 14:08:08 +1100 Subject: [PATCH 3/3] docs(common): Point the Theming link at the registered story ID The Theming story is registered as Features/Theming/Overview, so the link resolved to guides-theming--docs and opened nothing. --- modules/react/common/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/react/common/README.md b/modules/react/common/README.md index 7b3a114d49..81c96eec23 100644 --- a/modules/react/common/README.md +++ b/modules/react/common/README.md @@ -87,7 +87,7 @@ in Storybook. Theming documentation has its own README. You can find it [here](./lib/theming/README.md), and the guide is in -[Guides / Theming](https://workday.github.io/canvas-kit/?path=/docs/guides-theming--docs). +[Features / Theming / Overview](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs). ---