diff --git a/.storybook/main.ts b/.storybook/main.ts index 37954fc0ff..20b5bd1cfb 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -11,6 +11,7 @@ import {ExportedSymbol, Value} from '@workday/canvas-kit-docs/docgen/docTypes'; import {version} from '../lerna.json' assert {type: 'json'}; import stylingConfig from '../styling.config'; import { vitePluginInlineSpecifications } from './vite-plugin-inline-specifications'; +import {remarkRewriteCanvasRoutes} from './remark-rewrite-canvas-routes'; import { vitePluginRedirectMDXToGithub } from './vite-plugin-redirect-mdx-to-github'; import { vitePluginWholeSource } from './vite-plugin-whole-source'; import { vitePluginTypescriptWithTransformers } from '@workday/canvas-kit-styling-transform/vite'; @@ -44,7 +45,7 @@ const config: StorybookConfig = { options: { mdxPluginOptions: { mdxCompileOptions: { - remarkPlugins: [remarkGfm], + remarkPlugins: [remarkGfm, remarkRewriteCanvasRoutes], }, }, }, @@ -73,7 +74,7 @@ const config: StorybookConfig = { ...mdx({ include: '*.md', providerImportSource: '@mdx-js/react', - remarkPlugins: [remarkGfm], + remarkPlugins: [remarkGfm, remarkRewriteCanvasRoutes], }), }, vitePluginWholeSource(), diff --git a/.storybook/remark-rewrite-canvas-routes.ts b/.storybook/remark-rewrite-canvas-routes.ts new file mode 100644 index 0000000000..1762b54f7f --- /dev/null +++ b/.storybook/remark-rewrite-canvas-routes.ts @@ -0,0 +1,32 @@ +import type {Plugin} from 'unified'; +import {visit} from 'unist-util-visit'; + +import routes from './routes'; + +/** + * Rewrites Canvas site paths (`/components/.../`) to Storybook manager URLs for local docs and + * GitHub Pages. Runs during MDX compile so links in tables and nested MDX are included. + */ +export function remarkRewriteCanvasRoutes(): Plugin { + const routeKeys = new Set(Object.keys(routes)); + + return tree => { + visit(tree, 'link', node => { + const url = node.url; + if (typeof url !== 'string' || !url.startsWith('/')) { + return; + } + + const hashIndex = url.indexOf('#'); + const pathPart = hashIndex === -1 ? url : url.slice(0, hashIndex); + const hash = hashIndex === -1 ? '' : url.slice(hashIndex + 1); + + if (!routeKeys.has(pathPart)) { + return; + } + + const storyId = routes[pathPart as keyof typeof routes]; + node.url = `../?path=/docs/${storyId}${hash ? `#${hash}` : ''}`; + }); + }; +} diff --git a/.storybook/routes.js b/.storybook/routes.js index 5f17176fb5..1be755060f 100644 --- a/.storybook/routes.js +++ b/.storybook/routes.js @@ -10,8 +10,10 @@ const routes = { // Components '/components/buttons/action-bar/': 'components-buttons-action-bar--docs', '/components/buttons/button/': 'components-buttons--docs', - '/components/buttons/segmented-control/': 'preview-segmented-control--docs', + '/components/buttons/segmented-control/': 'components-buttons-segmented-control--docs', + '/components/buttons/toolbar/': 'components-buttons-toolbar--docs', '/components/containers/card/': 'components-containers-card--docs', + '/components/containers/expandable/': 'components-containers-expandable--docs', '/components/containers/side-panel/': 'preview-side-panel--docs', '/components/containers/table/': 'components-containers-table--docs', '/components/containers/tabs/': 'components-containers-tabs--docs', @@ -23,6 +25,7 @@ const routes = { '/components/inputs/color-input/': 'components-inputs-color-picker-color-input--docs', '/components/inputs/form-field/': 'components-inputs-form-field--docs', '/components/inputs/radio/': 'preview-inputs-radio--docs', + '/preview/inputs/radio/': 'preview-inputs-radio--docs', '/components/inputs/select/': 'components-inputs-select--docs', '/components/inputs/switch/': 'preview-inputs-switch-new--docs', '/components/inputs/text-area/': 'components-inputs-textarea--docs', @@ -44,7 +47,9 @@ const routes = { '/components/text/subtext/': 'components-text-subtext--docs', '/components/text/text/': 'components-text-text--docs', '/components/text/title/': 'components-text-title--docs', + '/components/hyperlink/': 'components-hyperlink--docs', // Get Started + '/guides/accessibility/overview/': 'guides-accessibility-overview--docs', '/get-started/for-developers/theming/overview/': 'features-theming-overview--docs', '/get-started/for-contributors/code/': 'guides-contributing--docs', '/get-started/for-developers/guides/combobox/': 'features-combobox--docs', diff --git a/.storybook/vite-plugin-redirect-mdx-to-github.ts b/.storybook/vite-plugin-redirect-mdx-to-github.ts index 4b317304fc..9e2a405e89 100644 --- a/.storybook/vite-plugin-redirect-mdx-to-github.ts +++ b/.storybook/vite-plugin-redirect-mdx-to-github.ts @@ -18,14 +18,22 @@ export function vitePluginRedirectMDXToGithub(): PluginOption { if (/.mdx?$/.test(id)) { return code .replace(/\[([^\]]+)\]\((\/[^\)]+)\)/g, function replacer(_match, p1, p2) { - const [url, hash] = p2.split('#'); + const hashIndex = p2.indexOf('#'); + const url = hashIndex === -1 ? p2 : p2.slice(0, hashIndex); + const hash = hashIndex === -1 ? '' : p2.slice(hashIndex + 1); if (routeKeys.includes(url)) { - return `[${p1}](?path=/docs/${routes[url]}${hash ? '#' + hash : ''})`; + // `../?path=` so links from the docs iframe resolve to the manager URL + // (e.g. `/?path=/docs/...` locally and `/canvas-kit/?path=...` on GitHub Pages). + return `[${p1}](../?path=/docs/${routes[url]}${hash ? `#${hash}` : ''})`; } // no match, return original return `[${p1}](${p2})`; }) .replace(/\[([^\]]+)\]\((\.\.?[^\)]+)\)/g, function replacer(match, p1, p2) { + // Storybook doc links rewritten by `remarkRewriteCanvasRoutes` / the route replacer above + if (p2.startsWith('../?path=') || p2.startsWith('./?path=') || p2.startsWith('?path=')) { + return match; + } // extract the directory from the resourcePath given by Webpack const {dir} = path.parse(id); diff --git a/modules/react/button/stories/button/Button.mdx b/modules/react/button/stories/button/Button.mdx index 9d1a9b82da..28874887be 100644 --- a/modules/react/button/stories/button/Button.mdx +++ b/modules/react/button/stories/button/Button.mdx @@ -118,25 +118,20 @@ tokens with the fallback being `brand.primary.**`. ## Accessibility -The primary accessibility goal is a visible, programmatically determinable name on a native control -that performs an in-page action (submit, open, dismiss, and similar). This page covers -**`PrimaryButton`**, **`SecondaryButton`**, **`TertiaryButton`**, and **`DeleteButton`**. Use -[Hyperlink](https://workday.github.io/canvas-kit/?path=/docs/components-hyperlink--docs) when the -control navigates to another URL or in-page location. For -[Toolbar](https://workday.github.io/canvas-kit/?path=/docs/components-buttons-toolbar--docs) -icon and dropdown controls, see that component’s documentation. For a control that opens a menu, -compose [Menu](/components/popups/menu/) so **`Menu.Target`** wires popup state. When users choose -one value from **two or more** mutually exclusive options, use -[Segmented Control](/components/buttons/segmented-control/) (`SegmentedControl.List` with multiple -**`SegmentedControl.Item`** components)—not a lone variant button. - -See the [Button pattern (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/button/) and Canvas Kit -[Accessibility overview](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-overview--docs). +The primary accessibility goal is a visible, programmatically determinable name on a native control that performs an in-page action (submit, open, dismiss, and similar). This page covers **`PrimaryButton`**, **`SecondaryButton`**, **`TertiaryButton`**, and **`DeleteButton`**. + +Use [Hyperlink](/components/hyperlink/) when the control navigates to another URL or in-page location. + +For [Toolbar](/components/buttons/toolbar/) icon and dropdown controls, see that component’s documentation. For a control that opens a menu, dialog, or expandable, see [Menu accessibility](/components/popups/menu/#accessibility), [Dialog accessibility](/components/popups/dialog/#accessibility), and [Expandable accessibility](/components/containers/expandable/#accessibility). + +When users choose one value from **two or more** mutually exclusive options, use +[Segmented Control](/components/buttons/segmented-control/)—not a lone variant button. Use [Radio](/preview/inputs/radio/) when the selection needs radio semantics or native form submission. + +See the [Button pattern (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/button/) and Canvas Kit [Accessibility overview](/guides/accessibility/overview/). ### Minimum Accessible Structure -The following matches the [PrimaryButton](#primarybutton) examples: visible text for the default case, -text plus a decorative icon, and an icon-only control with an accessible name. +The following matches the [PrimaryButton](#primarybutton) examples: visible text for the default case, text plus a decorative icon, and an icon-only control with an accessible name. ```tsx import {PrimaryButton} from '@workday/canvas-kit-react/button'; @@ -152,111 +147,87 @@ import {plusIcon, relatedActionsVerticalIcon} from '@workday/canvas-system-icons ; ``` -For icon-only buttons, prefer **`Tooltip`** with **`title`** and default `type="label"` (which sets -**`aria-label`** from **`title`** so sighted users also see the label). If the spec explicitly says -not to use a tooltip, set **`aria-label`** on the button instead. The same patterns apply to -**`SecondaryButton`**, **`TertiaryButton`**, and **`DeleteButton`**. +For icon-only buttons, prefer **`Tooltip`** with **`title`** and default `type="label"` (which sets **`aria-label`** from **`title`** so sighted users also see the label). **`Tooltip`** with `type="label"` overwrites the wrapped control’s accessible name—use it on icon-only buttons only; visible button text already supplies the name. If the spec explicitly says not to use a tooltip, set **`aria-label`** on the button instead. The same patterns apply to **`SecondaryButton`**, **`TertiaryButton`**, and **`DeleteButton`**. ### Built-in Behaviors -Canvas Kit applies these automatically on **`PrimaryButton`**, **`SecondaryButton`**, -**`TertiaryButton`**, and **`DeleteButton`**. **Do not duplicate them** in consuming code. +Canvas Kit applies these automatically on **`PrimaryButton`**, **`SecondaryButton`**, **`TertiaryButton`**, and **`DeleteButton`**. **Do not duplicate them** in consuming code. **ARIA and DOM** (_applied by button components_): -- **Variant buttons**: Render a native `