From dbf39059bced29b02663e35e0f84f567d5bbf57f Mon Sep 17 00:00:00 2001 From: Matthew Rust Date: Mon, 16 Feb 2026 21:01:04 -0800 Subject: [PATCH 1/5] feat: storybook mcp --- modules/mcp/README.md | 135 ++++---- modules/mcp/build/build-story-apps.ts | 207 ++++++++++++ modules/mcp/build/discover-stories.ts | 179 ++++++++++ modules/mcp/build/harness.html | 294 +++++++++++++++++ modules/mcp/build/index.ts | 11 + modules/mcp/build/story-viewer.html | 98 ++++++ .../build/storybook-stubs/canvas-kit-docs.tsx | 162 +++++++++ .../storybook-stubs/storybook-blocks.tsx | 3 + .../build/storybook-stubs/storybook-react.tsx | 11 + modules/mcp/build/vite-plugins.ts | 44 +++ modules/mcp/lib/index.ts | 158 +++++++++ modules/mcp/lib/stories-config.json | 310 ++++++++++++++++++ modules/mcp/package.json | 16 +- modules/preview-react/color-picker/index.ts | 2 +- modules/preview-react/select/index.ts | 2 +- modules/react/badge/index.ts | 2 +- modules/react/checkbox/lib/Checkbox.tsx | 2 +- modules/react/icon/index.ts | 2 +- modules/react/layout/index.ts | 8 +- 19 files changed, 1556 insertions(+), 90 deletions(-) create mode 100644 modules/mcp/build/build-story-apps.ts create mode 100644 modules/mcp/build/discover-stories.ts create mode 100644 modules/mcp/build/harness.html create mode 100644 modules/mcp/build/story-viewer.html create mode 100644 modules/mcp/build/storybook-stubs/canvas-kit-docs.tsx create mode 100644 modules/mcp/build/storybook-stubs/storybook-blocks.tsx create mode 100644 modules/mcp/build/storybook-stubs/storybook-react.tsx create mode 100644 modules/mcp/build/vite-plugins.ts create mode 100644 modules/mcp/lib/stories-config.json diff --git a/modules/mcp/README.md b/modules/mcp/README.md index b5f713c0a0..446ca1d05d 100644 --- a/modules/mcp/README.md +++ b/modules/mcp/README.md @@ -1,6 +1,6 @@ # Canvas Kit MCP -Model Context Protocol (MCP) server that provides Canvas Kit upgrade guides and design token migration documentation to AI assistants. +Our MCP server provides resources and tools to help you work with Canvas Kit components. ## Installation @@ -23,120 +23,101 @@ Add to your MCP servers configuration file: "mcpServers": { "canvas-kit-mcp": { "command": "npx", - "args": [ - "-y", - "@workday/canvas-kit-mcp" - ] + "args": ["-y", "@workday/canvas-kit-mcp"] } } } ``` -Configuration file locations: -- **Cursor**: `~/.cursor-tutor/config.json` (macOS/Linux) or `%APPDATA%\cursor-tutor\config.json` (Windows) -- **Windsurf**: Settings → MCP Servers -- **VS Code**: Cline/Continue extension settings - ### Claude Code CLI ```sh claude mcp add --scope project --transport stdio canvas-kit -- npx -y @workday/canvas-kit-mcp ``` -### Claude Desktop - -Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS): - -```json -{ - "mcpServers": { - "canvas-kit-mcp": { - "command": "npx", - "args": [ - "-y", - "@workday/canvas-kit-mcp" - ] - } - } -} -``` - ## Tools -This MCP server provides the following tools: - ### `get-canvas-kit-upgrade-guides` -Retrieves Canvas Kit version upgrade documentation covering major version migrations. +Returns Canvas Kit upgrade guide documentation (v9 through v14) as resource links. -**Use cases:** -- Upgrading Canvas Kit to a new major version -- Understanding breaking changes between versions -- Finding migration paths for deprecated components -- Learning about new features and components +### `get-canvas-kit-tokens` -**Returns:** Links to upgrade guide resources including version-specific migration steps, deprecation notices, theming guides, and styling migration documentation. +Returns Canvas Kit design token documentation for migrating to `@workday/canvas-tokens-web`. -### `get-canvas-kit-tokens` +### `fetch-component-documentation-example` + +Renders an interactive Canvas Kit component story inline for the user. Accepts a `story` parameter +with an enum of all available component slugs (e.g. `buttons`, `text-input`, `modal`, `tabs`, etc.). -Retrieves Canvas Kit design token migration documentation for transitioning to the modern `@workday/canvas-tokens-web` package. +The tool returns: -**Use cases:** -- Migrating from old token systems to `@workday/canvas-tokens-web` -- Converting deprecated color tokens to the new token system -- Understanding token hierarchy: base tokens, system tokens, and brand tokens -- Finding correct system token replacements -- Learning token naming patterns and semantic color roles -- Migrating spacing, shape, typography, opacity, and depth tokens -- Ensuring WCAG accessibility compliance with color contrast requirements +- The Storybook documentation URL +- A `resource_link` to `docs://examples/{story}` with documentation and code examples +- Story HTML via `_meta` for inline MCP App rendering -**Returns:** Links to token documentation including migration guides, color palettes, color roles, contrast guidelines, and token system references. +**LLMs should read the `docs://examples/{story}` resource first** for documentation and code +examples. Only call `fetch-component-documentation-example` to show the user a live interactive +preview. ## Resources -The server exposes documentation resources organized into two categories: +### `docs://upgrade-guides/*` -### Upgrade Guides -Version-specific migration documentation covering: -- Breaking changes and deprecations -- New components and features -- Styling system migrations (Emotion, CSS variables, `@workday/canvas-kit-styling`) -- Theming and component refactoring -- Accessibility improvements +Markdown upgrade guides for Canvas Kit major versions (v9-v14). -### Token Documentation -Design token migration and usage guides covering: -- Token system migrations (v2 → v3 → v4) -- Color palette and semantic color roles -- Token naming conventions and hierarchy -- Accessibility and contrast guidelines -- Spacing, shape, size, opacity, and depth tokens -- OKLCH color space implementation +### `docs://tokens/*` -All resources are available to AI assistants through MCP resource URIs. +Design token migration guides, color palette, roles, contrast, and scale documentation. -## Source Documentation +### `docs://examples/{slug}` -The documentation files served by this MCP server are maintained in the Canvas Kit repository at [`modules/docs/llm`](../../docs/llm). This directory contains: +Markdown documentation and inline code examples for each component. These are extracted from the +MDX story files at build time, with `ExampleCodeBlock` references replaced by the actual source code +of each example. -- **Upgrade Guides** (`upgrade-guides/`) - Version-specific migration documentation -- **Token Documentation** (`tokens/`) - Design token guides and migration resources -- **LLM-Optimized Files** - Specialized documentation formatted for AI assistants +### `ui://story/{slug}` -To update the documentation served by this MCP server, modify the files in `modules/docs/llm` and rebuild the MCP package. +Interactive HTML previews of Canvas Kit components, served as MCP App resources +(`text/html;profile=mcp-app`). These are compiled from each component's Storybook MDX documentation +and include live, styled component examples. ## Contributing Canvas Kit MCP has two exports: -- `src/cli.js` - Node server that can be invoked via npx for local stdio -- `src/index.js` - Low-level module exports for extending the server or hosting with other transports +- `dist/cli.js` -- a Node server that can be invoked via npx for local stdio +- `dist/index.js` -- module exports for extending the server or hosting with other transports + +### Build Pipeline + +The build runs in stages via `npm run build`: + +1. **`build:discover`** -- scans `modules/react` and `modules/preview-react` for story files, + extracts metadata (title, slug, Storybook URL, MDX path, prose with inlined code examples), and + writes `lib/stories-config.json` +2. **`build:apps`** -- compiles each MDX story into a self-contained single-file HTML app using + Vite, bundling React, Emotion, Canvas Tokens CSS, and lightweight Storybook stubs +3. **`build:copy`** -- copies static resources (upgrade guides, token docs) into `dist/lib` +4. **`build:types`** -- generates TypeScript declarations +5. **`build:mcp`** -- bundles `lib/index.ts` and `lib/cli.ts` with esbuild + +### Key build files + +- `build/vite-plugins.ts` -- shared Vite plugins (`canvasKitSourceResolver`) and + `CANVAS_KIT_PACKAGE_MAP` for monorepo package resolution +- `build/discover-stories.ts` -- story discovery and `stories-config.json` generation +- `build/build-story-apps.ts` -- MDX-to-HTML compilation with Vite +- `build/harness.html` -- HTML template for MCP App stories (MCP bridge, base typography, font + loading) +- `build/storybook-stubs/` -- lightweight replacements for Storybook components (`Meta`, + `ExampleCodeBlock`, `SymbolDoc`, etc.) used in MDX files -### Testing Locally +### To test locally #### MCP Inspector -The inspector requires Node.js v22+: +The inspector requires Node >= 22 so you will need to temporarily switch: ```bash nvm use 22 @@ -153,9 +134,7 @@ Add an entry to your MCP servers configuration pointing to your local build: "mcpServers": { "canvas-kit-mcp-local": { "command": "node", - "args": [ - "/absolute/path/to/canvas-kit/modules/mcp/dist/cli.js" - ] + "args": ["/absolute/path/to/canvas-kit/modules/mcp/dist/cli.js"] } } } diff --git a/modules/mcp/build/build-story-apps.ts b/modules/mcp/build/build-story-apps.ts new file mode 100644 index 0000000000..e7f3830f3c --- /dev/null +++ b/modules/mcp/build/build-story-apps.ts @@ -0,0 +1,207 @@ +import {build, type Plugin} from 'vite'; +import react from '@vitejs/plugin-react'; +import {viteSingleFile} from 'vite-plugin-singlefile'; +import mdx from '@mdx-js/rollup'; +import remarkGfm from 'remark-gfm'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {canvasKitSourceResolver} from './vite-plugins'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +interface StoriesConfig { + stories: Record< + string, + { + title: string; + storybookUrl: string; + mdxPath: string; + } + >; +} + +function extractExports(source: string): string[] { + const exportPattern = + /export (?:default|const|var|function)(?: class)?(?: function)? ([^:\s<();]*)/; + return (source.match(new RegExp(exportPattern, 'g')) || []) + .map(match => match.match(exportPattern)![1] || 'Example') + .filter(name => name.charAt(0).toUpperCase() === name.charAt(0)) + .filter((value, index, self) => self.indexOf(value) === index); +} + +function wholeSourcePlugin(): Plugin { + return { + name: 'whole-source-raw', + enforce: 'pre', + transform(code, id) { + if (!/\/examples\/[^/]+\.tsx$/.test(id)) { + return null; + } + const raw = JSON.stringify(code) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029'); + const exports = extractExports(code); + if (exports.length === 0) { + return null; + } + const rewritten = code.includes('export default (') + ? code.replace('export default (', 'const Example = (') + '\nexport default Example;' + : code; + return `${rewritten}\n${exports.map(name => `${name}.__RAW__ = ${raw};`).join('\n')}\n`; + }, + }; +} + +function generateEntryHtml(entryFile: string): string { + const harnessPath = path.join(__dirname, 'harness.html'); + if (!fs.existsSync(harnessPath)) { + throw new Error(`Harness template not found at ${harnessPath}`); + } + + let harness = fs.readFileSync(harnessPath, 'utf-8'); + const appScript = ``; + harness = harness.replace('', `${appScript}\n`); + return harness; +} + +function generateEntryTsx(mdxRelativePath: string): string { + return `import React from 'react'; +import {createRoot} from 'react-dom/client'; +import {MDXProvider} from '@mdx-js/react'; +import '@workday/canvas-tokens-web/css/base/_variables.css'; +import '@workday/canvas-tokens-web/css/brand/_variables.css'; +import '@workday/canvas-tokens-web/css/system/_variables.css'; +import MDXContent from '${mdxRelativePath}'; +import {Meta} from '@storybook/blocks'; +import {ExampleCodeBlock, SymbolDoc, SymbolDescription, Specifications, InformationHighlight} from '@workday/canvas-kit-docs'; + +const mdxComponents = {Meta, ExampleCodeBlock, SymbolDoc, SymbolDescription, Specifications, InformationHighlight}; + +function App() { + return ( + + + + ); +} + +createRoot(document.getElementById('root')!).render( + + + +); +`; +} + +async function buildStoryApps() { + const configPath = path.resolve(__dirname, '../lib/stories-config.json'); + if (!fs.existsSync(configPath)) { + console.error('stories-config.json not found. Run discover-stories first.'); + process.exit(1); + } + + const config: StoriesConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); + const outDir = path.resolve(__dirname, '../dist/apps'); + const repoRoot = path.resolve(__dirname, '../../..'); + const stubsDir = path.resolve(__dirname, 'storybook-stubs'); + + fs.mkdirSync(outDir, {recursive: true}); + + const slugs = Object.keys(config.stories); + console.log(`Building ${slugs.length} story apps...`); + + let built = 0; + let failed = 0; + + for (const slug of slugs) { + const story = config.stories[slug]; + const mdxAbsPath = path.resolve(repoRoot, story.mdxPath); + + if (!fs.existsSync(mdxAbsPath)) { + console.warn(` SKIP ${slug}: MDX not found at ${mdxAbsPath}`); + failed++; + continue; + } + + const mdxDir = path.dirname(mdxAbsPath); + const tempEntryPath = path.join(mdxDir, `__mcp_entry_${slug}.tsx`); + const tempHtmlPath = path.join(mdxDir, `__mcp_index_${slug}.html`); + + const mdxBaseName = path.basename(mdxAbsPath); + const entryTsx = generateEntryTsx(`./${mdxBaseName}`); + const entryHtml = generateEntryHtml(`__mcp_entry_${slug}.tsx`); + + fs.writeFileSync(tempEntryPath, entryTsx); + fs.writeFileSync(tempHtmlPath, entryHtml); + + try { + await build({ + root: mdxDir, + base: './', + plugins: [ + canvasKitSourceResolver(repoRoot), + wholeSourcePlugin(), + { + enforce: 'pre', + ...mdx({remarkPlugins: [remarkGfm], providerImportSource: '@mdx-js/react'}), + }, + react({include: /\.(mdx|js|jsx|ts|tsx)$/}), + viteSingleFile(), + ], + resolve: { + alias: { + '@workday/canvas-kit-docs': path.join(stubsDir, 'canvas-kit-docs.tsx'), + '@storybook/blocks': path.join(stubsDir, 'storybook-blocks.tsx'), + '@storybook/react': path.join(stubsDir, 'storybook-react.tsx'), + }, + }, + build: { + outDir, + emptyOutDir: false, + rollupOptions: { + input: tempHtmlPath, + output: { + entryFileNames: `${slug}.js`, + assetFileNames: `${slug}.[ext]`, + }, + }, + minify: true, + sourcemap: false, + }, + logLevel: 'silent', + }); + + const outputHtml = path.join(outDir, `__mcp_index_${slug}.html`); + const finalHtml = path.join(outDir, `${slug}.html`); + if (fs.existsSync(outputHtml)) { + fs.renameSync(outputHtml, finalHtml); + } + + built++; + console.log(` OK ${slug}`); + } catch (error) { + failed++; + console.error(` FAIL ${slug}:`, error instanceof Error ? error.message : error); + } finally { + try { + fs.unlinkSync(tempEntryPath); + } catch { + // Ignore + } + try { + fs.unlinkSync(tempHtmlPath); + } catch { + // Ignore + } + } + } + + console.log(`\nBuild complete: ${built} succeeded, ${failed} failed out of ${slugs.length}`); +} + +buildStoryApps().catch((error: unknown) => { + console.error('Build failed:', error); + process.exit(1); +}); diff --git a/modules/mcp/build/discover-stories.ts b/modules/mcp/build/discover-stories.ts new file mode 100644 index 0000000000..b83a486309 --- /dev/null +++ b/modules/mcp/build/discover-stories.ts @@ -0,0 +1,179 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {glob} from 'glob'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const STORYBOOK_BASE_URL = 'https://workday.github.io/canvas-kit/'; + +interface StoryEntry { + title: string; + storybookUrl: string; + mdxPath: string; + mdxProse: string; +} + +function titleToStorybookPath(title: string): string { + return title.toLowerCase().replace(/\//g, '-').replace(/\s+/g, '-'); +} + +function titleToSlug(title: string): string { + const parts = title.split('/'); + const leaf = parts[parts.length - 1]; + return leaf.toLowerCase().replace(/\s+/g, '-'); +} + +function extractTitle(filePath: string): string | null { + const content = fs.readFileSync(filePath, 'utf8'); + const match = content.match(/title:\s*['"`]([^'"`]+)['"`]/); + return match ? match[1] : null; +} + +function extractMdxProse(mdxFilePath: string, exampleSources: Record): string { + const content = fs.readFileSync(mdxFilePath, 'utf8'); + const withoutImports = content.replace( + /import\s+(?:(?:\{[\s\S]*?\}|\*\s+as\s+\w+|[\w]+)\s+from\s+)?['"][^'"]+['"];?\n?/g, + '' + ); + return withoutImports + .split('\n') + .map(line => { + const codeBlockMatch = line.match(/^\s*/); + if (codeBlockMatch) { + const name = codeBlockMatch[1]; + const source = exampleSources[name]; + if (source) { + return `\`\`\`tsx\n${source.trimEnd()}\n\`\`\``; + } + return ''; + } + if ( + /^\s*<(Meta|SymbolDoc|SymbolDescription|Specifications|InformationHighlight)\b/.test(line) + ) { + return ''; + } + return line; + }) + .join('\n') + .replace(/^\n+/, '') + .replace(/\n{3,}/g, '\n\n'); +} + +function findExampleSources(mdxFilePath: string): Record { + const mdxDir = path.dirname(mdxFilePath); + const examplesDir = path.join(mdxDir, 'examples'); + if (!fs.existsSync(examplesDir) || !fs.statSync(examplesDir).isDirectory()) { + return {}; + } + + const sources: Record = {}; + const entries = fs.readdirSync(examplesDir).filter(f => f.endsWith('.tsx') || f.endsWith('.ts')); + + for (const entry of entries) { + const name = entry.replace(/\.(tsx?|ts)$/, ''); + sources[name] = fs.readFileSync(path.join(examplesDir, entry), 'utf8'); + } + + return sources; +} + +function findMdxFile(storyFilePath: string): string | null { + const dir = path.dirname(storyFilePath); + const entries = fs.readdirSync(dir); + const mdxFiles = entries.filter(e => e.endsWith('.mdx')); + + if (mdxFiles.length === 0) { + return null; + } + + const storyBaseName = path.basename(storyFilePath).replace(/\.stories\.(ts|tsx)$/, ''); + const exactMatch = mdxFiles.find(f => f.replace('.mdx', '') === storyBaseName); + if (exactMatch) { + return path.join(dir, exactMatch); + } + + return path.join(dir, mdxFiles[0]); +} + +async function main() { + const repoModules = path.resolve(__dirname, '../..'); + const outputPath = path.resolve(__dirname, '../lib/stories-config.json'); + + const storyFiles = await glob('**/stories/**/*.stories.{ts,tsx}', { + cwd: repoModules, + absolute: true, + ignore: ['**/node_modules/**', '**/visual-testing/**'], + }); + + const stories: Record = {}; + const slugCounts = new Map(); + + const TITLE_PREFIXES = ['Components/', 'Preview/']; + const candidates: Array<{slug: string; title: string; storyFile: string; mdxPath: string}> = []; + + for (const storyFile of storyFiles) { + const title = extractTitle(storyFile); + if (!title) { + continue; + } + if (!TITLE_PREFIXES.some(prefix => title.startsWith(prefix))) { + continue; + } + + const mdxPath = findMdxFile(storyFile); + if (!mdxPath) { + continue; + } + + const slug = titleToSlug(title); + const count = slugCounts.get(slug) || 0; + slugCounts.set(slug, count + 1); + + const relativeMdxPath = path.relative(path.resolve(__dirname, '../../..'), mdxPath); + candidates.push({slug, title, storyFile, mdxPath: relativeMdxPath}); + } + + for (const candidate of candidates) { + let slug = candidate.slug; + const count = slugCounts.get(slug) || 0; + + if (count > 1) { + const parts = candidate.title.split('/'); + slug = parts.join('-').toLowerCase().replace(/\s+/g, '-'); + } + + if (stories[slug]) { + console.warn(`Duplicate slug "${slug}" for "${candidate.title}", skipping`); + continue; + } + + const storybookPath = titleToStorybookPath(candidate.title); + const repoRoot = path.resolve(__dirname, '../../..'); + const absoluteMdxPath = path.resolve(repoRoot, candidate.mdxPath); + const exampleSources = findExampleSources(absoluteMdxPath); + stories[slug] = { + title: candidate.title, + storybookUrl: `${STORYBOOK_BASE_URL}?path=/docs/${storybookPath}--docs`, + mdxPath: candidate.mdxPath, + mdxProse: extractMdxProse(absoluteMdxPath, exampleSources), + }; + } + + const outputDir = path.dirname(outputPath); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}); + } + + fs.writeFileSync(outputPath, JSON.stringify({stories}, null, 2) + '\n'); + + const slugList = Object.keys(stories); + console.log(`Discovered ${slugList.length} component stories:`); + slugList.forEach(slug => console.log(` - ${slug}: ${stories[slug].title}`)); +} + +main().catch((error: unknown) => { + console.error('Discovery failed:', error); + process.exit(1); +}); diff --git a/modules/mcp/build/harness.html b/modules/mcp/build/harness.html new file mode 100644 index 0000000000..cbd27d0e73 --- /dev/null +++ b/modules/mcp/build/harness.html @@ -0,0 +1,294 @@ + + + + + + Canvas Kit Story + + + + + + +
+ + + diff --git a/modules/mcp/build/index.ts b/modules/mcp/build/index.ts index 4168d45a6d..2144d5ae09 100644 --- a/modules/mcp/build/index.ts +++ b/modules/mcp/build/index.ts @@ -42,4 +42,15 @@ allFiles.forEach(file => console.log(` - ${file}`)); allFiles.forEach(file => copyFile(file)); +const storyViewerSrc = path.resolve(__dirname, 'story-viewer.html'); +const storyViewerDest = path.resolve(__dirname, '../dist/apps/story-viewer.html'); +if (fs.existsSync(storyViewerSrc)) { + const appsDir = path.dirname(storyViewerDest); + if (!fs.existsSync(appsDir)) { + fs.mkdirSync(appsDir, {recursive: true}); + } + fs.copyFileSync(storyViewerSrc, storyViewerDest); + console.log(' - story-viewer.html -> dist/apps/'); +} + console.log('\nCopy completed successfully!'); diff --git a/modules/mcp/build/story-viewer.html b/modules/mcp/build/story-viewer.html new file mode 100644 index 0000000000..47d70da215 --- /dev/null +++ b/modules/mcp/build/story-viewer.html @@ -0,0 +1,98 @@ + + + + + + Canvas Kit Story Viewer + + + +
Loading story...
+ + + diff --git a/modules/mcp/build/storybook-stubs/canvas-kit-docs.tsx b/modules/mcp/build/storybook-stubs/canvas-kit-docs.tsx new file mode 100644 index 0000000000..f2da188f7c --- /dev/null +++ b/modules/mcp/build/storybook-stubs/canvas-kit-docs.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'; +import {vscDarkPlus} from 'react-syntax-highlighter/dist/esm/styles/prism'; + +declare global { + interface Window { + __MCP_BRIDGE__?: { + request(method: string, params: unknown): Promise; + notify(method: string, params: unknown): void; + on(event: string, callback: (data: unknown) => void): () => void; + initialize(appInfo?: unknown): Promise; + updateModelContext(content: unknown): void; + applyHostStyles(hostContext: unknown): void; + }; + } +} + +interface ExampleComponent extends React.ComponentType { + __RAW__?: string; +} + +export function ExampleCodeBlock({code}: {code: ExampleComponent}) { + const [showCode, setShowCode] = React.useState(false); + const [sent, setSent] = React.useState(false); + const timerRef = React.useRef>(); + const raw = code?.__RAW__; + + const handleSendToLLM = () => { + if (!raw) { + return; + } + if (timerRef.current) { + clearTimeout(timerRef.current); + } + setSent(true); + timerRef.current = setTimeout(() => setSent(false), 2000); + + const bridge = window.__MCP_BRIDGE__; + if (!bridge) { + return; + } + + bridge.request('ui/message', { + role: 'user', + content: [ + {type: 'text', text: `Here is a Canvas Kit code example:\n\n\`\`\`tsx\n${raw}\n\`\`\``}, + ], + }); + }; + + const Component = code; + + return ( +
+
+ + {raw && ( +
+ +
+ )} +
+ {showCode && raw && ( +
+ + {raw} + + +
+ )} +
+ ); +} + +export function SymbolDoc(_props: {name?: string; fileName?: string}) { + return null; +} + +export function SymbolDescription(_props: {name?: string; fileName?: string}) { + return null; +} + +export function Specifications(_props: {file?: string; name?: string}) { + return null; +} + +export function InformationHighlight({ + children, +}: React.PropsWithChildren<{variant?: string; className?: string; cs?: unknown}>) { + return ( +
+ {children} +
+ ); +} diff --git a/modules/mcp/build/storybook-stubs/storybook-blocks.tsx b/modules/mcp/build/storybook-stubs/storybook-blocks.tsx new file mode 100644 index 0000000000..dbbbd2c60b --- /dev/null +++ b/modules/mcp/build/storybook-stubs/storybook-blocks.tsx @@ -0,0 +1,3 @@ +export function Meta(_props: {of?: unknown}) { + return null; +} diff --git a/modules/mcp/build/storybook-stubs/storybook-react.tsx b/modules/mcp/build/storybook-stubs/storybook-react.tsx new file mode 100644 index 0000000000..3eaecfbee4 --- /dev/null +++ b/modules/mcp/build/storybook-stubs/storybook-react.tsx @@ -0,0 +1,11 @@ +export type Meta = { + title?: string; + component?: T; + tags?: string[]; + parameters?: Record; +}; + +export type StoryObj = { + render?: React.ComponentType; + args?: Partial; +}; diff --git a/modules/mcp/build/vite-plugins.ts b/modules/mcp/build/vite-plugins.ts new file mode 100644 index 0000000000..32910c7356 --- /dev/null +++ b/modules/mcp/build/vite-plugins.ts @@ -0,0 +1,44 @@ +import type {Plugin} from 'vite'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +export const CANVAS_KIT_PACKAGE_MAP: Record = { + '@workday/canvas-kit-react': 'modules/react', + '@workday/canvas-kit-preview-react': 'modules/preview-react', + '@workday/canvas-kit-labs-react': 'modules/labs-react', + '@workday/canvas-kit-styling': 'modules/styling', + '@workday/canvas-kit-styling-transform': 'modules/styling-transform', + '@workday/canvas-kit-popup-stack': 'modules/popup-stack', + '@workday/canvas-kit-react-fonts': 'modules/react-fonts', +}; + +export function canvasKitSourceResolver(repoRoot: string): Plugin { + return { + name: 'canvas-kit-source-resolver', + enforce: 'pre', + resolveId(source) { + for (const [pkg, modulePath] of Object.entries(CANVAS_KIT_PACKAGE_MAP)) { + if (source === pkg) { + const resolved = path.join(repoRoot, modulePath, 'index.ts'); + if (fs.existsSync(resolved)) { + return resolved; + } + } + + if (source.startsWith(pkg + '/')) { + const subpath = source.slice(pkg.length + 1); + for (const candidate of [ + path.join(repoRoot, modulePath, subpath, 'index.ts'), + path.join(repoRoot, modulePath, subpath + '.ts'), + path.join(repoRoot, modulePath, subpath + '.tsx'), + ]) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + } + } + return null; + }, + }; +} diff --git a/modules/mcp/lib/index.ts b/modules/mcp/lib/index.ts index e8e27d0f37..e3167aa404 100644 --- a/modules/mcp/lib/index.ts +++ b/modules/mcp/lib/index.ts @@ -3,8 +3,10 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import {fileURLToPath} from 'node:url'; +import {z} from 'zod'; import packageJson from '../package.json'; import fileNames from './config.json'; +import storiesConfig from './stories-config.json'; import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; const __filename = fileURLToPath(import.meta.url); @@ -468,5 +470,161 @@ Returns links to token documentation resources including migration guides, color }; } ); + + interface StoryConfig { + title: string; + storybookUrl: string; + mdxPath: string; + mdxProse: string; + } + + const stories = storiesConfig.stories as Record; + const storySlugs: string[] = []; + + for (const [slug, story] of Object.entries(stories)) { + const appPath = path.resolve(__dirname, 'apps', `${slug}.html`); + const appExists = fs.existsSync(appPath); + + if (appExists) { + storySlugs.push(slug); + server.registerResource( + story.title, + `ui://story/${slug}`, + { + title: story.title, + description: `Interactive preview of the ${story.title} Canvas Kit component`, + mimeType: 'text/html;profile=mcp-app', + }, + async (uri: URL) => ({ + contents: [ + { + uri: uri.href, + text: fs.readFileSync(appPath, 'utf8'), + mimeType: 'text/html;profile=mcp-app', + _meta: { + ui: { + csp: { + resourceDomains: ['https://fonts.googleapis.com', 'https://fonts.gstatic.com'], + }, + }, + }, + }, + ], + }) + ); + } + + if (story.mdxProse) { + server.registerResource( + `${story.title} Documentation & Sample Code`, + `docs://examples/${slug}`, + { + title: `${story.title} Documentation & Sample Code`, + description: `Documentation and source code for all ${story.title} component examples.`, + mimeType: 'text/markdown', + }, + async (uri: URL) => ({ + contents: [ + { + uri: uri.href, + text: story.mdxProse, + }, + ], + }) + ); + } + } + + const storyViewerPath = path.resolve(__dirname, 'apps', 'story-viewer.html'); + if (storySlugs.length > 0 && fs.existsSync(storyViewerPath)) { + const slugEnum = storySlugs as [string, ...string[]]; + + server.registerResource( + 'Canvas Kit Story Viewer', + 'ui://story-viewer', + { + title: 'Canvas Kit Story Viewer', + description: 'Wrapper app that renders Canvas Kit component story previews.', + mimeType: 'text/html;profile=mcp-app', + }, + async (uri: URL) => ({ + contents: [ + { + uri: uri.href, + text: fs.readFileSync(storyViewerPath, 'utf8'), + mimeType: 'text/html;profile=mcp-app', + _meta: { + ui: { + csp: { + resourceDomains: ['https://fonts.googleapis.com', 'https://fonts.gstatic.com'], + }, + }, + }, + }, + ], + }) + ); + + const fetchStoryHandler = async ({story}: {story: string}) => { + const config = stories[story]; + if (!config) { + throw new Error(`Unknown story "${story}". Valid stories: ${storySlugs.join(', ')}`); + } + const appPath = path.resolve(__dirname, 'apps', `${story}.html`); + const storyHtml = fs.readFileSync(appPath, 'utf8'); + const hasDocs = !!config.mdxProse; + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ + displayGuide: + 'Present the Storybook URL as a markdown link. If you need code examples, read the resource_link.', + title: config.title, + storybookUrl: config.storybookUrl, + }), + }, + ...(hasDocs + ? [ + { + type: 'resource_link' as const, + uri: `docs://examples/${story}`, + name: `${config.title} Documentation & Sample Code`, + mimeType: 'text/markdown', + description: `Documentation and code examples for ${config.title}. Read this if you need to write code.`, + }, + ] + : []), + ], + _meta: { + storyHtml, + }, + }; + }; + + (server.registerTool as Function)( + 'fetch-component-documentation-example', + { + title: 'Fetch Canvas Kit Component Documentation and Storybook example', + description: + 'Renders an interactive Canvas Kit component story inline for the user to see.\n\n' + + 'Before Calling:\n' + + '1. Read the docs://examples/{story} resource for documentation and code examples\n' + + '2. Only call this tool if the user needs to see the documentation or code examples\n' + + '3. Do not call this tool just to learn about a component — read the resource instead', + inputSchema: { + story: z.enum(slugEnum).describe('The component story slug to preview'), + }, + annotations: { + readOnlyHint: true, + }, + _meta: { + ui: {resourceUri: 'ui://story-viewer'}, + }, + }, + fetchStoryHandler + ); + } + return server; } diff --git a/modules/mcp/lib/stories-config.json b/modules/mcp/lib/stories-config.json new file mode 100644 index 0000000000..85ac80bf89 --- /dev/null +++ b/modules/mcp/lib/stories-config.json @@ -0,0 +1,310 @@ +{ + "stories": { + "tooltip": { + "title": "Components/Popups/Tooltip", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-popups-tooltip--docs", + "mdxPath": "modules/react/tooltip/stories/Tooltip.mdx", + "mdxProse": "# Canvas Kit React Tooltips\n\nA Tooltip component that renders information/text when the user hovers over an element. A tooltip is\nused to label or describe an element. By default, a tooltip will label an element. This is useful\nfor buttons with icons. A tooltip can also be used to describe additional information about an\nelement\n\n[> Workday Design Reference](https://design.workday.com/components/popups/tooltips)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\nThis component follows the\n[W3 Tooltip specification](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/). Tooltips are used to\nlabel buttons with icons and provide additional context to elements.\n\n### When to use tooltips\n\nUse a tooltip when you want to display additional information for users to better understand the\npurpose, context, or interaction.\n\n### When not to use tooltips\n\nWhen the visual text will be the exact same as what is visually displayed to the user without the\ntooltip being visible\n\n- Does this element need additional context or information?\n - No: Don't use a tooltip\n - Yes:\n - Is the tooltip text useful to screen reader users?\n - No: Use `type=\"muted\"` which will not make the tooltip visible to screen reader users\n - Yes:\n - Is the tooltip text different from the visual text displayed to users?\n - No text: Use `type=\"label\"` which will add `aria-label` like the icon example\n - Yes: Use `type=\"describe\"` which will add `aria-describedby`\n - No: Don't use a tooltip\n\n### Basic Example\n\nHere is a basic example of a `TertiaryButton` that renders an icon using a tooltip to label the\nicon. This labels the button for both sighted users and screen readers. A tooltip provides an\n`aria-label` to child elements for the accessibility tree and a visual tooltip during mouse hover\nand focus events.\n\n```tsx\nimport {xIcon} from '@workday/canvas-system-icons-web';\nimport {TertiaryButton} from '@workday/canvas-kit-react/button';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const Default = () => {\n return (\n \n \n \n );\n};\n```\n\n### Describing an Element\n\n Assistive technology may ignore type=\"describe\" techniques based on verbosity settings. Please use type=\"description\" on Tooltips.\n \n\n\nThe default mode for a tooltip is to label content via `aria-label`. If a tooltip is meant to\nprovide ancillary information, the `type` can be set to `describe`. This will add `aria-describedby`\nto the target element. This will allow screen reader users to hear the name of the control that is\nbeing focused and the ancillary tooltip information.\n\n```tsx\nimport {DeleteButton} from '@workday/canvas-kit-react/button';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const DescribeType = () => {\n return (\n \n Delete\n \n );\n};\n```\n\n### Description of an Element\n\nThe default mode for a tooltip is to assign a name to the target element with an `aria-label`\nstring. If a tooltip is meant to provide ancillary information, the `type` can be set to `description`.\nThis will add `aria-description` strings to the target element instead. This variant is useful on\ntext buttons and other components that already have a label or name. Use this type instead of `describe` to ensure proper aria attributes are added to the dom regardless if the tooltip is visible. \n\n> **Note:** If you use `description` type and want to pass `jsx`, it **must* be inline and **not** a component to ensure the inner text is properly read by voiceover.\n>\n> ```jsx\n> // The text will be understood as: You must accept terms and conditions\n> Youmust accept terms and conditions}/>\n>\n> // This will render a string including the html and will not be properly understood by voice over.\n> const MyComponent = () => Youmust accept terms and conditions\n> {\n return (\n \n \n Advanced Search\n \n \n Save\n \n \n Delete\n \n \n );\n};\n```\n\n### Muted Tooltips\n\nIf a tooltip does not need to be visible to screen reader users, or you handle accessibility of the\ntooltip yourself, you can set the `type` to `muted`. This will not add any special ARIA attributes\nto the target element.\n\n```tsx\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const Muted = () => {\n return (\n \n Some text. The contents of the tooltip are invisible to screen reader users.\n \n );\n};\n```\n\n### Custom Content\n\nA tooltip can contain HTML, but should not contain any focusable elements or semantically meaningful\nformatting. The content will lose all semantic meaning when read by a screen reader. If complex\ncontent or a focusable element is needed by your UI, a tooltip is not a good choice. Consider using\na dialog instead.\n\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const CustomContent = () => {\n return (\n \n \n This is a custom tooltip with custom HTML\n \n }\n >\n Hover Me\n \n \n );\n};\n```\n\n### Delayed Tooltip\n\nThe default delay for showing and hiding a tooltip are 300ms and 100ms, respectively. You can\ncontrol the length of the delay by providing custom `showDelay` and `hideDelay` in ms.\n\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const DelayedTooltip = () => {\n return (\n \n \n \n Tooltip appears after 2 seconds and disappears after 1 second\n \n \n \n );\n};\n```\n\n### Placements\n\nThe tooltip allows for a `placement` configuration. The tooltip uses\n[PopperJS](https://popper.js.org/) to position tooltips, so any valid PopperJS placement is valid\nfor tooltips.\n\n```tsx\nimport React from 'react';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {Card} from '@workday/canvas-kit-react/card';\nimport {Placement} from '@workday/canvas-kit-react/popup';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst placementCardStyles = createStyles({\n display: 'flex',\n width: 100,\n height: 100,\n justifyContent: 'space-around',\n alignItems: 'center',\n padding: system.space.x1,\n});\n\nexport const Placements = () => {\n const placementStyles = {\n display: 'flex',\n justifyContent: 'space-around',\n };\n\n const createPlacement = (placement: string, index) => {\n return (\n \n \n {placement}\n \n \n );\n };\n\n return (\n \n
\n
\n {['top-start', 'top', 'top-end'].map(createPlacement)}\n
\n
\n
\n {['left-start', 'left', 'left-end'].map(createPlacement)}\n
\n
\n
\n {['right-start', 'right', 'right-end'].map(createPlacement)}\n
\n
\n
\n {['bottom-start', 'bottom', 'bottom-end'].map(createPlacement)}\n
\n
\n
\n );\n};\n```\n\n### Tooltips on overflowing content\n\nThe `OverflowTooltip` component can be applied to any element that has some type of overflow\napplied, or has a child element that has overflow applied. The most common and widely supported type\nof truncation is the ellipsis.\n\n```css\noverflow: hidden;\ntext-overflow: ellipsis;\nwhite-space: nowrap;\n```\n\n**Note**: Text truncation should be avoided if possible. A user should not have to activate a\ntooltip to access important content. If user-generated content is being truncated, the following\nsituation might occur which is a bad user experience. Consider the following list:\n\n- Home Site A\n- Home Site B\n- Home Site C\n\nIf the list items get truncated via an ellipsis, this is what the user could see:\n\n- Home Sit...\n- Home Sit...\n- Home Sit...\n\nHere are suggestions to try to avoid truncation:\n\n- Allow content to wrap instead\n- Limit character count in admin interfaces if possible to avoid need for truncation\n- Avoid fixed container sizes if possible to allow content to flow naturally\n\nIf truncation is required, here are a few guidelines to insure minimal impact on users:\n\n- Only truncate text of elements that naturally receive focus.\n - Keyboard users can only activate tooltips with focus. Adding `tabindex=0` can give focus to\n non-focusable elements, but increase the amount of tab stops for keyboard users.\n- Provide the full content elsewhere in the UI\n\nCanvas Kit Buttons have this style applied to the text inside them. `OverflowTooltip` in combination\nwith a max-width can show a tooltip only when overflow is detected:\n\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {OverflowTooltip} from '@workday/canvas-kit-react/tooltip';\nimport {space} from '@workday/canvas-kit-react/tokens';\nimport {resetIcon} from '@workday/canvas-system-icons-web';\n\nconst CustomContent = ({...elemProps}) => (\n \n Super Mega Ultra Long Content With Max Width Custom\n \n);\n\nexport const Ellipsis = () => {\n return (\n \n \n Short Content\n {' '}\n \n \n Super Mega Ultra Long Content With Max Width On The Button\n \n \n \n \n Super Mega Ultra Long Content With Max Width On The Button with Icon\n \n \n \n \n Super Mega Ultra Long Content With Max Width\n \n \n \n \n \n \n );\n};\n```\n\n### Line Clamp\n\nThe `OverflowTooltip` can support various types of overflow. The component will first look for\n`text-overflow: ellipsis` and `-webkit-line-clamp`, but will fall back to\n`overflow: auto | scroll | clip | hidden`. These properties will be used to determine which\n`element` is experiencing an overflow. Overflow detection is as follows where `element` is\ndetermined by the above style properties:\n\n```js\nelement.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;\n```\n\nHere's an example using the `-webkit-line-clamp` property (multi-line ellipsis which works in all\nbrowsers):\n\n```tsx\nimport React from 'react';\n\nimport {OverflowTooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const LineClamp = () => {\n return (\n \n \n \n );\n};\n```\n\nOther truncation techniques should be supported as well, even JavaScript ones as long as overflow is\ntriggered somehow and detectable differences in scroll size and client size.\n\n### The UseTooltip Hook\n\nThe `Tooltip` component is a combination of the `TooltipContainer` (a styled element), `Popper`\n(which uses PopperJS and the popup stack), the `useTooltip` hook and some behavior. If custom\nbehavior is required, these sub-components can be composed in a custom container element. This\nexample uses those parts directly while being functionally equivalent to the original basic example.\n\n```tsx\nimport React from 'react';\n\nimport {Popper} from '@workday/canvas-kit-react/popup';\nimport {xIcon} from '@workday/canvas-system-icons-web';\nimport {TertiaryButton} from '@workday/canvas-kit-react/button';\nimport {TooltipContainer, useTooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const UseTooltip = () => {\n const {targetProps, popperProps, tooltipProps} = useTooltip();\n\n return (\n <>\n \n \n Close\n \n \n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" + }, + "inputs-text-input": { + "title": "Components/Inputs/Text Input", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-text-input--docs", + "mdxPath": "modules/react/text-input/stories/TextInput.mdx", + "mdxProse": "# Canvas Kit Text Input\n\nText Inputs allow users to enter words or characters without styling.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-input)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Input should be used in tandem with [Form Field](/components/inputs/form-field/) to meet\naccessibility standards.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n \n \n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Input to prevent users from interacting with it.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n \n \n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Input to display a sample of its expected format or value\nbefore a value has been provided.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n \n \n );\n};\n```\n\n### Ref Forwarding\n\nText Input supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `` element.\n\n```tsx\nimport React from 'react';\nimport {PrimaryButton} from '@workday/canvas-kit-react/button';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const RefForwarding = () => {\n const [value, setValue] = React.useState('');\n const ref = React.useRef(null);\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n const handleClick = () => {\n ref.current.focus();\n };\n\n return (\n <>\n \n Email\n \n \n \n \n Focus Text Input\n \n );\n};\n```\n\n### Grow\n\nSet the `grow` prop of the wrapping Form Field to `true` to configure the Text Input to expand to\nthe width of its container.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Street Address\n \n \n \n \n );\n};\n```\n\nThe `grow` prop may also be applied directly to the Text Input if Form Field is not being used.\n\n### Label Position Horizontal\n\nSet the `orientation` prop of the Form Field to designate the position of the label relative to the\ninput component. By default, the orientation will be set to `vertical`.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const LabelPosition = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n Add a valid email\n \n \n );\n};\n```\n\n### Required\n\nSet the `required` prop of the wrapping Form Field to `true` to indicate that the field is required.\nLabels for required fields are suffixed by a red asterisk.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n \n \n );\n};\n```\n\n### Icons\n\n`InputGroup` is available to add icons to the `TextInput`. Internally, a container `div` element is\nused with relative position styling on the `div` and absolute position styling on the start and end\nicons. `InputGroup.InnerStart` and `InputGroup.InnerEnd` are used to position elements at the start\nand end of the input. \"start\" and \"end\" are used instead of \"left\" and \"right\" to match\n[CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties)\nand will be semantically correct in left-to-right and right-to-left languages.\n\n`InputGroup.InnerStart` and `InputGroup.InnerEnd` subcomponents can handle any child elements, but\nare built for icons. The default width is `40px`, which is perfect for icons. If you need to use\nsomething else, be sure to set the `width` property of `InputGroup.InnerStart` or\n`InputGroup.InnerEnd` to match the intended width of the element. Do not use the `cs` prop or any\nmethod to change width. The `width` prop is used to correctly position other inner elements.\n\n```tsx\nimport React from 'react';\n\nimport {mailIcon} from '@workday/canvas-system-icons-web';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {InputGroup} from '@workday/canvas-kit-react/text-input';\nimport {SystemIcon} from '@workday/canvas-kit-react/icon';\n\nexport const Icons = () => {\n return (\n \n Email\n \n \n \n \n );\n};\n\n// create a prop forwarding component for FormField to forward to\nconst InputGroupFormFieldForwarder = (props: {}) => {\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n```\n\n### Error States\n\nSet the `error` prop of the wrapping Form Field to `FormField.ErrorType.Alert` or\n`FormField.ErrorType.Error` to set the Text Input to the Alert or Error state, respectively. You\nwill also need to set the `hintId` and `hintText` props on the Form Field to meet accessibility\nstandards.\n\nThe `error` prop may be applied directly to the Text Input with a value of\n`TextInput.ErrorType.Alert` or `TextInput.ErrorType.Error` if Form Field is not being used.\n\n#### Alert\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Alert = () => {\n const [value, setValue] = React.useState('invalid@email');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n Please enter a valid email.\n \n \n );\n};\n```\n\n#### Error\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextInput} from '@workday/canvas-kit-react/text-input';\n\nexport const Error = () => {\n const [value, setValue] = React.useState('invalid@email');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Email\n \n \n Please enter a valid email.\n \n \n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" + }, + "toast": { + "title": "Components/Popups/Toast", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-popups-toast--docs", + "mdxPath": "modules/react/toast/stories/toast.mdx", + "mdxProse": "# Canvas Kit Toast\n\n`Toast` is a [compound component](/getting-started/for-developers/resources/compound-components/)\nthat contains updates or messages about the status of an application's process.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\n`Toast` includes a container `Toast` component and the following subcomponents: `Toast.Body`,\n`Toast.CloseIcon`, `Toast.Icon`, `Toast.Message`, and `Toast.Link`.\n\n`Toast` supports different modes through the `mode` prop: `status`, `alert`, and `dialog`. Each mode\nconveys a different purpose of the message and assigns the necessary ARIA attributes to support that\npurpose and provide an accessible experience.\n\nBy default, `mode` is set to `status`, which indicates the message contains advisory information\nsuch as a successful action.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n return (\n \n \n \n Your workbook was successfully processed.\n \n \n );\n};\n```\n\nA `status` `Toast` will wrap the message within a `polite` ARIA live region with a `role` of\n`status`.\n\nHere's a more complete example with a button which triggers a dismissable `Toast`. The `Toast` is\npositioned using `Popper`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {Popper} from '@workday/canvas-kit-react/popup';\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\n\nexport const WithPopper = () => {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef(null);\n\n const handleClose = () => {\n setOpen(false);\n };\n\n const handleOpen = () => {\n setOpen(true);\n };\n\n return (\n
\n Show Toast\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n
\n );\n};\n```\n\n### Alert\n\nSet the `mode` prop to `alert` to convey urgent and important information such as an error.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {exclamationCircleIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const ToastAlert = () => (\n \n \n \n There was an error with your workbook.\n \n \n);\n```\n\nAn `alert` `Toast` will wrap the message within an `assertive` ARIA live region with a `role` of\n`alert`.\n\n### Dialog\n\nSet the `mode` prop to `dialog` to convey the presence of a follow-up action. If you use this\n`mode`, you need to add an `aria-label`. This `aria-label` should be additional information for the\n`Toast` such as `notification`. The `Toast` will also add an `aria-describedby` that links the\n`Toast` to `Toast.Message` so that it is read out to screen readers. The `aria-label` should be\ndifferent that the contents of the `Toast` so there is no duplication being read out by screen\nreaders.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const ToastDialog = () => (\n \n \n \n Your workbook was successfully processed.\n Custom Link\n \n \n);\n```\n\nScreen readers will read the `Toast` out in the following order: \"Notification: Your workbook was\nsuccessfully processed.\"\n\n> **Note**: You must use the `Toast.Message` subcomponent when the `mode` is `dialog` so that `id`\n> is tied to the message for accessibility. You can also pass in a `model` with `useToastModel` to\n> provide a specific `id` for the `Toast.Message`\n\n```tsx\nexport const CustomIDToast = () => {\n const model = useToastModel({\n id: '12df5',\n mode: 'dialog',\n });\n return (\n \n \n \n Your workbook was successfully processed.\n View More Details\n \n \n );\n};\n```\n\n`Toast.CloseIcon` may be included within a `dialog` `Toast` to create a `Toast` with both an action\nlink and a close button.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const WithActionLinkAndCloseIcon = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n Your workbook was successfully\n Custom Link\n \n \n \n );\n};\n```\n\n### Right-to-Left (RTL)\n\n`Toast` supports right-to-left languages when specified in the `CanvasProvider` `theme`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {CanvasProvider, ContentDirection} from '@workday/canvas-kit-react/common';\n\nexport const RTL = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n );\n};\n```\n\n## Component API\n\n" + }, + "textarea": { + "title": "Components/Inputs/TextArea", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-textarea--docs", + "mdxPath": "modules/react/text-area/stories/TextArea.mdx", + "mdxProse": "# Canvas Kit Text Area\n\nText Areas allow users to enter and edit multiple lines of text.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-area)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Area should be used in tandem with [Form Field](/components/inputs/form-field/) to meet\naccessibility standards.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Area to prevent users from interacting with it.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Input to display a sample of its expected format or value\nbefore a value has been provided.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Ref Forwarding\n\nText Area supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `\n );\n};\n```\n\n### Disabled\n\nUse `TextArea.Field`'s `disabled` prop to prevent users from interacting with the field.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Placeholder\n\nUse `TextArea.Field`'s `placeholder` prop to display a sample of its expected format or value before\na value has been provided.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Required\n\nUse `TextArea.Field`'s `isRequired` prop (or use with the `useTextAreaModel` hook) to indicate that\nthe field is required. This will also add a red asterisk to `TextArea.Label`.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Ref Forwarding\n\n`TextArea` supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `\n Focus Text Area\n \n );\n};\n```\n\n### Resize Constraints\n\nUse the `resize` css attribute to restrict resizing of it to certain dimensions.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {styled} from '@workday/canvas-kit-react/common';\n\nconst StyledField = styled(TextArea.Field)({\n resize: 'vertical',\n});\n\nexport const ResizeConstraints = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Grow\n\nThere are lots of ways to accomplish this. The `TextArea.Field` extends from Box so it is easy to\nextend full width, e.g. setting width prop to 100%, or you can set the `alignItems` prop to\n`stretch` on `TextArea`, etc.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Label Position\n\nUse the `orientation` property to set `TextArea.Label`'s position. You can override the default\nspacing using the `gap` prop. Below are examples of both positions:\n\n#### Horizontal\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const LabelPositionHorizontal = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n#### Vertical\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const LabelPositionVertical = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Visually Hiding The Label\n\nIf your label is just for screen reader users you can use the `accessibleHide` utility class from\n`@workday/canvas-kit-react/common`. You will likely want to set the `gap` prop on `TextArea` to\n`zero`.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {accessibleHide, styled} from '@workday/canvas-kit-react/common';\n\nconst StyledTextAreaLabel = styled(TextArea.Label)({\n ...accessibleHide,\n});\n\nexport const HiddenLabel = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Error States\n\nUse the `hasError` property from `useTextAreaModel` to set the `TextArea` to the Error state. If you\nhave an accompanying hint you can use the `TextArea.Hint` subcomponent.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {space} from '@workday/canvas-kit-react/tokens';\n\nexport const Error = () => {\n const [value, setValue] = React.useState('four');\n const [hint, setHint] = React.useState('');\n const [hasError, setHasError] = React.useState(false);\n\n const validateInput = (value: string) => {\n const stringLength = value.length;\n if (stringLength !== 3) {\n setHasError(true);\n const hintStart = 'Word length must be';\n setHint(stringLength < 3 ? `${hintStart} greater than 2` : `${hintStart} less than 4`);\n } else {\n setHasError(false);\n setHint('');\n }\n };\n\n React.useEffect(() => {\n validateInput(value);\n // Only run on load\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const handleChange = (event: React.ChangeEvent) => {\n validateInput(event.target.value);\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Other Visual States\n\nUse the `useThemedRing` hook to change the visual state of the `\n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" + }, + "select": { + "title": "Preview/Select", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-select--docs", + "mdxPath": "modules/preview-react/select/stories/Select.mdx", + "mdxProse": "# Canvas Kit Select\n\n## Top Label\n\n### Default\n\n### Default with Custom Options Data\n\n### Default with Simple Options Data\n\n### Scrollable\n\n### Disabled\n\n### Alert\n\n### Error\n\n### Grow\n\n## Left Label\n\n### Default\n\n### Default with Custom Options Data\n\n### Default with Simple Options Data\n\n### Scrollable\n\n### Disabled\n\n### Alert\n\n### Error\n\n### Grow\n\n" + }, + "segmented-control": { + "title": "Preview/Segmented Control", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-segmented-control--docs", + "mdxPath": "modules/preview-react/segmented-control/stories/SegmentedControl.mdx", + "mdxProse": "# Canvas Kit Segmented Control\n\nSegmented Control is a\n[compound component](/getting-started/for-developers/resources/compound-components/) that represents\na linear group of multiple buttons allowing the selection of a specific value.\n\n[> Workday Design Reference](https://design.workday.com/components/buttons/segmented-control)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n### Basic Example\n\n`SegmentedControl` includes a container `SegmentedControl` component and the following\nsubcomponents: `SegmentedControl.List` and `SegmentedControl.Item`.\n\nThe example below contains a `SegmentedControl` with four icon-only buttons. Each button is rendered\nusing a `SegmentedControl.Item` and is paired with a tooltip describing the button's function. Only\none button can be active at a time.\n\n```tsx\nimport React from 'react';\n\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n const [viewType, setViewType] = React.useState('table');\n\n return (\n <>\n setViewType(data.id)}>\n \n \n \n \n \n \n \n \n Selected: {viewType}\n \n \n );\n};\n```\n\nNote that you must provide `SegmentedControl.List` with an `aria-label` prop for accessibility\nreasons.\n\nWe **strongly** discourage including more than four buttons in a single `SegmentedControl`.\n\n### Variations\n\n`SegmentedControl` supports three variations based on whether or not its `SegmentedControl.Item`\ncomponents have an `icon` prop and/or text content: icon-only, text-only, and text-and-icon.\n\nAll `SegmentedControl.Item` components within a given `SegmentedControl` must be of the same\nvariation.\n\n#### Icon-Only\n\nTo render an icon-only `SegmentedControl`, apply the `icon` prop to `SegmentedControl.Item` and do\nnot provide it with text content. Refer to the [basic example](#basic-example) above for an instance\nof an icon-only `SegmentedControl`.\n\nThe icon-only variation is the only variation which supports a vertical orientation in addition to\nthe default horizontal orientation. Set the `orientation` prop of `SegmentedControl` to `vertical`\nto configure the component to render vertically.\n\n```tsx\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Vertical = () => (\n \n \n \n \n \n \n \n \n);\n```\n\n#### Text-Only\n\nTo render a text-only `SegmentedControl`, omit the `icon` prop from `SegmentedControl.Item` and\nprovide it with text content.\n\n```tsx\nimport React from 'react';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const TextOnly = () => (\n \n \n Table\n List\n Diagram\n \n \n);\n```\n\n#### Text-and-Icon\n\nTo render a text-and-icon `SegmentedControl`, apply the `icon` prop to `SegmentedControl.Item` and\nprovide it with text content.\n\n```tsx\nimport React from 'react';\nimport {gridIcon, listViewIcon, pieChartIcon} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const TextAndIcon = () => (\n \n \n \n Table\n \n \n List\n \n \n Diagram\n \n \n \n);\n```\n\n### Sizes\n\n`SegmentedControl` accepts a `size` prop which supports the following values:\n\n- `small`\n- `medium` (Default)\n- `large`\n\n```tsx\nimport React from 'react';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {BodyText} from '@workday/canvas-kit-react/text';\n\nexport const Sizes = () => (\n <>\n \n \n Small\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n \n Medium\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n \n Large\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n);\n```\n\n### Disabled\n\nSet the `disabled` prop of `SegmentedControl` to disable the entire component including its buttons.\n\n```tsx\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Disabled = () => (\n \n \n \n \n \n \n \n \n);\n```\n\n### Right-to-Left (RTL)\n\n`SegmentedControl` supports right-to-left languages when specified in the `CanvasProvider` `theme`.\n\n```tsx\nimport {CanvasProvider, ContentDirection} from '@workday/canvas-kit-react/common';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const RTL = () => (\n \n \n \n \n שולחן\n \n \n רשימה\n \n \n פרטים\n \n \n תרשים\n \n \n \n \n);\n```\n\n### Dynamic Items\n\n`SegmentedControl` supports a\n[dynamic API](/getting-started/for-developers/resources/collection-api/#dynamic-items) where instead\nof statically providing the JSX for each `SegmentedControl.Item`, you pass an array of `items` in\nthe `model` state and provide a render function to display the items.\n\n```tsx\nimport React from 'react';\nimport {\n SegmentedControl,\n useSegmentedControlModel,\n} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Dynamic = () => {\n const [viewType, setViewType] = React.useState('table');\n\n const model = useSegmentedControlModel({\n items: [\n {id: 'table', icon: gridIcon, label: 'Table'},\n {id: 'list', icon: listViewIcon, label: 'List'},\n {id: 'detail', icon: listDetailIcon, label: 'Detail'},\n {id: 'diagram', icon: pieChartIcon, label: 'Diagram'},\n ],\n size: 'small',\n initialValue: viewType,\n onSelect: data => {\n console.log(`${data.id} is selected`);\n setViewType(data.id);\n },\n });\n\n return (\n \n \n {item => (\n \n {item.label}\n \n )}\n \n \n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" + }, + "pill": { + "title": "Preview/Pill", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-pill--docs", + "mdxPath": "modules/preview-react/pill/stories/Pill.mdx", + "mdxProse": "# Canvas Kit Pill\n\n`Pill`s are static or interactive indicators that allow users to input, filter, or label\ninformation.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n`Pill`s are used to visually label objects on a page for quick recognition. They’re offered as both\nstatic (read-only) and interactive elements. They allow users to filter a list or table, or label\ninformation to help with scanning and organization.\n\n### Basic Pills\n\nBy default a Pill is considered interactive, therefore it's default variant is `default`. All leading\nelements (icons or avatars) are intended to be descriptive, helping support the label. Do not rely\non the leading element to indicate the interaction behavior.\n\n#### Icon\n\nYou can render an icon inside the `Pill` with `Pill.Icon`. It will render a `plusIcon` by default,\nbut it can be customized by providing an icon to the `icon` prop. Because it uses `SystemIcon` under\nthe hood, you also have access to all `SystemIconProps`.\n\n#### Accessibility\n\nYou must provide an `aria-label` to the `Pill.Icon` for proper accessibility.\n\n```tsx\nimport React from 'react';\n\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\n\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n display: 'flex',\n gap: system.space.x2,\n});\n\nexport const Basic = () => {\n const [text, setText] = React.useState('');\n return (\n
\n
\n setText('The first pill is clicked!')}>\n \n Regina Skeltor\n \n \n \n Regina Skeltor\n \n
\n {text}\n
\n );\n};\n```\n\n#### Avatar\n\nYou can render an avatar image inside the `Pill` with `Pill.Avatar`. It should appear before the\n`Pill` text. Because it uses `Avatar` under the hood, you also have access to all `AvatarProps`.\n\n```tsx\nimport React from 'react';\n\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\n// @ts-ignore: Cannot find module error\nimport testAvatar from './test-avatar.png';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n display: 'flex',\n gap: system.space.x2,\n});\n\nexport const WithAvatar = () => {\n const [text, setText] = React.useState('');\n return (\n
\n
\n setText('The first pill is clicked!')}>\n \n Regina Skeltor\n \n \n \n Regina Skeltor\n \n
\n {text}\n
\n );\n};\n```\n\n#### Count\n\nThe count appears after the label. It is usually associated with the label. If you have a category,\nthe count will directly correlate to that category.\n\n```tsx\nimport React from 'react';\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n display: 'flex',\n gap: system.space.x2,\n});\n\nexport const WithCount = () => {\n const [text, setText] = React.useState('');\n return (\n
\n
\n setText('The first pill is clicked!')}>\n Shoes\n 30\n \n \n Shoes\n 30\n \n
\n {text}\n
\n );\n};\n```\n\n### Read Only\n\nThe `readOnly` variant is a non-interactive element that is used to display information. \n\nYou can define a read only `Pill` by providing a `variant='readOnly'` prop.\n\n```tsx\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst flexStyles = createStyles({\n display: 'flex',\n gap: system.space.x2,\n});\n\nexport const WithReadOnly = () => (\n
\n Read-only\n \n Read-only but with super long text in case you want to read a paragraph in a Pill which we\n don't recommend\n \n
\n);\n```\n\n### Removable Pills\n\nRemovable `Pill`s display an `X` icon after the label. They have a smaller, more specific focus\nstate and click target to be more intentional about their actions and to avoid unintended removal.\n\nYou can define a removable `Pill` by providing a `variant='removable'` prop.\n\n```tsx\n\n Pink Shirts\n console.warn('clicked')} />\n\n```\n\nIn this case, we use a `Pill.IconButton` because the `X` becomes the focusable and clickable\nelement.\n\nThe default icon for `Pill.IconButton` is `xSmallIcon` but this can also be overwritten by passing\nan `icon` prop to `Pill.IconButton`\n\n```tsx\nimport React from 'react';\n\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\n// @ts-ignore: Cannot find module error\nimport testAvatar from './test-avatar.png';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {system} from '@workday/canvas-tokens-web';\nimport {createStyles} from '@workday/canvas-kit-styling';\n\nconst flexStyles = createStyles({\n display: 'flex',\n gap: system.space.x2,\n});\n\nexport const WithRemovable = () => {\n const [text, setText] = React.useState('');\n return (\n
\n
\n \n Pink Shirts\n setText('The first pill is clicked!')}\n />\n \n \n \n Carolyn Grimaldi\n setText('The second pill is clicked!')}\n />\n \n \n This is a category that should not exist because it is too long\n \n \n
\n {text}\n
\n );\n};\n```\n\n### List of Pills\n\n`Pill`s can often represent multiple pieces of information such as a filtered list of categories or\nskills.\n\nIn order to achieve this, use our `Flex` component to wrap each `Pill` and space them out\naccordingly.\n\n```tsx\nimport React from 'react';\n\nimport {Pill} from '@workday/canvas-kit-preview-react/pill';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst data = [\n 'Shoes',\n 'Pants',\n 'Dress Shoes',\n 'Color',\n 'Accessories',\n 'Luxury',\n 'Casual',\n 'Hats',\n 'Beanies',\n 'Glasses',\n 'Jewelry',\n];\n\nconst flexWrapStyles = createStyles({\n display: 'flex',\n flexWrap: 'wrap',\n gap: system.space.x2,\n});\n\nexport const WithList = () => {\n const [items, setItems] = React.useState(data);\n\n return (\n
\n {items.map((cat, index) => {\n return (\n \n {cat}\n setItems(items.filter(i => i !== cat))}\n />\n \n );\n })}\n
\n );\n};\n```\n\n### Custom Styles\n\n`Pill` supports custom styling via the `cs` prop. For more information, check our\n[\"How To Customize Styles\"](https://workday.github.io/canvas-kit/?path=/docs/styling-how-to-customize-styles--docs)\nor view the example below.\n\n```tsx\nimport {Pill, pillCountStencil, pillStencil} from '@workday/canvas-kit-preview-react/pill';\n\nimport {createStencil} from '@workday/canvas-kit-styling';\nimport {base} from '@workday/canvas-tokens-web';\nimport {systemIconStencil} from '@workday/canvas-kit-react/icon';\n\nconst customPillStencil = createStencil({\n base: {\n [pillStencil.vars.background]: base.berrySmoothie300,\n [pillStencil.vars.border]: base.berrySmoothie500,\n [pillStencil.vars.label]: base.frenchVanilla100,\n [systemIconStencil.vars.color]: base.frenchVanilla100,\n [pillCountStencil.vars.backgroundColor]: base.berrySmoothie400,\n\n '&:hover, &.hover': {\n [pillStencil.vars.background]: base.berrySmoothie400,\n [pillStencil.vars.label]: base.frenchVanilla100,\n [pillCountStencil.vars.backgroundColor]: base.berrySmoothie500,\n [systemIconStencil.vars.color]: base.frenchVanilla100,\n },\n '&:active, &.active': {\n [pillStencil.vars.background]: base.berrySmoothie400,\n [pillStencil.vars.label]: base.frenchVanilla100,\n [systemIconStencil.vars.color]: base.frenchVanilla100,\n [pillCountStencil.vars.backgroundColor]: base.berrySmoothie500,\n },\n '&:disabled, &.disabled': {\n [pillStencil.vars.background]: base.berrySmoothie400,\n [pillStencil.vars.label]: base.frenchVanilla100,\n [systemIconStencil.vars.color]: base.frenchVanilla100,\n },\n },\n});\n\nexport const CustomStyles = () => {\n return (\n
\n \n \n Custom Pill Color\n 10\n \n
\n );\n};\n```\n\n## Component API\n\n" + }, + "menu": { + "title": "Preview/Menu", + "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-menu--docs", + "mdxPath": "modules/preview-react/menu/stories/Menu.mdx", + "mdxProse": "# Canvas Kit Menu\n\n\n Deprecated\n\n\n`Menu` in Preview been deprecated and will be removed in a future major version. Please use\n[Menu in Main](https://workday.github.io/canvas-kit/?path=/docs/components-popups-menu--basic)\ninstead.\n\n`DeprecatedMenu` displays a list of options when triggered by an action or UI element like an icon\nor button.\n\n[> Workday Design Reference](https://design.workday.com/components/popups/menus)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n### Basic Example\n\n`DeprecatedMenu` is typically triggered by an action such as pressing a button. Here's an example of\nhow you might implement that behavior using a [`Popup`](/components/popups/popup/).\n\n```tsx\nimport React from 'react';\nimport {DeprecatedMenu, DeprecatedMenuItem} from '@workday/canvas-kit-preview-react/menu';\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {\n Popup,\n usePopupModel,\n useAlwaysCloseOnOutsideClick,\n useReturnFocus,\n useCloseOnEscape,\n} from '@workday/canvas-kit-react/popup';\n\nconst menuId = 'basic-menu';\n\nexport const Basic = () => {\n const model = usePopupModel();\n\n useAlwaysCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useReturnFocus(model);\n\n const isOpen = model.state.visibility !== 'hidden';\n\n const handleButtonKeyDown = (event: React.KeyboardEvent) => {\n let isShortcut = false;\n if (event.key === `Spacebar` || event.key === ` ` || event.key === `Enter`) {\n isShortcut = true;\n if (isOpen) {\n model.events.hide();\n } else {\n model.events.show();\n }\n } else if (event.key === `ArrowDown` || event.key === `ArrowUp`) {\n isShortcut = true;\n model.events.show();\n }\n\n if (isShortcut) {\n // Prevent ArrowDown and ArrowUp keys from scrolling the entire page\n event.preventDefault();\n }\n };\n\n return (\n \n \n Open Menu\n \n \n {/*\n isOpen must be set for focus to be properly assigned to the DeprecatedMenu;\n onClose must be set in order to the DeprecatedMenu to close properly after\n selecting a DeprecatedMenuItem\n */}\n \n First Item\n \n Second Item (with a really really really long label)\n \n Third Item (disabled)\n \n Fourth Item (with markup)\n \n Fifth Item (with divider)\n \n \n \n );\n};\n```\n\n`DeprecatedMenu` will automatically assign focus to itself when it appears provided you set the\n`isOpen` prop correctly, so you do **not** need to use the `useInitialFocus` `Popup` hook. You\n**will**, however, need to use `useReturnFocus` to return focus back to the triggering button after\nclosing the `DeprecatedMenu`.\n\n`DeprecatedMenu` follows the\n[Actions Menu pattern](https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-actions-active-descendant.html)\nusing `aria-activedescendant`. Below is table of supported keyboard shortcuts and associated\nactions.\n\n| Key | Action |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `Enter` or `Space` | Activates the menu item and then closes the menu |\n| `Escape` | Closes the menu |\n| `Up Arrow` | Moves focus to the previous menu item – if focus is on first menu item, it moves focus to the last menu item |\n| `Down Arrow` | Moves focus to the next menu item – if focus is on last menu item, it moves focus to the first menu item |\n| `Home` | Moves focus to the first menu item |\n| `End` | Moves focus to the last menu item |\n| `A-Z / a-z` | Moves focus to the next menu item with a label that starts with the typed character if such an menu item exists – otherwise, focus does not move |\n\nNote that although `DeprecatedMenu` includes support for keyboard shortcuts for the menu itself,\nyou'll need to add your own keyboard handling and aria attributes to the triggering button.\n\n### Context Menu\n\n```tsx\nimport * as React from 'react';\n\nimport {createComponent, useForkRef} from '@workday/canvas-kit-react/common';\nimport {DeprecatedMenu, DeprecatedMenuItem} from '@workday/canvas-kit-preview-react/menu';\nimport {\n Popup,\n PopupModelContext,\n usePopupModel,\n useAlwaysCloseOnOutsideClick,\n useCloseOnEscape,\n useTransferOnFullscreenExit,\n} from '@workday/canvas-kit-react/popup';\n\nconst ContextMenuTarget = createComponent('div')({\n displayName: 'Popup.ContextMenuTarget',\n Component: ({children, ...elemProps}, ref, Element) => {\n const model = React.useContext(PopupModelContext);\n const elementRef = useForkRef(ref, model.state.targetRef as any);\n\n const onContextMenu = (event: React.MouseEvent) => {\n if (model.state.visibility === 'visible') {\n model.events.hide(event);\n } else if (model.state.visibility === 'hidden') {\n model.events.show(event);\n }\n\n // Prevent the default context menu from showing to avoid double menus\n event.preventDefault();\n };\n\n return (\n \n {children}\n \n );\n },\n});\n\nexport const ContextMenu = () => {\n const model = usePopupModel();\n\n useAlwaysCloseOnOutsideClick(model);\n useCloseOnEscape(model);\n useTransferOnFullscreenExit(model);\n\n return (\n \n \n Right click on this text (Context menu target)\n \n \n \n Back\n Forward\n Reload\n Bookmark Page\n Save Page As...\n Select All\n Take Screenshot\n View Page Source\n Inspect Accessibility Properties\n Inspect\n \n \n \n );\n};\n```\n\n### Custom Menu Item\n\n`DeprecatedMenu` renders a styled `
\n }\n >\n Hover Me\n \n \n );\n};\n```\n\n### Delayed Tooltip\n\nThe default delay for showing and hiding a tooltip are 300ms and 100ms, respectively. You can\ncontrol the length of the delay by providing custom `showDelay` and `hideDelay` in ms.\n\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const DelayedTooltip = () => {\n return (\n \n \n \n Tooltip appears after 2 seconds and disappears after 1 second\n \n \n \n );\n};\n```\n\n### Placements\n\nThe tooltip allows for a `placement` configuration. The tooltip uses\n[PopperJS](https://popper.js.org/) to position tooltips, so any valid PopperJS placement is valid\nfor tooltips.\n\n```tsx\nimport React from 'react';\nimport {Tooltip} from '@workday/canvas-kit-react/tooltip';\nimport {Card} from '@workday/canvas-kit-react/card';\nimport {Placement} from '@workday/canvas-kit-react/popup';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst placementCardStyles = createStyles({\n boxShadow: system.depth[2],\n display: 'flex',\n width: 100,\n height: 100,\n justifyContent: 'space-around',\n alignItems: 'center',\n padding: system.space.x1,\n});\n\nexport const Placements = () => {\n const placementStyles = {\n display: 'flex',\n justifyContent: 'space-around',\n };\n\n const createPlacement = (placement: string, index) => {\n return (\n \n \n {placement}\n \n \n );\n };\n\n return (\n \n
\n
\n {['top-start', 'top', 'top-end'].map(createPlacement)}\n
\n
\n
\n {['left-start', 'left', 'left-end'].map(createPlacement)}\n
\n
\n
\n {['right-start', 'right', 'right-end'].map(createPlacement)}\n
\n
\n
\n {['bottom-start', 'bottom', 'bottom-end'].map(createPlacement)}\n
\n
\n
\n );\n};\n```\n\n### Tooltips on overflowing content\n\nThe `OverflowTooltip` component can be applied to any element that has some type of overflow\napplied, or has a child element that has overflow applied. The most common and widely supported type\nof truncation is the ellipsis.\n\n```css\noverflow: hidden;\ntext-overflow: ellipsis;\nwhite-space: nowrap;\n```\n\n**Note**: Text truncation should be avoided if possible. A user should not have to activate a\ntooltip to access important content. If user-generated content is being truncated, the following\nsituation might occur which is a bad user experience. Consider the following list:\n\n- Home Site A\n- Home Site B\n- Home Site C\n\nIf the list items get truncated via an ellipsis, this is what the user could see:\n\n- Home Sit...\n- Home Sit...\n- Home Sit...\n\nHere are suggestions to try to avoid truncation:\n\n- Allow content to wrap instead\n- Limit character count in admin interfaces if possible to avoid need for truncation\n- Avoid fixed container sizes if possible to allow content to flow naturally\n\nIf truncation is required, here are a few guidelines to insure minimal impact on users:\n\n- Only truncate text of elements that naturally receive focus.\n - Keyboard users can only activate tooltips with focus. Adding `tabindex=0` can give focus to\n non-focusable elements, but increase the amount of tab stops for keyboard users.\n- Provide the full content elsewhere in the UI\n\nCanvas Kit Buttons have this style applied to the text inside them. `OverflowTooltip` in combination\nwith a max-width can show a tooltip only when overflow is detected:\n\n```tsx\nimport React from 'react';\n\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\nimport {OverflowTooltip} from '@workday/canvas-kit-react/tooltip';\nimport {space} from '@workday/canvas-kit-react/tokens';\nimport {resetIcon} from '@workday/canvas-system-icons-web';\n\nconst CustomContent = ({...elemProps}) => (\n \n Super Mega Ultra Long Content With Max Width Custom\n \n);\n\nexport const Ellipsis = () => {\n return (\n \n \n Short Content\n {' '}\n \n \n Super Mega Ultra Long Content With Max Width On The Button\n \n \n \n \n Super Mega Ultra Long Content With Max Width On The Button with Icon\n \n \n \n \n Super Mega Ultra Long Content With Max Width\n \n \n \n \n \n \n );\n};\n```\n\n### Line Clamp\n\nThe `OverflowTooltip` can support various types of overflow. The component will first look for\n`text-overflow: ellipsis` and `-webkit-line-clamp`, but will fall back to\n`overflow: auto | scroll | clip | hidden`. These properties will be used to determine which\n`element` is experiencing an overflow. Overflow detection is as follows where `element` is\ndetermined by the above style properties:\n\n```js\nelement.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;\n```\n\nHere's an example using the `-webkit-line-clamp` property (multi-line ellipsis which works in all\nbrowsers):\n\n```tsx\nimport React from 'react';\n\nimport {OverflowTooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const LineClamp = () => {\n return (\n \n \n \n );\n};\n```\n\nOther truncation techniques should be supported as well, even JavaScript ones as long as overflow is\ntriggered somehow and detectable differences in scroll size and client size.\n\n### The UseTooltip Hook\n\nThe `Tooltip` component is a combination of the `TooltipContainer` (a styled element), `Popper`\n(which uses PopperJS and the popup stack), the `useTooltip` hook and some behavior. If custom\nbehavior is required, these sub-components can be composed in a custom container element. This\nexample uses those parts directly while being functionally equivalent to the original basic example.\n\n```tsx\nimport React from 'react';\n\nimport {Popper} from '@workday/canvas-kit-react/popup';\nimport {xIcon} from '@workday/canvas-system-icons-web';\nimport {TertiaryButton} from '@workday/canvas-kit-react/button';\nimport {TooltipContainer, useTooltip} from '@workday/canvas-kit-react/tooltip';\n\nexport const UseTooltip = () => {\n const {targetProps, popperProps, tooltipProps} = useTooltip();\n\n return (\n <>\n \n \n Close\n \n \n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" }, "toast": { "title": "Components/Popups/Toast", "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-popups-toast--docs", "mdxPath": "modules/react/toast/stories/toast.mdx", - "mdxProse": "# Canvas Kit Toast\n\n`Toast` is a [compound component](/getting-started/for-developers/resources/compound-components/)\nthat contains updates or messages about the status of an application's process.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\n`Toast` includes a container `Toast` component and the following subcomponents: `Toast.Body`,\n`Toast.CloseIcon`, `Toast.Icon`, `Toast.Message`, and `Toast.Link`.\n\n`Toast` supports different modes through the `mode` prop: `status`, `alert`, and `dialog`. Each mode\nconveys a different purpose of the message and assigns the necessary ARIA attributes to support that\npurpose and provide an accessible experience.\n\nBy default, `mode` is set to `status`, which indicates the message contains advisory information\nsuch as a successful action.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n return (\n \n \n \n Your workbook was successfully processed.\n \n \n );\n};\n```\n\nA `status` `Toast` will wrap the message within a `polite` ARIA live region with a `role` of\n`status`.\n\nHere's a more complete example with a button which triggers a dismissable `Toast`. The `Toast` is\npositioned using `Popper`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {Popper} from '@workday/canvas-kit-react/popup';\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\n\nexport const WithPopper = () => {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef(null);\n\n const handleClose = () => {\n setOpen(false);\n };\n\n const handleOpen = () => {\n setOpen(true);\n };\n\n return (\n
\n Show Toast\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n
\n );\n};\n```\n\n### Alert\n\nSet the `mode` prop to `alert` to convey urgent and important information such as an error.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {exclamationCircleIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const ToastAlert = () => (\n \n \n \n There was an error with your workbook.\n \n \n);\n```\n\nAn `alert` `Toast` will wrap the message within an `assertive` ARIA live region with a `role` of\n`alert`.\n\n### Dialog\n\nSet the `mode` prop to `dialog` to convey the presence of a follow-up action. If you use this\n`mode`, you need to add an `aria-label`. This `aria-label` should be additional information for the\n`Toast` such as `notification`. The `Toast` will also add an `aria-describedby` that links the\n`Toast` to `Toast.Message` so that it is read out to screen readers. The `aria-label` should be\ndifferent that the contents of the `Toast` so there is no duplication being read out by screen\nreaders.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const ToastDialog = () => (\n \n \n \n Your workbook was successfully processed.\n Custom Link\n \n \n);\n```\n\nScreen readers will read the `Toast` out in the following order: \"Notification: Your workbook was\nsuccessfully processed.\"\n\n> **Note**: You must use the `Toast.Message` subcomponent when the `mode` is `dialog` so that `id`\n> is tied to the message for accessibility. You can also pass in a `model` with `useToastModel` to\n> provide a specific `id` for the `Toast.Message`\n\n```tsx\nexport const CustomIDToast = () => {\n const model = useToastModel({\n id: '12df5',\n mode: 'dialog',\n });\n return (\n \n \n \n Your workbook was successfully processed.\n View More Details\n \n \n );\n};\n```\n\n`Toast.CloseIcon` may be included within a `dialog` `Toast` to create a `Toast` with both an action\nlink and a close button.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const WithActionLinkAndCloseIcon = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n Your workbook was successfully\n Custom Link\n \n \n \n );\n};\n```\n\n### Right-to-Left (RTL)\n\n`Toast` supports right-to-left languages when specified in the `CanvasProvider` `theme`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {CanvasProvider, ContentDirection} from '@workday/canvas-kit-react/common';\n\nexport const RTL = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n );\n};\n```\n\n## Component API\n\n" + "mdxProse": "# Canvas Kit Toast\n\n`Toast` is a [compound component](/get-started/for-developers/documentation/compound-components/)\nthat contains updates or messages about the status of an application's process.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\n`Toast` includes a container `Toast` component and the following subcomponents: `Toast.Body`,\n`Toast.CloseIcon`, `Toast.Icon`, `Toast.Message`, and `Toast.Link`.\n\n`Toast` supports different modes through the `mode` prop: `status`, `alert`, and `dialog`. Each mode\nconveys a different purpose of the message and assigns the necessary ARIA attributes to support that\npurpose and provide an accessible experience.\n\nBy default, `mode` is set to `status`, which indicates the message contains advisory information\nsuch as a successful action.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n return (\n \n \n \n Your workbook was successfully processed.\n \n \n );\n};\n```\n\nA `status` `Toast` will wrap the message within a `polite` ARIA live region with a `role` of\n`status`.\n\nHere's a more complete example with a button which triggers a dismissable `Toast`. The `Toast` is\npositioned using `Popper`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {Popper} from '@workday/canvas-kit-react/popup';\nimport {SecondaryButton} from '@workday/canvas-kit-react/button';\n\nexport const WithPopper = () => {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef(null);\n\n const handleClose = () => {\n setOpen(false);\n };\n\n const handleOpen = () => {\n setOpen(true);\n };\n\n return (\n
\n Show Toast\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n
\n );\n};\n```\n\n### Alert\n\nSet the `mode` prop to `alert` to convey urgent and important information such as an error.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {exclamationCircleIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const ToastAlert = () => (\n \n \n \n There was an error with your workbook.\n \n \n);\n```\n\nAn `alert` `Toast` will wrap the message within an `assertive` ARIA live region with a `role` of\n`alert`.\n\n### Dialog\n\nSet the `mode` prop to `dialog` to convey the presence of a follow-up action. If you use this\n`mode`, you need to add an `aria-label`. This `aria-label` should be additional information for the\n`Toast` such as `notification`. The `Toast` will also add an `aria-describedby` that links the\n`Toast` to `Toast.Message` so that it is read out to screen readers. The `aria-label` should be\ndifferent that the contents of the `Toast` so there is no duplication being read out by screen\nreaders.\n\n```tsx\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\n\nexport const ToastDialog = () => (\n \n \n \n Your workbook was successfully processed.\n Custom Link\n \n \n);\n```\n\nScreen readers will read the `Toast` out in the following order: \"Notification: Your workbook was\nsuccessfully processed.\"\n\n> **Note**: You must use the `Toast.Message` subcomponent when the `mode` is `dialog` so that `id`\n> is tied to the message for accessibility. You can also pass in a `model` with `useToastModel` to\n> provide a specific `id` for the `Toast.Message`\n\n```tsx\nexport const CustomIDToast = () => {\n const model = useToastModel({\n id: '12df5',\n mode: 'dialog',\n });\n return (\n \n \n \n Your workbook was successfully processed.\n View More Details\n \n \n );\n};\n```\n\n`Toast.CloseIcon` may be included within a `dialog` `Toast` to create a `Toast` with both an action\nlink and a close button.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\n\nexport const WithActionLinkAndCloseIcon = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n Your workbook was successfully\n Custom Link\n \n \n \n );\n};\n```\n\n### Right-to-Left (RTL)\n\n`Toast` supports right-to-left languages when specified in the `CanvasProvider` `theme`.\n\n```tsx\nimport React from 'react';\n\nimport {Toast} from '@workday/canvas-kit-react/toast';\nimport {checkIcon} from '@workday/canvas-system-icons-web';\nimport {colors} from '@workday/canvas-kit-react/tokens';\nimport {CanvasProvider} from '@workday/canvas-kit-react/common';\n\nexport const RTL = () => {\n const handleClose = () => console.log('close button clicked');\n\n return (\n \n \n \n \n Your workbook was successfully processed.\n \n \n \n \n );\n};\n```\n\n## Component API\n\n" }, - "textarea": { - "title": "Components/Inputs/TextArea", - "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/components-inputs-textarea--docs", - "mdxPath": "modules/react/text-area/stories/TextArea.mdx", - "mdxProse": "# Canvas Kit Text Area\n\nText Areas allow users to enter and edit multiple lines of text.\n\n[> Workday Design Reference](https://design.workday.com/components/inputs/text-area)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-react\n```\n\n## Usage\n\n### Basic Example\n\nText Area should be used in tandem with [Form Field](/components/inputs/form-field/) to meet\naccessibility standards.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Basic = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Disabled\n\nSet the `disabled` prop of the Text Area to prevent users from interacting with it.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Placeholder\n\nSet the `placeholder` prop of the Text Input to display a sample of its expected format or value\nbefore a value has been provided.\n\n```tsx\nimport React from 'react';\nimport {FormField} from '@workday/canvas-kit-react/form-field';\nimport {TextArea} from '@workday/canvas-kit-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n Leave a Review\n \n \n \n \n );\n};\n```\n\n### Ref Forwarding\n\nText Area supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `\n );\n};\n```\n\n### Disabled\n\nUse `TextArea.Field`'s `disabled` prop to prevent users from interacting with the field.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Disabled = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Placeholder\n\nUse `TextArea.Field`'s `placeholder` prop to display a sample of its expected format or value before\na value has been provided.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Placeholder = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Required\n\nUse `TextArea.Field`'s `isRequired` prop (or use with the `useTextAreaModel` hook) to indicate that\nthe field is required. This will also add a red asterisk to `TextArea.Label`.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Required = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Ref Forwarding\n\n`TextArea` supports [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). It will forward\n`ref` to its underlying `\n Focus Text Area\n \n );\n};\n```\n\n### Resize Constraints\n\nUse the `resize` css attribute to restrict resizing of it to certain dimensions.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {styled} from '@workday/canvas-kit-react/common';\n\nconst StyledField = styled(TextArea.Field)({\n resize: 'vertical',\n});\n\nexport const ResizeConstraints = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Grow\n\nThere are lots of ways to accomplish this. The `TextArea.Field` extends from Box so it is easy to\nextend full width, e.g. setting width prop to 100%, or you can set the `alignItems` prop to\n`stretch` on `TextArea`, etc.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const Grow = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Label Position\n\nUse the `orientation` property to set `TextArea.Label`'s position. You can override the default\nspacing using the `gap` prop. Below are examples of both positions:\n\n#### Horizontal\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const LabelPositionHorizontal = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n#### Vertical\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\n\nexport const LabelPositionVertical = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Visually Hiding The Label\n\nIf your label is just for screen reader users you can use the `accessibleHide` utility class from\n`@workday/canvas-kit-react/common`. You will likely want to set the `gap` prop on `TextArea` to\n`zero`.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {accessibleHide, styled} from '@workday/canvas-kit-react/common';\n\nconst StyledTextAreaLabel = styled(TextArea.Label)({\n ...accessibleHide,\n});\n\nexport const HiddenLabel = () => {\n const [value, setValue] = React.useState('');\n\n const handleChange = (event: React.ChangeEvent) => {\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Error States\n\nUse the `hasError` property from `useTextAreaModel` to set the `TextArea` to the Error state. If you\nhave an accompanying hint you can use the `TextArea.Hint` subcomponent.\n\n```tsx\nimport React from 'react';\nimport {TextArea} from '@workday/canvas-kit-preview-react/text-area';\nimport {space} from '@workday/canvas-kit-react/tokens';\n\nexport const Error = () => {\n const [value, setValue] = React.useState('four');\n const [hint, setHint] = React.useState('');\n const [hasError, setHasError] = React.useState(false);\n\n const validateInput = (value: string) => {\n const stringLength = value.length;\n if (stringLength !== 3) {\n setHasError(true);\n const hintStart = 'Word length must be';\n setHint(stringLength < 3 ? `${hintStart} greater than 2` : `${hintStart} less than 4`);\n } else {\n setHasError(false);\n setHint('');\n }\n };\n\n React.useEffect(() => {\n validateInput(value);\n // Only run on load\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const handleChange = (event: React.ChangeEvent) => {\n validateInput(event.target.value);\n setValue(event.target.value);\n };\n\n return (\n \n );\n};\n```\n\n### Other Visual States\n\nUse the `useThemedRing` hook to change the visual state of the `\n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" - }, - "select": { - "title": "Preview/Select", - "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-select--docs", - "mdxPath": "modules/preview-react/select/stories/Select.mdx", - "mdxProse": "# Canvas Kit Select\n\n## Top Label\n\n### Default\n\n### Default with Custom Options Data\n\n### Default with Simple Options Data\n\n### Scrollable\n\n### Disabled\n\n### Alert\n\n### Error\n\n### Grow\n\n## Left Label\n\n### Default\n\n### Default with Custom Options Data\n\n### Default with Simple Options Data\n\n### Scrollable\n\n### Disabled\n\n### Alert\n\n### Error\n\n### Grow\n\n" + "mdxProse": "# Canvas Kit Status Indicator\n\nStatus Indicators help the user quickly identify the status of a task, action, or page element.\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n### Basic Example\n\n`StatusIndicator` includes a container `StatusIndicator` component and the following subcomponents\nwhich can be composed in a variety of ways: `StatusIndicator.Label` and `StatusIndicator.Icon`.\n\nA basic `StatusIndicator` with a `StatusIndicator.Label` will render text with a gray background and\nlow emphasis.\n\n```tsx\nimport React from 'react';\n\nimport {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';\n\nexport const Basic = () => {\n return (\n \n Unpublished\n \n );\n};\n```\n\n### Emphasis\n\nSet the `emphasis` prop of `StatusIndicator` to adjust the contrast between the text and background\ncolor. Emphasis is typically used to convey more visual urgency.\n\n`emphasis` accepts `high` or `low`.\n\n```tsx\nimport React from 'react';\n\nimport {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';\nimport {uploadCloudIcon} from '@workday/canvas-system-icons-web';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n gap: system.space.x4,\n});\n\nexport const Emphasis = () => {\n return (\n \n \n \n High Emphasis\n \n \n \n Low Emphasis\n \n \n );\n};\n```\n\n### Icon\n\nUse `StatusIndicator.Icon` to add an icon to the `StatusIndicator` as a visual decorator. The\nposition of the icon may be adjusted depending on where you place it in the markup.\n\n```tsx\nimport React from 'react';\n\nimport {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';\nimport {uploadCloudIcon} from '@workday/canvas-system-icons-web';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst parentContainerStyles = createStyles({\n gap: system.space.x4,\n});\n\nexport const Icon = () => {\n return (\n \n \n \n Unpublished\n \n \n published\n \n \n \n );\n};\n```\n\n### Overflow\n\nWe **strongly** discourage using text in a `StatusIndicator` which will cause it to exceed its\nmaximum width of `200px`. In situations where this cannot be avoided and text must be overflowed, we\nsuggest wrapping `StatusIndicator` in an `OverflowTooltip` and applying `tabIndex={0}` to it so the\noverflowed text is accessible via keyboard and mouse. You may also override the default `maxWidth`\nof `StatusIndicator` via [style props](/get-started/for-developers/documentation/style-props/).\n\n```tsx\nimport React from 'react';\n\nimport {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';\nimport {uploadCloudIcon} from '@workday/canvas-system-icons-web';\nimport {OverflowTooltip} from '@workday/canvas-kit-react/tooltip';\nimport {calc, createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst statusIndicatorStyles = createStyles({\n maxWidth: calc.add(system.space.x20, system.space.x4),\n});\n\nexport const Overflow = () => {\n return (\n \n \n \n \n Your workbook is currently in process of saving\n \n \n \n );\n};\n```\n\n### Variants\n\nSet the `variant` prop of `StatusIndicator` to adjust its background color. `variant` accepts the\nfollowing values:\n\n- `gray`\n- `orange`\n- `blue`\n- `green`\n- `red`\n- `transparent`\n\nThe background color dictated by the `variant` will be dark or light based on the `emphasis`.\n\n```tsx\nimport React from 'react';\n\nimport {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';\nimport {uploadCloudIcon} from '@workday/canvas-system-icons-web';\nimport {Flex} from '@workday/canvas-kit-react/layout';\nimport {createStyles} from '@workday/canvas-kit-styling';\nimport {system} from '@workday/canvas-tokens-web';\n\nconst styleOverrides = {\n parentContainerStyles: createStyles({\n gap: system.space.x4,\n flexDirection: 'column',\n }),\n innerContainerStyles: createStyles({\n gap: system.space.x4,\n }),\n};\n\nexport const Variants = () => {\n return (\n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor \n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor \n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n Lorem ipsum dolor\n \n \n \n \n );\n};\n```\n\n### Custom Styles\n\nStatus Indicator and its subcomponents support custom styling via the `cs` prop. For more\ninformation, check our\n[\"How To Customize Styles\"](https://workday.github.io/canvas-kit/?path=/docs/styling-guides-customizing-styles--docs).\n\n## Component API\n\n" }, "segmented-control": { "title": "Preview/Segmented Control", "storybookUrl": "https://workday.github.io/canvas-kit/?path=/docs/preview-segmented-control--docs", "mdxPath": "modules/preview-react/segmented-control/stories/SegmentedControl.mdx", - "mdxProse": "# Canvas Kit Segmented Control\n\nSegmented Control is a\n[compound component](/getting-started/for-developers/resources/compound-components/) that represents\na linear group of multiple buttons allowing the selection of a specific value.\n\n[> Workday Design Reference](https://design.workday.com/components/buttons/segmented-control)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n### Basic Example\n\n`SegmentedControl` includes a container `SegmentedControl` component and the following\nsubcomponents: `SegmentedControl.List` and `SegmentedControl.Item`.\n\nThe example below contains a `SegmentedControl` with four icon-only buttons. Each button is rendered\nusing a `SegmentedControl.Item` and is paired with a tooltip describing the button's function. Only\none button can be active at a time.\n\n```tsx\nimport React from 'react';\n\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n const [viewType, setViewType] = React.useState('table');\n\n return (\n <>\n setViewType(data.id)}>\n \n \n \n \n \n \n \n \n Selected: {viewType}\n \n \n );\n};\n```\n\nNote that you must provide `SegmentedControl.List` with an `aria-label` prop for accessibility\nreasons.\n\nWe **strongly** discourage including more than four buttons in a single `SegmentedControl`.\n\n### Variations\n\n`SegmentedControl` supports three variations based on whether or not its `SegmentedControl.Item`\ncomponents have an `icon` prop and/or text content: icon-only, text-only, and text-and-icon.\n\nAll `SegmentedControl.Item` components within a given `SegmentedControl` must be of the same\nvariation.\n\n#### Icon-Only\n\nTo render an icon-only `SegmentedControl`, apply the `icon` prop to `SegmentedControl.Item` and do\nnot provide it with text content. Refer to the [basic example](#basic-example) above for an instance\nof an icon-only `SegmentedControl`.\n\nThe icon-only variation is the only variation which supports a vertical orientation in addition to\nthe default horizontal orientation. Set the `orientation` prop of `SegmentedControl` to `vertical`\nto configure the component to render vertically.\n\n```tsx\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Vertical = () => (\n \n \n \n \n \n \n \n \n);\n```\n\n#### Text-Only\n\nTo render a text-only `SegmentedControl`, omit the `icon` prop from `SegmentedControl.Item` and\nprovide it with text content.\n\n```tsx\nimport React from 'react';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const TextOnly = () => (\n \n \n Table\n List\n Diagram\n \n \n);\n```\n\n#### Text-and-Icon\n\nTo render a text-and-icon `SegmentedControl`, apply the `icon` prop to `SegmentedControl.Item` and\nprovide it with text content.\n\n```tsx\nimport React from 'react';\nimport {gridIcon, listViewIcon, pieChartIcon} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const TextAndIcon = () => (\n \n \n \n Table\n \n \n List\n \n \n Diagram\n \n \n \n);\n```\n\n### Sizes\n\n`SegmentedControl` accepts a `size` prop which supports the following values:\n\n- `small`\n- `medium` (Default)\n- `large`\n\n```tsx\nimport React from 'react';\nimport {Box} from '@workday/canvas-kit-react/layout';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {BodyText} from '@workday/canvas-kit-react/text';\n\nexport const Sizes = () => (\n <>\n \n \n Small\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n \n Medium\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n \n Large\n \n \n \n \n Table\n \n \n List\n \n \n Detail\n \n \n Diagram\n \n \n \n \n \n);\n```\n\n### Disabled\n\nSet the `disabled` prop of `SegmentedControl` to disable the entire component including its buttons.\n\n```tsx\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Disabled = () => (\n \n \n \n \n \n \n \n \n);\n```\n\n### Right-to-Left (RTL)\n\n`SegmentedControl` supports right-to-left languages when specified in the `CanvasProvider` `theme`.\n\n```tsx\nimport {CanvasProvider, ContentDirection} from '@workday/canvas-kit-react/common';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\n\nexport const RTL = () => (\n \n \n \n \n שולחן\n \n \n רשימה\n \n \n פרטים\n \n \n תרשים\n \n \n \n \n);\n```\n\n### Dynamic Items\n\n`SegmentedControl` supports a\n[dynamic API](/getting-started/for-developers/resources/collection-api/#dynamic-items) where instead\nof statically providing the JSX for each `SegmentedControl.Item`, you pass an array of `items` in\nthe `model` state and provide a render function to display the items.\n\n```tsx\nimport React from 'react';\nimport {\n SegmentedControl,\n useSegmentedControlModel,\n} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Dynamic = () => {\n const [viewType, setViewType] = React.useState('table');\n\n const model = useSegmentedControlModel({\n items: [\n {id: 'table', icon: gridIcon, label: 'Table'},\n {id: 'list', icon: listViewIcon, label: 'List'},\n {id: 'detail', icon: listDetailIcon, label: 'Detail'},\n {id: 'diagram', icon: pieChartIcon, label: 'Diagram'},\n ],\n size: 'small',\n initialValue: viewType,\n onSelect: data => {\n console.log(`${data.id} is selected`);\n setViewType(data.id);\n },\n });\n\n return (\n \n \n {item => (\n \n {item.label}\n \n )}\n \n \n );\n};\n```\n\n## Component API\n\n## Specifications\n\n" + "mdxProse": "# Canvas Kit Segmented Control\n\nSegmented Control is a\n[compound component](/get-started/for-developers/documentation/compound-components/) that represents\na linear group of multiple buttons allowing the selection of a specific value.\n\n[> Workday Design Reference](https://design.workday.com/components/buttons/segmented-control)\n\n## Installation\n\n```sh\nyarn add @workday/canvas-kit-preview-react\n```\n\n## Usage\n\n### Basic Example\n\n`SegmentedControl` includes a container `SegmentedControl` component and the following\nsubcomponents: `SegmentedControl.List` and `SegmentedControl.Item`.\n\nThe example below contains a `SegmentedControl` with four icon-only buttons. Each button is rendered\nusing a `SegmentedControl.Item` and is paired with a tooltip describing the button's function. Only\none button can be active at a time.\n\n```tsx\nimport React from 'react';\n\nimport {SegmentedControl} from '@workday/canvas-kit-preview-react/segmented-control';\nimport {BodyText} from '@workday/canvas-kit-react/text';\nimport {\n gridIcon,\n listViewIcon,\n listDetailIcon,\n pieChartIcon,\n} from '@workday/canvas-system-icons-web';\n\nexport const Basic = () => {\n const [viewType, setViewType] = React.useState('table');\n\n return (\n <>\n setViewType(data.id)}>\n \n \n \n \n \n \n \n \n Selected: {viewType}\n \n \n );\n};\n```\n\nWe **strongly** discourage including more than four buttons in a single `SegmentedControl`.\n\n### Accessibility\n\nOur `SegmentedControl` component renders semantic HTML `