From 4db97a72eb5920a748e00c19d48024f2eac0340e Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Thu, 6 Aug 2026 10:59:33 +0100 Subject: [PATCH 01/19] build(skills): exclude Playground examples from generated skill files --- scripts/skills/build_skills.mjs | 284 ++++++++++++------- skills/carbon-react/components/card.md | 61 ---- skills/carbon-react/components/pill.md | 29 -- skills/carbon-react/components/typography.md | 18 ++ 4 files changed, 201 insertions(+), 191 deletions(-) diff --git a/scripts/skills/build_skills.mjs b/scripts/skills/build_skills.mjs index 85f7c14608..c552bc1001 100644 --- a/scripts/skills/build_skills.mjs +++ b/scripts/skills/build_skills.mjs @@ -106,24 +106,29 @@ const componentCandidates = []; // Scan __next__ directories for upcoming components const nextComponentFiles = fg.sync( - ["src/components/**/__next__/index.{ts,tsx}", "src/components/**/__next__/*.component.{ts,tsx}"], - { - cwd: repoRoot, + [ + "src/components/**/__next__/index.{ts,tsx}", + "src/components/**/__next__/*.component.{ts,tsx}", + ], + { + cwd: repoRoot, absolute: true, - ignore: ["**/__internal__/**"] - } + ignore: ["**/__internal__/**"], + }, ); for (const filePath of nextComponentFiles) { const sourceFile = project.addSourceFileAtPathIfExists(filePath); if (!sourceFile) continue; - + const exported = sourceFile.getExportedDeclarations(); for (const [exportName, declarations] of exported.entries()) { if (exportName === "default" || exportName.endsWith("Props")) continue; if (!/^[A-Z]/.test(exportName)) continue; - - const relativePath = "./" + path.relative(path.join(repoRoot, "src"), filePath).replace(/\\/g, "/"); + + const relativePath = + "./" + + path.relative(path.join(repoRoot, "src"), filePath).replace(/\\/g, "/"); const rawModuleSpecifier = relativePath .replace(/\/index\.(ts|tsx)$/, "") .replace(/\.(ts|tsx)$/, ""); @@ -131,11 +136,11 @@ for (const filePath of nextComponentFiles) { /(\/__next__)\/.*\.component$/, "$1", ); - + // Keep original name for props lookup, use display name for output componentCandidates.push({ - name: exportName, // Original name for props lookup - displayName: `${exportName}Next`, // Display name for output files + name: exportName, // Original name for props lookup + displayName: `${exportName}Next`, // Display name for output files moduleSpecifier, }); } @@ -171,7 +176,8 @@ for (const exportDecl of indexFile.getExportDeclarations()) { } } -const uniqueComponentCandidates = dedupeComponentCandidates(componentCandidates); +const uniqueComponentCandidates = + dedupeComponentCandidates(componentCandidates); /** @type {ComponentData[]} */ const componentData = []; @@ -186,19 +192,21 @@ for (const candidate of uniqueComponentCandidates) { } const moduleDir = getModuleDir(modulePath); - const moduleFiles = fg.sync(["**/*.{ts,tsx}"], { - cwd: moduleDir, - absolute: true, - ignore: [ - "**/*.spec.*", - "**/*.test.*", - "**/*.stories.*", - "**/*.pw.*", - "**/*.mdx", - "**/__internal__/**", - "**/__next__/**", - ], - }).sort(); + const moduleFiles = fg + .sync(["**/*.{ts,tsx}"], { + cwd: moduleDir, + absolute: true, + ignore: [ + "**/*.spec.*", + "**/*.test.*", + "**/*.stories.*", + "**/*.pw.*", + "**/*.mdx", + "**/__internal__/**", + "**/__next__/**", + ], + }) + .sort(); for (const filePath of moduleFiles) { project.addSourceFileAtPathIfExists(filePath); @@ -249,7 +257,11 @@ const wouldWrite = []; /** @type {Map} */ const lineEndingPreferences = !checkMode - ? await collectLineEndingPreferences([componentsOutDir, referencesDir, skillsRoot]) + ? await collectLineEndingPreferences([ + componentsOutDir, + referencesDir, + skillsRoot, + ]) : new Map(); if (!checkMode) { @@ -261,7 +273,10 @@ if (!checkMode) { } const skillRootContent = renderSkillRootContent(); -wouldWrite.push({ path: path.join(skillsRoot, "SKILL.md"), content: skillRootContent }); +wouldWrite.push({ + path: path.join(skillsRoot, "SKILL.md"), + content: skillRootContent, +}); for (const relativePath of docsReferenceFiles) { const sourcePath = path.join(repoRoot, relativePath); @@ -269,9 +284,14 @@ for (const relativePath of docsReferenceFiles) { continue; } const fileName = path.basename(sourcePath); - const targetPath = path.join(referencesDir, fileName).replace(/\.mdx?$/, ".md"); + const targetPath = path + .join(referencesDir, fileName) + .replace(/\.mdx?$/, ".md"); const content = await fs.readFile(sourcePath, "utf8"); - wouldWrite.push({ path: targetPath, content: content.replace(/\r\n/g, "\n") }); + wouldWrite.push({ + path: targetPath, + content: content.replace(/\r\n/g, "\n"), + }); } const indexLines = ["# Carbon Component Catalog", "", "## Components", ""]; @@ -292,7 +312,10 @@ for (const component of componentData.sort((a, b) => } const indexContent = indexLines.join("\n"); -wouldWrite.push({ path: path.join(skillsRoot, "index.md"), content: indexContent }); +wouldWrite.push({ + path: path.join(skillsRoot, "index.md"), + content: indexContent, +}); if (checkMode) { const { hasDiff, diffSummary } = await checkWouldWrite(wouldWrite, { @@ -301,7 +324,9 @@ if (checkMode) { }); if (hasDiff) { // eslint-disable-next-line no-console -- CI output - console.error("Skills build check failed: files on disk differ from expected output:\n"); + console.error( + "Skills build check failed: files on disk differ from expected output:\n", + ); // eslint-disable-next-line no-console -- CI output console.error(diffSummary); process.exit(1); @@ -352,7 +377,10 @@ async function collectLineEndingPreferences(directories) { continue; } - const files = await fs.readdir(dir, { recursive: true, withFileTypes: true }); + const files = await fs.readdir(dir, { + recursive: true, + withFileTypes: true, + }); for (const file of files) { if (!file.isFile()) { @@ -382,7 +410,11 @@ async function collectLineEndingPreferences(directories) { * @param {Map} preferences * @returns {Promise} */ -async function applyExistingLineEndingPreference(filePath, content, preferences) { +async function applyExistingLineEndingPreference( + filePath, + content, + preferences, +) { const key = normalizePathKey(filePath); const preference = preferences.get(key); @@ -528,19 +560,21 @@ function resolvePropsDefinition( if (!resolved) { continue; } - const targetFiles = fg.sync(["**/*.{ts,tsx}"], { - cwd: getModuleDir(resolved), - absolute: true, - ignore: [ - "**/*.spec.*", - "**/*.test.*", - "**/*.stories.*", - "**/*.pw.*", - "**/*.mdx", - "**/__internal__/**", - "**/__next__/**", - ], - }).sort(); + const targetFiles = fg + .sync(["**/*.{ts,tsx}"], { + cwd: getModuleDir(resolved), + absolute: true, + ignore: [ + "**/*.spec.*", + "**/*.test.*", + "**/*.stories.*", + "**/*.pw.*", + "**/*.mdx", + "**/__internal__/**", + "**/__next__/**", + ], + }) + .sort(); const resolvedDefinition = findTypeDefinitionInFiles( projectInstance, @@ -590,46 +624,50 @@ function extractPropsFromDefinition(propsDefinition, defaultsMap) { .getProperties() .filter((symbol) => !shouldExcludePropSymbol(symbol)) .map((symbol) => { - const declaration = symbol - .getDeclarations() - .find( - (decl) => - decl.isKind(SyntaxKind.PropertySignature) || - decl.isKind(SyntaxKind.PropertyDeclaration), + const declaration = symbol + .getDeclarations() + .find( + (decl) => + decl.isKind(SyntaxKind.PropertySignature) || + decl.isKind(SyntaxKind.PropertyDeclaration), + ); + const propType = declaration + ? declaration.getType() + : symbol.getTypeAtLocation(propsDefinition.node); + const literals = getLiteralUnionValues(propType); + const jsDocs = declaration?.getJsDocs?.() ?? []; + const description = jsDocs + .map((doc) => doc.getDescription().trim()) + .filter(Boolean) + .join(" "); + const deprecationInfo = getDeprecationFromJsDocs(jsDocs); + const required = declaration?.isKind(SyntaxKind.PropertySignature) + ? !declaration.hasQuestionToken() + : true; + + const resolvedText = propType.getText( + declaration ?? propsDefinition.node, ); - const propType = declaration - ? declaration.getType() - : symbol.getTypeAtLocation(propsDefinition.node); - const literals = getLiteralUnionValues(propType); - const jsDocs = declaration?.getJsDocs?.() ?? []; - const description = jsDocs - .map((doc) => doc.getDescription().trim()) - .filter(Boolean) - .join(" "); - const deprecationInfo = getDeprecationFromJsDocs(jsDocs); - const required = declaration?.isKind(SyntaxKind.PropertySignature) - ? !declaration.hasQuestionToken() - : true; - - const resolvedText = propType.getText(declaration ?? propsDefinition.node); - const typeNode = declaration?.getTypeNode?.(); - const typeText = ( - resolvedText.includes("import(") && typeNode - ? typeNode.getText() - : resolvedText - ).replace(/\s+/g, " ").trim(); - - return { - name: symbol.getName(), - type: typeText, - required, - literals, - description: description || null, - defaultValue: defaultsMap.get(symbol.getName()) ?? null, - deprecated: deprecationInfo.deprecated, - deprecationReason: deprecationInfo.reason, - }; - }); + const typeNode = declaration?.getTypeNode?.(); + const typeText = ( + resolvedText.includes("import(") && typeNode + ? typeNode.getText() + : resolvedText + ) + .replace(/\s+/g, " ") + .trim(); + + return { + name: symbol.getName(), + type: typeText, + required, + literals, + description: description || null, + defaultValue: defaultsMap.get(symbol.getName()) ?? null, + deprecated: deprecationInfo.deprecated, + deprecationReason: deprecationInfo.reason, + }; + }); } /** @@ -881,14 +919,21 @@ function extractFromParameters(parameters, defaults) { * @returns {Promise>} */ async function extractStoryData(projectInstance, rootDir) { - const storyFiles = fg.sync( - ["src/**/*.stories.@(js|jsx|ts|tsx)", "docs/**/*.stories.@(js|jsx|ts|tsx)"], - { cwd: rootDir, absolute: true }, - ).sort(); - const mdxFiles = fg.sync(["src/**/*.mdx", "docs/**/*.mdx"], { - cwd: rootDir, - absolute: true, - }).sort(); + const storyFiles = fg + .sync( + [ + "src/**/*.stories.@(js|jsx|ts|tsx)", + "docs/**/*.stories.@(js|jsx|ts|tsx)", + ], + { cwd: rootDir, absolute: true }, + ) + .sort(); + const mdxFiles = fg + .sync(["src/**/*.mdx", "docs/**/*.mdx"], { + cwd: rootDir, + absolute: true, + }) + .sort(); /** @type {Map} */ const storyMap = new Map(); @@ -911,6 +956,10 @@ async function extractStoryData(projectInstance, rootDir) { const existing = storyMap.get(componentName) ?? []; for (const story of stories) { + // Exclude Playground stories from skills files + if (story.name === "Playground") { + continue; + } existing.push({ ...story, source: path.relative(rootDir, filePath), @@ -934,6 +983,10 @@ async function extractStoryData(projectInstance, rootDir) { } const existing = storyMap.get(componentName) ?? []; for (const example of examples) { + // Exclude Playground examples from skills files + if (example.name === "Playground") { + continue; + } existing.push({ name: example.name, argsText: example.code, @@ -1262,7 +1315,10 @@ function renderSkillRootContent() { * @param {{componentsOutDir: string, referencesDir: string}} outputDirs * @returns {Promise<{hasDiff: boolean, diffSummary: string}>} */ -async function checkWouldWrite(wouldWrite, { componentsOutDir, referencesDir }) { +async function checkWouldWrite( + wouldWrite, + { componentsOutDir, referencesDir }, +) { /** @type {string[]} */ const diffs = []; const expectedPaths = new Set(wouldWrite.map((w) => w.path)); @@ -1272,7 +1328,12 @@ async function checkWouldWrite(wouldWrite, { componentsOutDir, referencesDir }) try { existing = await fs.readFile(filePath, "utf8"); } catch (err) { - if (err && typeof err === 'object' && 'code' in err && err?.code === "ENOENT") { + if ( + err && + typeof err === "object" && + "code" in err && + err?.code === "ENOENT" + ) { diffs.push(` Missing: ${path.relative(repoRoot, filePath)}`); continue; } @@ -1512,7 +1573,10 @@ function renderComponentMarkdown(component, stories) { const bData = b.name.startsWith("data-"); const aAria = a.name.startsWith("aria-"); const bAria = b.name.startsWith("aria-"); - const groupOrder = (/** @type {boolean} */ data, /** @type {boolean} */ aria) => (data ? 1 : aria ? 2 : 0); + const groupOrder = ( + /** @type {boolean} */ data, + /** @type {boolean} */ aria, + ) => (data ? 1 : aria ? 2 : 0); const ga = groupOrder(aData, aAria); const gb = groupOrder(bData, bAria); if (ga !== gb) return ga - gb; @@ -1536,7 +1600,9 @@ function renderComponentMarkdown(component, stories) { const defaultValue = (prop.defaultValue ?? "").replace(/\r/g, ""); if (hasDeprecatedProps) { const deprecated = prop.deprecated ? "Yes" : ""; - const deprecationReason = (prop.deprecationReason ?? "").replace(/\s+/g, " ").trim(); + const deprecationReason = (prop.deprecationReason ?? "") + .replace(/\s+/g, " ") + .trim(); lines.push( `| ${prop.name} | ${escapePipes(prop.type)} | ${prop.required ? "Yes" : "No"} | ${escapePipes(literals)} | ${deprecated} | ${escapePipes(deprecationReason)} | ${escapePipes(description)} | ${escapePipes(defaultValue)} |`, ); @@ -1557,10 +1623,24 @@ function renderComponentMarkdown(component, stories) { lines.push(`### ${story.name}`); lines.push(""); if (story.argsText) { - lines.push("**Args**", "", "```tsx", story.argsText.replace(/\r\n/g, "\n"), "```", ""); + lines.push( + "**Args**", + "", + "```tsx", + story.argsText.replace(/\r\n/g, "\n"), + "```", + "", + ); } if (story.renderText) { - lines.push("**Render**", "", "```tsx", story.renderText.replace(/\r\n/g, "\n"), "```", ""); + lines.push( + "**Render**", + "", + "```tsx", + story.renderText.replace(/\r\n/g, "\n"), + "```", + "", + ); } lines.push(""); } @@ -1637,7 +1717,9 @@ function dedupeComponentCandidates(candidates) { continue; } - if (scoreComponentCandidate(candidate) > scoreComponentCandidate(existing)) { + if ( + scoreComponentCandidate(candidate) > scoreComponentCandidate(existing) + ) { byOutputName.set(outputName, candidate); } } @@ -1661,4 +1743,4 @@ function scoreComponentCandidate(candidate) { score += 1; } return score; -} \ No newline at end of file +} diff --git a/skills/carbon-react/components/card.md b/skills/carbon-react/components/card.md index 09e5db076b..42660ea4bf 100644 --- a/skills/carbon-react/components/card.md +++ b/skills/carbon-react/components/card.md @@ -468,67 +468,6 @@ description: Carbon Card component props and usage examples. ``` -### Playground - -**Args** - -```tsx -{ - spacing: "medium", - roundness: "moderate", - width: undefined, - height: undefined, - draggable: false, - } -``` - -**Render** - -```tsx -(args) => { - return ( - - {}}>Move up - {}}> - Move down - - - {}}>Delete - {}}>Edit - - ) : undefined - } - > - - - Heading - Additional text - - - - - - - - - Body text - - - More text - - Even more text - - - - ); - } -``` - - ### Default **Render** diff --git a/skills/carbon-react/components/pill.md b/skills/carbon-react/components/pill.md index 83d56c72b1..22e4b0010e 100644 --- a/skills/carbon-react/components/pill.md +++ b/skills/carbon-react/components/pill.md @@ -48,35 +48,6 @@ description: Carbon Pill component props and usage examples. | size | "S" \| "M" \| "L" \| "XL" \| undefined | No | | Yes | The `XL` size is deprecated and will be removed in a future release. Use `L` instead. | Sets the size of the pill. | "M" | ## Examples -### Playground - -**Args** - -```tsx -{ - children: "Label", - variant: "grey", - size: "M", - fill: true, - inverse: false, - onDelete: undefined, - icon: undefined, - } -``` - -**Render** - -```tsx -(args) => { - return ( - - {args.children} - - ); - } -``` - - ### Wrapped **Render** diff --git a/skills/carbon-react/components/typography.md b/skills/carbon-react/components/typography.md index 9254206873..ee3f41f23e 100644 --- a/skills/carbon-react/components/typography.md +++ b/skills/carbon-react/components/typography.md @@ -77,6 +77,24 @@ description: Carbon Typography component props and usage examples. | truncate | boolean \| undefined | No | | Yes | Use `textOverflow` and `whiteSpace` props instead. This prop will eventually be removed. Apply truncation | | false | ## Examples +### Playground + +**Args** + +```tsx +{ + children: "Typography content", + variant: "p", + } +``` + +**Render** + +```tsx +(args) => {args.children} +``` + + ### Variants **Render** From 146222bacce3943d43331f504e0f3f9756854b48 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:15 +0100 Subject: [PATCH 02/19] docs(badge): add playground example --- skills/carbon-react/components/badge.md | 167 ---------------- src/components/badge/badge-test.stories.tsx | 141 ++++++++++++- src/components/badge/badge.mdx | 56 ++---- src/components/badge/badge.stories.tsx | 208 +++++++------------- 4 files changed, 225 insertions(+), 347 deletions(-) diff --git a/skills/carbon-react/components/badge.md b/skills/carbon-react/components/badge.md index e444df0b8f..4d749f014d 100644 --- a/skills/carbon-react/components/badge.md +++ b/skills/carbon-react/components/badge.md @@ -59,170 +59,3 @@ description: Carbon Badge component props and usage examples. } ``` - -### With Children - -**Render** - -```tsx -({ ...args }) => { - return ( - - - - ); -} -``` - - -### Sizes - -**Render** - -```tsx -({ ...args }) => { - return ( - <> - - - - - - - ); -} -``` - - -### Subtle Variant - -**Args** - -```tsx -{ - variant: "subtle", -} -``` - -**Render** - -```tsx -({ ...args }) => { - return ( - <> - - - - - - - ); -} -``` - - -### Inverse - -**Args** - -```tsx -{ - inverse: true, -} -``` - -**Render** - -```tsx -({ ...args }) => { - return ( - <> - - - - - - - - - - - - - ); -} -``` - - -### With OnClick - -**Render** - -```tsx -({ ...args }) => { - const counter = 9; - return ( - {}} - aria-label={`Remove ${counter} filters.`} - {...args} - > - - - ); -} -``` - - -### Custom Color - -**Render** - -```tsx -({ ...args }) => { - const counter = 9; - return ( - {}} - aria-label={`Remove ${counter} filters.`} - color="--colorsSemanticNegative500" - {...args} - > - - - ); -} -``` - diff --git a/src/components/badge/badge-test.stories.tsx b/src/components/badge/badge-test.stories.tsx index 7e971c7244..d33673988a 100644 --- a/src/components/badge/badge-test.stories.tsx +++ b/src/components/badge/badge-test.stories.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Meta, StoryObj } from "@storybook/react-vite"; import Badge, { BadgeProps } from "."; import Box from "../box"; -import Button from "../button"; +import Button from "../button/__next__"; import MultiActionButton from "../multi-action-button"; import SplitButton from "../split-button"; import { Menu, MenuItem } from "../menu"; @@ -84,17 +84,17 @@ export const SizesWithChildren: Story = ({ ...args }) => { return ( - - - @@ -164,3 +164,136 @@ InTabs.parameters = { themeProvider: { chromatic: { theme: "sage" } }, chromatic: { disableSnapshot: false }, }; + +export const WithChildren: Story = ({ ...args }) => { + return ( + + + + ); +}; +WithChildren.storyName = "With Children"; + +export const Sizes: Story = ({ ...args }) => { + return ( + <> + + + + + + + ); +}; +Sizes.storyName = "Sizes"; + +export const SubtleVariant: Story = ({ ...args }) => { + return ( + <> + + + + + + + ); +}; +SubtleVariant.storyName = "Subtle Variant"; +SubtleVariant.args = { + variant: "subtle", +}; + +export const Inverse: Story = ({ ...args }) => { + return ( + <> + + + + + + + + + + + + + ); +}; +Inverse.storyName = "Inverse"; +Inverse.args = { + inverse: true, +}; +Inverse.decorators = [ + (Story) => ( + + + + ), +]; + +export const WithOnClick: Story = ({ ...args }) => { + const counter = 9; + return ( + {}} + aria-label={`Remove ${counter} filters.`} + {...args} + > + + + ); +}; +WithOnClick.storyName = "With OnClick"; +WithOnClick.parameters = { + chromatic: { + disableSnapshot: true, + }, +}; + +export const CustomColor: Story = ({ ...args }) => { + const counter = 9; + return ( + {}} + aria-label={`Remove ${counter} filters.`} + color="--colorsSemanticNegative500" + {...args} + > + + + ); +}; +CustomColor.storyName = "Custom Color"; diff --git a/src/components/badge/badge.mdx b/src/components/badge/badge.mdx index 781cd34928..ae195eb107 100644 --- a/src/components/badge/badge.mdx +++ b/src/components/badge/badge.mdx @@ -1,4 +1,4 @@ -import { ArgTypes, Meta, Canvas } from "@storybook/addon-docs/blocks"; +import { ArgTypes, Meta, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as BadgeStories from "./badge.stories"; @@ -29,6 +29,22 @@ To use Badge component, import `Badge` and use as a standalone component or wrap import Badge from "carbon-react/lib/components/badge"; ``` +## Designer Notes + +A badge is a small numerical indicator positioned next to interactive elements. +It shows when something needs action or review. Typically used for notifications within navigation, and to show that filters have been applied. + +## Playground + +Use this interactive example to explore Badge props with Storybook controls. + + + + + ## Examples ### Default @@ -42,44 +58,6 @@ If the `counter` prop is not provided or its value is `0`, the `Badge` will not -### With Children - -To position a `Badge` relative to another component, for example a `Button`, you can pass it as a child of `Badge`. - -Please make sure you associate the `Badge` to the component it relates to, this can be done by setting the `Badge`'s `id` to the child component's `aria-describedby`. - - - -### Sizes - -You can use the `size` prop to change the size of the `Badge` to "small", "medium" (default), or "large". - -The "small" size will not visually display the `counter` value, however the prop will still be needed for the badge to render. - - - -### Subtle Variant - -By default, the `Badge` is rendered as the "typical" variant, however you can use the `variant` prop to change the appearance to "subtle". - - - -### Inverse - -You can use the `inverse` prop to render the `Badge` with the inverse color scheme. - - - -### (Deprecated) Badge with onClick - - - -### (Deprecated) Custom Color - -You can use the `color` prop to override the default color of the component. - - - ## Props ### Badge diff --git a/src/components/badge/badge.stories.tsx b/src/components/badge/badge.stories.tsx index 582a16c699..c572f01471 100644 --- a/src/components/badge/badge.stories.tsx +++ b/src/components/badge/badge.stories.tsx @@ -4,24 +4,35 @@ import { Meta, StoryObj } from "@storybook/react-vite"; import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props"; import Badge from "."; -import Button from "../button"; +import Button from "../button/__next__"; import Box from "../box"; -import Icon from "../icon"; const styledSystemProps = generateStyledSystemProps({ margin: true, }); -const meta: Meta = { +type BadgeStoryArgs = React.ComponentProps & { + withButton?: boolean; +}; + +const meta: Meta = { title: "Badge", component: Badge, argTypes: { ...styledSystemProps, counter: { control: { - type: "text", + type: "number", }, }, + size: { + options: ["small", "medium", "large"], + control: { type: "radio" }, + }, + variant: { + options: ["typical", "subtle"], + control: { type: "radio" }, + }, }, decorators: [ (Story) => ( @@ -41,6 +52,62 @@ const meta: Meta = { export default meta; type Story = StoryObj; +export const Playground: StoryObj = { + render: (args) => { + const { withButton, ...badgeProps } = args; + return ( + + + {withButton && ( + + )} + + + ); + }, + args: { + counter: 99, + size: "medium", + variant: "typical", + inverse: false, + withButton: false, + }, + argTypes: { + withButton: { + control: { type: "boolean" }, + description: "Render Badge with a Button child", + }, + }, + decorators: [ + (Story, { args }) => ( + + + + ), + ], +}; +Playground.storyName = "Playground"; + export const Default: Story = ({ ...args }) => { return ( <> @@ -52,136 +119,3 @@ export const Default: Story = ({ ...args }) => { ); }; Default.storyName = "Default"; - -export const WithChildren: Story = ({ ...args }) => { - return ( - - - - ); -}; -WithChildren.storyName = "With Children"; - -export const Sizes: Story = ({ ...args }) => { - return ( - <> - - - - - - - ); -}; -Sizes.storyName = "Sizes"; - -export const SubtleVariant: Story = ({ ...args }) => { - return ( - <> - - - - - - - ); -}; -SubtleVariant.storyName = "Subtle Variant"; -SubtleVariant.args = { - variant: "subtle", -}; - -export const Inverse: Story = ({ ...args }) => { - return ( - <> - - - - - - - - - - - - - ); -}; -Inverse.storyName = "Inverse"; -Inverse.args = { - inverse: true, -}; -Inverse.decorators = [ - (Story) => ( - - - - ), -]; - -export const WithOnClick: Story = ({ ...args }) => { - const counter = 9; - return ( - {}} - aria-label={`Remove ${counter} filters.`} - {...args} - > - - - ); -}; -WithOnClick.storyName = "With OnClick"; -WithOnClick.parameters = { - chromatic: { - disableSnapshot: true, - }, -}; - -export const CustomColor: Story = ({ ...args }) => { - const counter = 9; - return ( - {}} - aria-label={`Remove ${counter} filters.`} - color="--colorsSemanticNegative500" - {...args} - > - - - ); -}; -CustomColor.storyName = "Custom Color"; From 17564ca1d22e435a83589b58349810c1500e9873 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:26 +0100 Subject: [PATCH 03/19] docs(button-toggle): add playground example --- .../carbon-react/components/button-toggle.md | 404 ----------------- .../button-toggle-test.stories.tsx | 385 ++++++++++++++++ .../button-toggle/button-toggle.mdx | 103 +---- .../button-toggle/button-toggle.stories.tsx | 421 ++---------------- 4 files changed, 450 insertions(+), 863 deletions(-) diff --git a/skills/carbon-react/components/button-toggle.md b/skills/carbon-react/components/button-toggle.md index eb2c6b4168..3b40329a39 100644 --- a/skills/carbon-react/components/button-toggle.md +++ b/skills/carbon-react/components/button-toggle.md @@ -32,38 +32,6 @@ description: Carbon ButtonToggle component props and usage examples. | buttonIconSize | "small" \| "large" \| undefined | No | | Yes | `buttonIconSize` is no longer supported. | Sets the size of the buttonIcon | | ## Examples -### Default - -**Args** - -```tsx -{ - "aria-label": "Button Toggle Group", - value: "default-2", - } -``` - -**Render** - -```tsx -ControlledButtonToggleGroup -``` - - -### WithLabelAndHint - -**Args** - -```tsx -{ - id: "with-label", - label: "Label", - inputHint: "Hint Text", - value: "with-label-2", - } -``` - - ### Single **Render** @@ -85,69 +53,6 @@ ControlledButtonToggleGroup ``` -### With Icon - -**Render** - -```tsx -({ ...args }: ButtonToggleGroupProps) => { - const [leftValue, setLeftValue] = useState("icon-left-1"); - const [rightValue, setRightValue] = useState("icon-right-1"); - - const handleOnChangeLeft = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setLeftValue(selectedValue as string); - }; - - const handleOnChangeRight = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setRightValue(selectedValue as string); - }; - - return ( - <> - - - - Button 1 - - - - Button 2 - - - - - Button 1 - - - - Button 2 - - - - - ); -} -``` - - ### Loading **Render** @@ -187,312 +92,3 @@ ControlledButtonToggleGroup } ``` - -### Sizes - Grouped - -**Render** - -```tsx -({ ...args }: ButtonToggleGroupProps) => { - const [valueSmall, setValueSmall] = useState("small-2"); - const [valueMedium, setValueMedium] = useState("medium-2"); - const [valueLarge, setValueLarge] = useState("large-2"); - - const handleOnChangeSmall = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueSmall(selectedValue as string); - }; - - const handleOnChangeMedium = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueMedium(selectedValue as string); - }; - - const handleOnChangeLarge = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueLarge(selectedValue as string); - }; - - return ( - <> - - Button 1 - Button 2 - Button 3 - - - - - - Button 1 - - - - Button 2 - - - - Button 3 - - - - - - - Button 1 - - - - Button 2 - - - - Button 3 - - - - ); -} -``` - - -### Sizes - Single - -**Render** - -```tsx -() => { - const [isPressedSmall, setIsPressedSmall] = useState(true); - const [isPressedMedium, setIsPressedMedium] = useState(true); - const [isPressedLarge, setIsPressedLarge] = useState(true); - - const handleClickSmall = () => { - setIsPressedSmall(!isPressedSmall); - }; - - const handleClickMedium = () => { - setIsPressedMedium(!isPressedMedium); - }; - - const handleClickLarge = () => { - setIsPressedLarge(!isPressedLarge); - }; - - return ( - - - Small ButtonToggle - - - Medium ButtonToggle - - - Large ButtonToggle - - - ); -} -``` - - -### Icon Only - Grouped - -**Render** - -```tsx -({ ...args }: ButtonToggleGroupProps) => { - const [valueMedium, setValueMedium] = useState("medium-2"); - const [valueLarge, setValueLarge] = useState("large-2"); - - const handleOnChangeMedium = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueMedium(selectedValue as string); - }; - - const handleOnChangeLarge = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueLarge(selectedValue as string); - }; - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - ); -} -``` - - -### Icon Only - Single - -**Render** - -```tsx -() => { - const [isPressedSmall, setIsPressedSmall] = useState(true); - const [isPressedMedium, setIsPressedMedium] = useState(true); - const [isPressedLarge, setIsPressedLarge] = useState(true); - - const handleClickSmall = () => { - setIsPressedSmall(!isPressedSmall); - }; - - const handleClickMedium = () => { - setIsPressedMedium(!isPressedMedium); - }; - - const handleClickLarge = () => { - setIsPressedLarge(!isPressedLarge); - }; - - return ( - - - - - - - - - - - - ); -} -``` - - -### AllowDeselect - -**Args** - -```tsx -{ - id: "allow-deselect", - value: "allow-deselect-2", - allowDeselect: true, - } -``` - - -### FullWidth - -**Args** - -```tsx -{ - id: "full-width", - value: "full-width-2", - fullWidth: true, - } -``` - - -### Disabled - -**Args** - -```tsx -{ - id: "disabled", - label: "Disabled", - inputHint: "Hint Text", - value: "disabled-2", - disabled: true, - } -``` - diff --git a/src/components/button-toggle/button-toggle-test.stories.tsx b/src/components/button-toggle/button-toggle-test.stories.tsx index 6edfcd5cf5..64aaf23152 100644 --- a/src/components/button-toggle/button-toggle-test.stories.tsx +++ b/src/components/button-toggle/button-toggle-test.stories.tsx @@ -163,3 +163,388 @@ export const WrappedButtons: Story = ({ ...args }: ButtonToggleGroupProps) => { ); }; WrappedButtons.storyName = "Wrapped Buttons"; + +// Documentation regression stories moved from the public docs. + +const ControlledButtonToggleGroup = ({ + id = "default", + children, + value, + ...args +}: Omit) => { + const [selectedButton, setSelectedButton] = useState(value); + + const handleOnChange = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setSelectedButton(selectedValue as string); + }; + + return ( + + Button 1 + Button 2 + Button 3 + + ); +}; + +export const Default: Story = { + render: ControlledButtonToggleGroup, + args: { + "aria-label": "Button Toggle Group", + value: "default-2", + }, + parameters: { + chromatic: { disableSnapshot: true }, + }, +}; + +export const WithLabelAndHint: Story = { + ...Default, + args: { + id: "with-label", + label: "Label", + inputHint: "Hint Text", + value: "with-label-2", + }, +}; + +export const WithIcon: Story = ({ ...args }: ButtonToggleGroupProps) => { + const [leftValue, setLeftValue] = useState("icon-left-1"); + const [rightValue, setRightValue] = useState("icon-right-1"); + + const handleOnChangeLeft = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setLeftValue(selectedValue as string); + }; + + const handleOnChangeRight = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setRightValue(selectedValue as string); + }; + + return ( + <> + + + + Button 1 + + + + Button 2 + + + + + Button 1 + + + + Button 2 + + + + + ); +}; +WithIcon.storyName = "With Icon"; + +export const SizesGrouped: Story = ({ ...args }: ButtonToggleGroupProps) => { + const [valueSmall, setValueSmall] = useState("small-2"); + const [valueMedium, setValueMedium] = useState("medium-2"); + const [valueLarge, setValueLarge] = useState("large-2"); + + const handleOnChangeSmall = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setValueSmall(selectedValue as string); + }; + + const handleOnChangeMedium = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setValueMedium(selectedValue as string); + }; + + const handleOnChangeLarge = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setValueLarge(selectedValue as string); + }; + + return ( + <> + + Button 1 + Button 2 + Button 3 + + + + + + Button 1 + + + + Button 2 + + + + Button 3 + + + + + + + Button 1 + + + + Button 2 + + + + Button 3 + + + + ); +}; +SizesGrouped.storyName = "Sizes - Grouped"; + +export const SizesSingle: Story = () => { + const [isPressedSmall, setIsPressedSmall] = useState(true); + const [isPressedMedium, setIsPressedMedium] = useState(true); + const [isPressedLarge, setIsPressedLarge] = useState(true); + + const handleClickSmall = () => { + setIsPressedSmall(!isPressedSmall); + }; + + const handleClickMedium = () => { + setIsPressedMedium(!isPressedMedium); + }; + + const handleClickLarge = () => { + setIsPressedLarge(!isPressedLarge); + }; + + return ( + + + Small ButtonToggle + + + Medium ButtonToggle + + + Large ButtonToggle + + + ); +}; +SizesSingle.storyName = "Sizes - Single"; + +export const IconOnlyGrouped: Story = ({ ...args }: ButtonToggleGroupProps) => { + const [valueMedium, setValueMedium] = useState("medium-2"); + const [valueLarge, setValueLarge] = useState("large-2"); + + const handleOnChangeMedium = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setValueMedium(selectedValue as string); + }; + + const handleOnChangeLarge = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setValueLarge(selectedValue as string); + }; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +IconOnlyGrouped.storyName = "Icon Only - Grouped"; + +export const IconOnlySingle: Story = () => { + const [isPressedSmall, setIsPressedSmall] = useState(true); + const [isPressedMedium, setIsPressedMedium] = useState(true); + const [isPressedLarge, setIsPressedLarge] = useState(true); + + const handleClickSmall = () => { + setIsPressedSmall(!isPressedSmall); + }; + + const handleClickMedium = () => { + setIsPressedMedium(!isPressedMedium); + }; + + const handleClickLarge = () => { + setIsPressedLarge(!isPressedLarge); + }; + + return ( + + + + + + + + + + + + ); +}; +IconOnlySingle.storyName = "Icon Only - Single"; + +export const AllowDeselect: Story = { + ...Default, + args: { + id: "allow-deselect", + value: "allow-deselect-2", + allowDeselect: true, + }, + parameters: { + chromatic: { disableSnapshot: true }, + }, +}; + +export const FullWidth: Story = { + ...Default, + args: { + id: "full-width", + value: "full-width-2", + fullWidth: true, + }, +}; + +export const Disabled: Story = { + ...Default, + args: { + id: "disabled", + label: "Disabled", + inputHint: "Hint Text", + value: "disabled-2", + disabled: true, + }, +}; diff --git a/src/components/button-toggle/button-toggle.mdx b/src/components/button-toggle/button-toggle.mdx index f2b3844a87..10aece32dd 100644 --- a/src/components/button-toggle/button-toggle.mdx +++ b/src/components/button-toggle/button-toggle.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as ButtonToggleStories from "./button-toggle.stories"; @@ -15,7 +15,7 @@ import * as ButtonToggleStories from "./button-toggle.stories"; Product Design System component - Press one of the buttons to make a selection. This component can be used when the user has to make a choice between a small number of options. +Press one of the buttons to make a selection. This component can be used when the user has to make a choice between a small number of options. ## Contents @@ -32,104 +32,43 @@ import { } from "carbon-react/lib/components/button-toggle"; ``` -## Examples - -### Grouped - -Pass `ButtonToggle` buttons as children of `ButtonToggleGroup` to render them in a group. -Control the state of the selected values by making use of the `value` and `onChange` props available in `ButtonToggleGroup`. +## Playground -If using this component without a visible label, please ensure to provide an accessible label to the group using the `aria-label` prop. +Use this interactive example to explore ButtonToggleGroup props with Storybook controls. - + -### With Label and Hint Text + -The `label` prop can be used to set a visible label for the group. -To provide an additional hint text string, use the `inputHint` prop. - - +## Examples ### Single -`ButtonToggle` can be used individually to act as a switch to turn something on and off. +`ButtonToggle` can be used individually to act as a switch to turn something on and off. Control the state of the button using the `pressed` prop available. -### With Icons - -To render an `Icon` within a `ButtonToggle`, just pass it as a child of the component. - -See our [Icon documentation](../?path=/docs/icon--docs) for more information. - - - ### Loading -To render a `Loader` within a `ButtonToggle`, just pass it as a child of the component. +To render a `Loader` within a `ButtonToggle`, just pass it as a child of the component. When doing this, please ensure to prevent any interaction with the button if selected while in a loading state. -See our [Loader documentation](../?path=/docs/loader--docs) for more information. +See our [Loader documentation](../?path=/docs/loader--docs) for more information. -### Sizes - -#### Grouped - -The size of a group can be changed by passing the `size` prop to `ButtonToggleGroup`. Available sizes are `"small"`, `"medium"` (default) and `"large"`. - -**Note:** Do not make use of icons when using the `"small"` size, please limit the content of the buttons to only text. - - - -#### Single - -The size of a button can be changed by passing the `size` prop to `ButtonToggle`. Available sizes are `"small"`, `"medium"` (default) and `"large"`. - -**Note:** This will only be applied when `ButtonToggle` is used in isolation, if used within a group this prop will have no effect. - - - -### Icon Only - -`ButtonToggle` can be rendered as an icon-only button when only `Icon` children are passed. - -Please ensure to provide accessible names using `aria-label` to any buttons with no visible label. - -#### Grouped - -**Note:** Do not make use of icons when using the `"small"` size, icon-only groups are only available in sizes `"medium"` or `"large"`. - - - -#### Single - - - -### Allow Deselect - -By default, the active `ButtonToggle` can only be deselected when another button in the group is selected. - -The `allowDeselect` prop allows users to deselect the currently active button without having to select another. - - - -### Full Width - -By default, the button wrapper will hug its content. -You can set the `fullWidth` prop to allow the buttons to expand the full width of their container. - - - -### Disabled - -The component can be disabled by passing the `disabled` prop to `ButtonToggleGroup`. -Individual `ButtonToggle` buttons can also be disabled by passing the `disabled` prop. - - - ## Props diff --git a/src/components/button-toggle/button-toggle.stories.tsx b/src/components/button-toggle/button-toggle.stories.tsx index 53b75679e6..e793598695 100644 --- a/src/components/button-toggle/button-toggle.stories.tsx +++ b/src/components/button-toggle/button-toggle.stories.tsx @@ -2,9 +2,7 @@ import React, { useState } from "react"; import { Meta, StoryObj } from "@storybook/react-vite"; import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props"; import { ButtonToggle, ButtonToggleGroup, ButtonToggleGroupProps } from "."; -import Icon from "../icon"; import { Loader } from "../loader/__next__/loader.component"; -import Box from "../box"; const styledSystemProps = generateStyledSystemProps({ margin: true, @@ -16,6 +14,22 @@ const meta: Meta = { subcomponents: { ButtonToggle }, argTypes: { ...styledSystemProps, + label: { + control: "text", + }, + inputHint: { + control: "text", + }, + fullWidth: { + control: "boolean", + }, + allowDeselect: { + control: "boolean", + }, + size: { + options: ["small", "medium", "large"], + control: { type: "radio" }, + }, }, parameters: { themeProvider: { chromatic: { theme: "sage" } }, @@ -28,55 +42,41 @@ const meta: Meta = { export default meta; type Story = StoryObj; -const ControlledButtonToggleGroup = ({ - id = "default", - children, - value, - ...args -}: Omit) => { - const [selectedButton, setSelectedButton] = useState(value); - - const handleOnChange = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setSelectedButton(selectedValue as string); - }; +export const Playground: Story = { + render: (args) => { + const [selectedButton, setSelectedButton] = useState("playground-2"); - return ( - - Button 1 - Button 2 - Button 3 - - ); -}; + const handleOnChange = ( + ev: React.MouseEvent, + selectedValue?: string, + ) => { + setSelectedButton(selectedValue as string); + }; -export const Default: Story = { - render: ControlledButtonToggleGroup, - args: { - "aria-label": "Button Toggle Group", - value: "default-2", - }, - parameters: { - chromatic: { disableSnapshot: true }, + return ( + + Button 1 + Button 2 + Button 3 + + ); }, -}; - -export const WithLabelAndHint: Story = { - ...Default, args: { - id: "with-label", label: "Label", - inputHint: "Hint Text", - value: "with-label-2", + inputHint: "", + fullWidth: false, + allowDeselect: false, + disabled: false, + size: "medium", + inputWidth: 100, }, }; +Playground.storyName = "Playground"; export const Single: Story = () => { const [isPressed, setIsPressed] = useState(true); @@ -93,63 +93,6 @@ export const Single: Story = () => { }; Single.storyName = "Single"; -export const WithIcon: Story = ({ ...args }: ButtonToggleGroupProps) => { - const [leftValue, setLeftValue] = useState("icon-left-1"); - const [rightValue, setRightValue] = useState("icon-right-1"); - - const handleOnChangeLeft = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setLeftValue(selectedValue as string); - }; - - const handleOnChangeRight = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setRightValue(selectedValue as string); - }; - - return ( - <> - - - - Button 1 - - - - Button 2 - - - - - Button 1 - - - - Button 2 - - - - - ); -}; -WithIcon.storyName = "With Icon"; - export const Loading: Story = ({ ...args }: ButtonToggleGroupProps) => { const [value, setValue] = useState(""); @@ -183,279 +126,3 @@ export const Loading: Story = ({ ...args }: ButtonToggleGroupProps) => { ); }; Loading.storyName = "Loading"; - -export const SizesGrouped: Story = ({ ...args }: ButtonToggleGroupProps) => { - const [valueSmall, setValueSmall] = useState("small-2"); - const [valueMedium, setValueMedium] = useState("medium-2"); - const [valueLarge, setValueLarge] = useState("large-2"); - - const handleOnChangeSmall = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueSmall(selectedValue as string); - }; - - const handleOnChangeMedium = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueMedium(selectedValue as string); - }; - - const handleOnChangeLarge = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueLarge(selectedValue as string); - }; - - return ( - <> - - Button 1 - Button 2 - Button 3 - - - - - - Button 1 - - - - Button 2 - - - - Button 3 - - - - - - - Button 1 - - - - Button 2 - - - - Button 3 - - - - ); -}; -SizesGrouped.storyName = "Sizes - Grouped"; - -export const SizesSingle: Story = () => { - const [isPressedSmall, setIsPressedSmall] = useState(true); - const [isPressedMedium, setIsPressedMedium] = useState(true); - const [isPressedLarge, setIsPressedLarge] = useState(true); - - const handleClickSmall = () => { - setIsPressedSmall(!isPressedSmall); - }; - - const handleClickMedium = () => { - setIsPressedMedium(!isPressedMedium); - }; - - const handleClickLarge = () => { - setIsPressedLarge(!isPressedLarge); - }; - - return ( - - - Small ButtonToggle - - - Medium ButtonToggle - - - Large ButtonToggle - - - ); -}; -SizesSingle.storyName = "Sizes - Single"; - -export const IconOnlyGrouped: Story = ({ ...args }: ButtonToggleGroupProps) => { - const [valueMedium, setValueMedium] = useState("medium-2"); - const [valueLarge, setValueLarge] = useState("large-2"); - - const handleOnChangeMedium = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueMedium(selectedValue as string); - }; - - const handleOnChangeLarge = ( - ev: React.MouseEvent, - selectedValue?: string, - ) => { - setValueLarge(selectedValue as string); - }; - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; -IconOnlyGrouped.storyName = "Icon Only - Grouped"; - -export const IconOnlySingle: Story = () => { - const [isPressedSmall, setIsPressedSmall] = useState(true); - const [isPressedMedium, setIsPressedMedium] = useState(true); - const [isPressedLarge, setIsPressedLarge] = useState(true); - - const handleClickSmall = () => { - setIsPressedSmall(!isPressedSmall); - }; - - const handleClickMedium = () => { - setIsPressedMedium(!isPressedMedium); - }; - - const handleClickLarge = () => { - setIsPressedLarge(!isPressedLarge); - }; - - return ( - - - - - - - - - - - - ); -}; -IconOnlySingle.storyName = "Icon Only - Single"; - -export const AllowDeselect: Story = { - ...Default, - args: { - id: "allow-deselect", - value: "allow-deselect-2", - allowDeselect: true, - }, - parameters: { - chromatic: { disableSnapshot: true }, - }, -}; - -export const FullWidth: Story = { - ...Default, - args: { - id: "full-width", - value: "full-width-2", - fullWidth: true, - }, -}; - -export const Disabled: Story = { - ...Default, - args: { - id: "disabled", - label: "Disabled", - inputHint: "Hint Text", - value: "disabled-2", - disabled: true, - }, -}; From 6b0140310c1226eb2c41985d97878d7b45c1f96b Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:31 +0100 Subject: [PATCH 04/19] docs(button): add playground example --- skills/carbon-react/components/button.md | 256 ------------------ .../button/__next__/button-test.stories.tsx | 192 +++++++++++++ src/components/button/__next__/button.mdx | 146 +++------- .../button/__next__/button.stories.tsx | 255 +++++------------ 4 files changed, 288 insertions(+), 561 deletions(-) diff --git a/skills/carbon-react/components/button.md b/skills/carbon-react/components/button.md index 3abb2f015e..56c4e2c278 100644 --- a/skills/carbon-react/components/button.md +++ b/skills/carbon-react/components/button.md @@ -76,46 +76,6 @@ description: Carbon Button component props and usage examples. | target | string \| undefined | No | | Yes | HTML target attribute | | | ## Examples -### Default - -**Render** - -```tsx -(args: ButtonProps) => { - return ; -} -``` - - -### Button Content - -**Render** - -```tsx -() => { - return ( - - - - - - ); -} -``` - - ### Click Handler **Render** @@ -132,222 +92,6 @@ description: Carbon Button component props and usage examples. ``` -### Variations - -**Render** - -```tsx -(args: ButtonProps) => { - return ( - - -

Default

-

Primary

- <> - - - - -
- ); -} -``` - - -### Disabled - -**Render** - -```tsx -() => { - return ; -} -``` - - -### Full-Width - -**Render** - -```tsx -() => { - return ( - - -
-
- -
- ); -} -``` - - -### Inverse - -**Render** - -```tsx -() => { - return ( - - - - - - - ); -} -``` - - -### Loading - -**Render** - -```tsx -() => { - return ( - - - - - ); -} -``` - - -### Wrapping Text - -**Render** - -```tsx -() => { - return ( - - - - - ); -} -``` - - -### HTML Button Types - -**Render** - -```tsx -() => { - return ( - - - - - - ); -} -``` - - -### As a Link - -**Render** - -```tsx -() => { - return ( - - - - - ); -} -``` - - ### Programmatic Focus **Render** diff --git a/src/components/button/__next__/button-test.stories.tsx b/src/components/button/__next__/button-test.stories.tsx index cbb8b179ac..f251c5eb8b 100644 --- a/src/components/button/__next__/button-test.stories.tsx +++ b/src/components/button/__next__/button-test.stories.tsx @@ -1064,3 +1064,195 @@ HoverStates.parameters = { "[data-role='primary'], [data-role='secondary'], [data-role='tertiary'], [data-role='subtle'], [data-role='primary-destructive'], [data-role='secondary-destructive'], [data-role='gradient-secondary'], [data-role='secondary-xs'], [data-role='tertiary-xs'], [data-role='subtle-xs']", }, }; + +// Documentation regression stories moved from the public docs. + +export const Default: Story = (args: ButtonProps) => { + return ; +}; +Default.storyName = "Default"; + +export const ButtonContent: Story = () => { + return ( + + + + + + ); +}; +ButtonContent.storyName = "Button Content"; + +export const Variations: Story = (args: ButtonProps) => { + return ( + + +

Default

+

Primary

+ <> + + + + +
+ ); +}; +Sizes.storyName = "Sizes"; + +export const Disabled: Story = () => { + return ; +}; +Disabled.storyName = "Disabled"; + +export const FullWidth: Story = () => { + return ( + + +
+
+ +
+ ); +}; +FullWidth.storyName = "Full-Width"; + +export const Inverse: Story = () => { + return ( + + + + + + + ); +}; +Inverse.storyName = "Inverse"; + +export const DocumentationLoading: Story = () => { + return ( + + + + + ); +}; +DocumentationLoading.storyName = "DocumentationLoading"; + +export const WrappingText: Story = () => { + return ( + + + + + ); +}; +WrappingText.storyName = "Wrapping Text"; + +export const HTMLButtonType: Story = () => { + return ( + + + + + + ); +}; +HTMLButtonType.storyName = "HTML Button Types"; + +export const ButtonAsALink: Story = () => { + return ( + + + + + ); +}; +ButtonAsALink.storyName = "As a Link"; diff --git a/src/components/button/__next__/button.mdx b/src/components/button/__next__/button.mdx index 666b5664d9..baeee2ab91 100644 --- a/src/components/button/__next__/button.mdx +++ b/src/components/button/__next__/button.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as ButtonStories from "./button.stories"; @@ -33,24 +33,31 @@ Use it to submit a form (Save), to advance to the next step in a process (Next), import Button, { type ButtonProps } from "carbon-react/lib/components/button/__next__"; ``` -## Examples - -### Default Button - -By default, a `Button` with no additional properties pass will render a default -primary button, with the `button` HTML type. - - - -### Button Content - -Button content is composable; in other words, it's up to you to decide what you -want the button to show, and how. In the following example, you can see how to -implement [Icons](../?path=/story/icon--docs) into buttons. If you have multiple -child elements, wrap them in a `Fragment` to ensure that the correct styling is -maintained. +## Playground + +Use this interactive example to explore Button props with Storybook controls. + + + + - +## Examples ### Click Handler @@ -59,99 +66,6 @@ enable actions to take place when users click the button. -### Variants - -Buttons come in three variants: `default`, `destructive` and `gradient`. To set your -desired variant, pass the `variant` prop; if no value is passed, `default` is -used. - -Similarly, variants can be one of up to four variant types: `primary`, -`secondary`, `tertiary` and `subtle`. Supported variant-type combinations are -as follows: - -| Variant | Primary | Secondary | Tertiary | Subtle | -| ------------- | ------- | --------- | -------- | ------ | -| `default` | ✅ | ✅ | ✅ | ✅ | -| `destructive` | ✅ | ✅ | ❌ | ❌ | -| `gradient` | ❌ | ✅ | ❌ | ❌ | - -By default, the `primary` variant type will be used if no `variantType` property -is passed - - - -### Sizes - -Buttons can be one of four sizes: `xs`, `small`, `medium` and `large`. Pass the -required size with the `size` property. - -#### A note on the `xs` size - -The `xs` button size is designed for use only in tables where spacing is limited; -**it is not meant for use beyond that**. XS buttons cannot use the `primary` -variant type, and will instead default to `secondary` if no variant is specified. -If `primary` is set, it will be ignored automatically. XS buttons should also use -only text content (i.e. no icons). - - - -### Disabled - -You can disable buttons using the `disabled` property. Before doing that, think -about whether a disabled button really makes sense for your situation — it's -best to save this option for times when other approaches won’t quite do the job. - - - -### Full-Width - -Passing the `fullWidth` property will allow buttons to use the full horizontal -space around them. - - - -### Inverse - -Buttons can be marked as `inverse` for scenarios where they require a dark mode -appearance. Only the `default` variant supports `inverse`; other variants will -be ignored. - - - -### Loading - -Loading state can be applied to buttons by passing the [Loader component](?path=/docs/loader--docs) -via the composable `children` prop. - - - -### Wrapping Text - -If you require button text that does not wrap (due to e.g. limited spacing), -use the `noWrap` prop to prevent the text from wrapping across multiple -lines. - - - -### HTML Button Type - -Standard HTML buttons can be one of three types: `button`, `submit` or -`reset`. This functionality is available via the `type` prop, which -allows `Button` instances to be used as standard buttons in elements -such as forms. - - - -### Button as Link - -It is possible to use the `Button` component as a link by passing an `href` prop. -When doing so, the component will render as an anchor tag (``) rather than a button (`; -}; -Default.storyName = "Default"; - -export const ButtonContent: Story = () => { - return ( - - - - - - ); +export const Playground: Story = { + render: (args: ButtonProps) => , + args: { + children: "Button", + variant: "default", + variantType: "primary", + size: "medium", + disabled: false, + fullWidth: false, + inverse: false, + noWrap: true, + type: "button", + href: undefined, + target: undefined, + rel: undefined, + }, + decorators: [ + (Story, { args }) => ( + + + + ), + ], }; -ButtonContent.storyName = "Button Content"; +Playground.storyName = "Playground"; export const ClickHandler: Story = () => { const [value, setValue] = useState(0); @@ -68,168 +105,6 @@ export const ClickHandler: Story = () => { }; ClickHandler.storyName = "Click Handler"; -export const Variations: Story = (args: ButtonProps) => { - return ( - - -

Default

-

Primary

- <> - - - - -
- ); -}; -Sizes.storyName = "Sizes"; - -export const Disabled: Story = () => { - return ; -}; -Disabled.storyName = "Disabled"; - -export const FullWidth: Story = () => { - return ( - - -
-
- -
- ); -}; -FullWidth.storyName = "Full-Width"; - -export const Inverse: Story = () => { - return ( - - - - - - - ); -}; -Inverse.storyName = "Inverse"; - -export const Loading: Story = () => { - return ( - - - - - ); -}; -Loading.storyName = "Loading"; - -export const WrappingText: Story = () => { - return ( - - - - - ); -}; -WrappingText.storyName = "Wrapping Text"; - -export const HTMLButtonType: Story = () => { - return ( - - - - - - ); -}; -HTMLButtonType.storyName = "HTML Button Types"; - -export const ButtonAsALink: Story = () => { - return ( - - - - - ); -}; -ButtonAsALink.storyName = "As a Link"; - export const ProgrammaticFocus: Story = () => { const buttonRef = useRef(null); From 95afc2064497aff40cd5fb47ee109e19528bfb37 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:38 +0100 Subject: [PATCH 05/19] docs(checkbox): add playground example --- skills/carbon-react/components/checkbox.md | 55 +++--- .../checkbox/checkbox-test.stories.tsx | 141 ++++++++++++++- src/components/checkbox/checkbox.mdx | 67 +++---- src/components/checkbox/checkbox.stories.tsx | 168 +++++------------- 4 files changed, 237 insertions(+), 194 deletions(-) diff --git a/skills/carbon-react/components/checkbox.md b/skills/carbon-react/components/checkbox.md index b9ccf7b96a..616564bb3d 100644 --- a/skills/carbon-react/components/checkbox.md +++ b/skills/carbon-react/components/checkbox.md @@ -588,7 +588,7 @@ description: Carbon Checkbox component props and usage examples. **Render** ```tsx -ControlledCheckbox +DocumentationControlledCheckbox ``` @@ -646,19 +646,6 @@ ControlledCheckbox ``` -### ProgressiveDisclosure - -**Args** - -```tsx -{ - ...WithLabel.args, - checked: true, - progressiveDisclosure: , - } -``` - - ### Indeterminate State **Render** @@ -713,44 +700,62 @@ ControlledCheckbox ``` -### WithCustomLabel +### Required **Args** ```tsx { - label: , + ...WithLabel.args, + required: true, } ``` -**Render** + +### Disabled + +**Args** ```tsx -ControlledCheckbox +{ + ...WithInputHint.args, + required: true, + disabled: true, + } ``` -### Required +### ProgressiveDisclosure **Args** ```tsx { - ...WithLabel.args, - required: true, + label: "Checkbox", + progressiveDisclosure: , } ``` +**Render** -### Disabled +```tsx +ControlledCheckbox +``` + + +### WithCustomLabel **Args** ```tsx { - ...WithInputHint.args, - required: true, - disabled: true, + label: , } ``` +**Render** + +```tsx +ControlledCheckbox +``` + diff --git a/src/components/checkbox/checkbox-test.stories.tsx b/src/components/checkbox/checkbox-test.stories.tsx index 6f048c7d0e..4975c4d947 100644 --- a/src/components/checkbox/checkbox-test.stories.tsx +++ b/src/components/checkbox/checkbox-test.stories.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { Meta, StoryObj } from "@storybook/react-vite"; import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props"; -import { Checkbox, CheckboxProps } from "."; +import { Checkbox, CheckboxGroup, CheckboxProps } from "."; import Box from "../box"; import Textbox from "../textbox"; import Icon from "../icon"; @@ -254,3 +254,142 @@ export const IndeterminateSizesWithFocus: Story = { }, }, }; + +// Documentation regression stories moved from the public docs. + +const DocumentationControlledCheckbox = ({ + ...args +}: Omit) => { + const [isChecked, setIsChecked] = useState(false); + + return ( + setIsChecked(e.target.checked)} + {...args} + /> + ); +}; + +export const WithLabel: Story = { + render: DocumentationControlledCheckbox, + args: { + label: "Checkbox", + }, +}; + +export const WithInputHint: Story = { + ...WithLabel, + args: { + ...WithLabel.args, + inputHint: "Input Hint", + }, +}; + +export const Sizes: Story = () => { + const [checkedSmall, setCheckedSmall] = useState(false); + const [checkedMedium, setCheckedMedium] = useState(false); + const [checkedLarge, setCheckedLarge] = useState(false); + + return ( + + { + setCheckedSmall(!checkedSmall); + }} + /> + { + setCheckedMedium(!checkedMedium); + }} + /> + { + setCheckedLarge(!checkedLarge); + }} + /> + + ); +}; +Sizes.storyName = "Sizes"; + +export const IndeterminateState: Story = () => { + const [items, setItems] = useState([ + { id: "checkbox-1", label: "Checkbox 1", checked: true }, + { id: "checkbox-2", label: "Checkbox 2", checked: false }, + { id: "checkbox-3", label: "Checkbox 3", checked: false }, + ]); + + const checkedCount = items.filter((item) => item.checked).length; + const allChecked = checkedCount === items.length; + const someChecked = checkedCount > 0 && checkedCount < items.length; + const controlledIds = items.map((item) => item.id).join(" "); + + const handleSelectAll = () => { + const newChecked = !allChecked; + setItems(items.map((item) => ({ ...item, checked: newChecked }))); + }; + + const handleChange = (id: string, checked: boolean) => { + setItems( + items.map((item) => (item.id === id ? { ...item, checked } : item)), + ); + }; + + return ( + <> + + + {items.map((item) => ( + handleChange(item.id, ev.target.checked)} + /> + ))} + + + ); +}; +IndeterminateState.storyName = "Indeterminate State"; + +export const Required: Story = { + ...WithLabel, + args: { + ...WithLabel.args, + required: true, + }, +}; + +export const Disabled: Story = { + ...WithInputHint, + args: { + ...WithInputHint.args, + required: true, + disabled: true, + }, +}; + +WithLabel.parameters = { chromatic: { disableSnapshot: true } }; +WithInputHint.parameters = { chromatic: { disableSnapshot: true } }; +Sizes.parameters = { chromatic: { disableSnapshot: true } }; +IndeterminateState.parameters = { chromatic: { disableSnapshot: true } }; +Required.parameters = { chromatic: { disableSnapshot: true } }; +Disabled.parameters = { chromatic: { disableSnapshot: true } }; diff --git a/src/components/checkbox/checkbox.mdx b/src/components/checkbox/checkbox.mdx index a4931590f9..4471157aca 100644 --- a/src/components/checkbox/checkbox.mdx +++ b/src/components/checkbox/checkbox.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as CheckboxStories from "./checkbox.stories"; @@ -31,6 +31,26 @@ Checkbox provides a way to check an individual option are selected, or check mul import { Checkbox } from "carbon-react/lib/components/checkbox"; ``` +## Playground + +Use this interactive example to explore Checkbox props with Storybook controls. + + + + + ## Related Components - Choosing one option from a longer list? [Try Radio Button](../?path=/docs/radio-button--docs). @@ -39,27 +59,9 @@ import { Checkbox } from "carbon-react/lib/components/checkbox"; ## Examples -### With Label - -Pass the `label` prop to `Checkbox` to set a label. - - - -### With Input Hint - -To add a hint to a `Checkbox`, pass the `inputHint` prop. - - - -### Sizes - -The size of the component can be changed by passing the `size` prop to `Checkbox`. Available options are `small`, `medium`, and `large`. - - - ### Progressive Disclosure -`Checkbox` can be used to conditionally reveal additional content when selected. +`Checkbox` can be used to conditionally reveal additional content when selected. This can be achieved by passing the content to the `progressiveDisclosure` prop in `Checkbox`, which accepts any valid ReactNode. It is recommended that you provide limited amounts of content within conditionally revealed sections, if you need to disclose larger amounts of content, please consider using a different pattern. @@ -68,39 +70,16 @@ It is recommended that you provide limited amounts of content within conditional -### Indeterminate State - -The indeterminate state is useful for parent-child selection patterns, such as a "Select All" checkbox that controls a list of related checkboxes. -Set the parent checkbox's `indeterminate` prop to `true` when some, but not all, child checkboxes are selected. - -When using this pattern, you should also set `aria-controls` on the parent checkbox to a space-separated list of the child checkbox IDs so assistive technologies can understand the relationship. - - - ### With Custom Labels The `label` prop in `Checkbox` accepts a ReactNode, which allows for custom content to be displayed as the label. -### Required - -`Checkbox` can be marked as required by passing the `required` prop. - - - -### Disabled - -`Checkbox` can be disabled using the `disabled` prop. - -**Please Note**: Even though Carbon does support disabled checkboxes, their use is not generally recommended by Design System. - - - ## Props ### Checkbox **Any other supplied props in `Checkbox` will be provided to the underlying HTML input element** - \ No newline at end of file + diff --git a/src/components/checkbox/checkbox.stories.tsx b/src/components/checkbox/checkbox.stories.tsx index 0ea3eef79e..b8d079dd32 100644 --- a/src/components/checkbox/checkbox.stories.tsx +++ b/src/components/checkbox/checkbox.stories.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Meta, StoryObj } from "@storybook/react-vite"; -import { Checkbox, CheckboxProps, CheckboxGroup } from "."; +import { Checkbox, CheckboxProps } from "."; import Box from "../box"; import Textbox from "../textbox"; import Icon from "../icon"; @@ -15,6 +15,19 @@ const meta = { component: Checkbox, argTypes: { ...styledSystemProps, + label: { + control: "text", + }, + disabled: { + control: "boolean", + }, + required: { + control: "boolean", + }, + size: { + options: ["small", "medium", "large"], + control: { type: "radio" }, + }, }, parameters: { chromatic: { disableSnapshot: true }, @@ -27,6 +40,30 @@ const meta = { export default meta; type Story = StoryObj; +export const Playground: Story = { + render: (args) => { + const [isChecked, setIsChecked] = useState(false); + return ( + setIsChecked(e.target.checked)} + /> + ); + }, + args: { + label: "Checkbox", + inputHint: "Hint text", + disabled: false, + required: false, + size: "medium", + indeterminate: false, + error: "", + progressiveDisclosure: "Additional content shown when checked", + }, +}; +Playground.storyName = "Playground"; + const ControlledCheckbox = ({ ...args }: Omit) => { @@ -41,57 +78,6 @@ const ControlledCheckbox = ({ ); }; -export const WithLabel: Story = { - render: ControlledCheckbox, - args: { - label: "Checkbox", - }, -}; - -export const WithInputHint: Story = { - ...WithLabel, - args: { - ...WithLabel.args, - inputHint: "Input Hint", - }, -}; - -export const Sizes: Story = () => { - const [checkedSmall, setCheckedSmall] = useState(false); - const [checkedMedium, setCheckedMedium] = useState(false); - const [checkedLarge, setCheckedLarge] = useState(false); - - return ( - - { - setCheckedSmall(!checkedSmall); - }} - /> - { - setCheckedMedium(!checkedMedium); - }} - /> - { - setCheckedLarge(!checkedLarge); - }} - /> - - ); -}; -Sizes.storyName = "Sizes"; - const DisclosedContent = () => { const [textboxValue, setTextboxValue] = useState(""); @@ -106,63 +92,6 @@ const DisclosedContent = () => { ); }; -export const ProgressiveDisclosure: Story = { - ...WithLabel, - args: { - ...WithLabel.args, - checked: true, - progressiveDisclosure: , - }, -}; - -export const IndeterminateState: Story = () => { - const [items, setItems] = useState([ - { id: "checkbox-1", label: "Checkbox 1", checked: true }, - { id: "checkbox-2", label: "Checkbox 2", checked: false }, - { id: "checkbox-3", label: "Checkbox 3", checked: false }, - ]); - - const checkedCount = items.filter((item) => item.checked).length; - const allChecked = checkedCount === items.length; - const someChecked = checkedCount > 0 && checkedCount < items.length; - const controlledIds = items.map((item) => item.id).join(" "); - - const handleSelectAll = () => { - const newChecked = !allChecked; - setItems(items.map((item) => ({ ...item, checked: newChecked }))); - }; - - const handleChange = (id: string, checked: boolean) => { - setItems( - items.map((item) => (item.id === id ? { ...item, checked } : item)), - ); - }; - - return ( - <> - - - {items.map((item) => ( - handleChange(item.id, ev.target.checked)} - /> - ))} - - - ); -}; -IndeterminateState.storyName = "Indeterminate State"; - const CustomLabel = () => ( <> @@ -170,26 +99,17 @@ const CustomLabel = () => ( ); -export const WithCustomLabel: Story = { +export const ProgressiveDisclosure: Story = { render: ControlledCheckbox, args: { - label: , - }, -}; - -export const Required: Story = { - ...WithLabel, - args: { - ...WithLabel.args, - required: true, + label: "Checkbox", + progressiveDisclosure: , }, }; -export const Disabled: Story = { - ...WithInputHint, +export const WithCustomLabel: Story = { + render: ControlledCheckbox, args: { - ...WithInputHint.args, - required: true, - disabled: true, + label: , }, }; From cb0d8dbc3a41620849f84fe869ebc4f59eb57d76 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:43 +0100 Subject: [PATCH 06/19] docs(decimal): add playground example --- .../decimal/decimal-test.stories.tsx | 194 +++++++++++++++++- src/components/decimal/decimal.mdx | 100 +++------ src/components/decimal/decimal.stories.tsx | 189 +++-------------- 3 files changed, 246 insertions(+), 237 deletions(-) diff --git a/src/components/decimal/decimal-test.stories.tsx b/src/components/decimal/decimal-test.stories.tsx index 2e28afd79a..aa69f52629 100644 --- a/src/components/decimal/decimal-test.stories.tsx +++ b/src/components/decimal/decimal-test.stories.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { action } from "storybook/actions"; -import { StoryFn } from "@storybook/react-vite"; +import { StoryFn, StoryObj } from "@storybook/react-vite"; import Decimal, { CustomEvent, DecimalProps } from "./decimal.component"; import Box from "../box"; @@ -289,3 +289,195 @@ PopoverContainerSizeControlled.argTypes = { control: { type: "select" }, }, }; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +/* TODO: we really need a better of having a reusable default story that can show state + * I've checked how it used to be and you couldn't see the state setting at that point either + * I've put a message on the Storybook Discord but it's been ignored so will need to chase or ask on git */ +export const DefaultStory: Story = { + render: (args: DecimalProps) => { + const [state, setState] = useState("0.01"); + const setValue = ({ target }: CustomEvent) => { + setState(target.value.rawValue); + }; + return ; + }, + args: { label: "Decimal", required: true }, + name: "Default", +}; + +export const Sizes: Story = () => { + const [state, setState] = useState({ + small: "0.01", + medium: "0.01", + large: "0.01", + }); + + const handleChange = (size: DecimalProps["size"]) => (e: CustomEvent) => { + setState({ ...state, [size || "small"]: e.target.value.rawValue }); + }; + + return (["small", "medium", "large"] as const).map((size) => ( + + )); +}; +Sizes.storyName = "Sizes"; + +export const Disabled: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, disabled: true }, + name: "Disabled", +}; + +export const Prefix: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, prefix: "£", maxWidth: "20%" }, + name: "Prefix", +}; + +export const Suffix: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, suffix: "kg", maxWidth: "20%" }, + name: "Suffix", +}; + +export const WithPopoverPosition: Story = { + render: (args: DecimalProps) => { + const [state, setState] = useState("0.01"); + const [selectValue, setSelectValue] = useState("1"); + const setValue = ({ target }: CustomEvent) => { + setState(target.value.rawValue); + }; + return ( + + +
+ } + /> + ); + }, + args: { label: "Decimal", maxWidth: "40%", popoverPosition: "left" }, + argTypes: { + popoverPosition: { + options: ["left", "right", "center"], + control: { type: "select" }, + }, + }, + name: "With Popover Position", +}; + +export const ReadOnly: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, readOnly: true }, + name: "Read Only", +}; + +export const Empty: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, allowEmptyValue: true }, + name: "Empty", +}; +Empty.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const WithCustomPrecision: Story = () => { + const [state, setState] = useState("0.0001"); + const setValue = ({ target }: CustomEvent) => { + setState(target.value.rawValue); + }; + return ( + + ); +}; +WithCustomPrecision.storyName = "With Custom Precision"; + +export const LabelInline: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, labelInline: true }, + parameters: { chromatic: { disableSnapshot: true } }, + name: "Label Inline", +}; + +export const WithCustomMaxWidth: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, maxWidth: "50%" }, + name: "With Custom Max Width", +}; + +export const WithFieldHelp: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, fieldHelp: "Help" }, + name: "With Field Help", +}; + +export const WithInputHint: Story = { + ...DefaultStory, + args: { + ...DefaultStory.args, + inputHint: "Hint text (optional).", + helpAriaLabel: "Help", + }, + name: "With Input Hint", +}; + +export const Required: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, required: true, helpAriaLabel: "Help" }, + name: "Required", +}; +Required.parameters = { + chromatic: { disableSnapshot: true }, +}; + +export const LeftAligned: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, required: true, align: "left" }, + name: "Left Aligned", +}; + +DefaultStory.parameters = { chromatic: { disableSnapshot: false } }; +Sizes.parameters = { chromatic: { disableSnapshot: false } }; +Disabled.parameters = { chromatic: { disableSnapshot: false } }; +Prefix.parameters = { chromatic: { disableSnapshot: false } }; +Suffix.parameters = { chromatic: { disableSnapshot: false } }; +WithPopoverPosition.parameters = { chromatic: { disableSnapshot: false } }; +ReadOnly.parameters = { chromatic: { disableSnapshot: false } }; +WithCustomPrecision.parameters = { chromatic: { disableSnapshot: false } }; +WithCustomMaxWidth.parameters = { chromatic: { disableSnapshot: false } }; +WithFieldHelp.parameters = { chromatic: { disableSnapshot: false } }; +WithInputHint.parameters = { chromatic: { disableSnapshot: false } }; +LeftAligned.parameters = { chromatic: { disableSnapshot: false } }; diff --git a/src/components/decimal/decimal.mdx b/src/components/decimal/decimal.mdx index 7389646e63..3cf58c7c28 100644 --- a/src/components/decimal/decimal.mdx +++ b/src/components/decimal/decimal.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as DecimalStories from "./decimal.stories"; @@ -32,81 +32,41 @@ Captures a number with a decimal point, or a currency value. import Decimal from "carbon-react/lib/components/decimal"; ``` -## Examples - -### Default - - - -### With Input Hint - - - -### Sizes - - - -### Disabled - - - -### Prefix - - - -### Suffix - - +## Playground + +Use this interactive example to explore Decimal props with Storybook controls. + + + + -**Note:** `prefix` takes precedence over `suffix`. +## Examples ### With Popover Container Use the `popoverContainerContent` prop to render content inside a [PopoverContainer](../?path=/docs/popover-container--docs) accessible via a button at the right of the input. Use the `popoverPosition` prop to control the position of the popover dialog relative to the trigger button. - If both props are supplied, only the prefix will be rendered and a console warning will be logged. Use one or the other. - -### ReadOnly - - - -### Empty - - - -### With Custom Precision - - - -### With custom maxWidth - - - -### Required - -You can use the `required` prop to indicate if the field is mandatory. - - - -### Left aligned - -You can use the `align` prop to choose how the the characters inside the component align. -In this example, `align` has been assigned the value `left`. - - - -### With labelInline - -Use the `labelInline` prop to display the label on the same horizontal row as the input. You can adjust its appearance using the `labelWidth` and -`labelAlign` props to control the width and text alignment of the label and the `inputWidth` prop to control the width of the input. - - - -### With fieldHelp (legacy) - -**Note:** This is a legacy feature and will only render if the `validationRedesignOptIn` feature flag on an ancestor [CarbonProvider](../?path=/docs/carbon-provider--docs) is *false*. - - + ## Validation States diff --git a/src/components/decimal/decimal.stories.tsx b/src/components/decimal/decimal.stories.tsx index 61fe5e18ce..675c9718e2 100644 --- a/src/components/decimal/decimal.stories.tsx +++ b/src/components/decimal/decimal.stories.tsx @@ -15,16 +15,16 @@ const meta: Meta = { component: Decimal, argTypes: { ...styledSystemProps, + precision: { + control: { type: "number" }, + }, }, }; export default meta; type Story = StoryObj; -/* TODO: we really need a better of having a reusable default story that can show state - * I've checked how it used to be and you couldn't see the state setting at that point either - * I've put a message on the Storybook Discord but it's been ignored so will need to chase or ask on git */ -export const DefaultStory: Story = { +export const Playground: Story = { render: (args: DecimalProps) => { const [state, setState] = useState("0.01"); const setValue = ({ target }: CustomEvent) => { @@ -32,51 +32,26 @@ export const DefaultStory: Story = { }; return ; }, - args: { label: "Decimal", required: true }, - name: "Default", -}; - -export const Sizes: Story = () => { - const [state, setState] = useState({ - small: "0.01", - medium: "0.01", - large: "0.01", - }); - - const handleChange = (size: DecimalProps["size"]) => (e: CustomEvent) => { - setState({ ...state, [size || "small"]: e.target.value.rawValue }); - }; - - return (["small", "medium", "large"] as const).map((size) => ( - - )); -}; -Sizes.storyName = "Sizes"; - -export const Disabled: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, disabled: true }, - name: "Disabled", -}; - -export const Prefix: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, prefix: "£", maxWidth: "20%" }, - name: "Prefix", -}; - -export const Suffix: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, suffix: "kg", maxWidth: "20%" }, - name: "Suffix", + args: { + label: "Decimal", + required: false, + disabled: false, + readOnly: false, + size: "medium", + prefix: "", + suffix: "", + precision: 2, + inputHint: "Hint text", + allowEmptyValue: false, + align: "right", + locale: "en-GB", + inputWidth: 100, + maxWidth: "100%", + labelInline: false, + error: "", + }, }; +Playground.storyName = "Playground"; export const WithPopoverContainer: Story = { render: (args: DecimalProps) => { @@ -119,121 +94,3 @@ export const WithPopoverContainer: Story = { args: { label: "Decimal", maxWidth: "40%" }, name: "With Popover Container", }; - -export const WithPopoverPosition: Story = { - render: (args: DecimalProps) => { - const [state, setState] = useState("0.01"); - const [selectValue, setSelectValue] = useState("1"); - const setValue = ({ target }: CustomEvent) => { - setState(target.value.rawValue); - }; - return ( - - -
- } - /> - ); - }, - args: { label: "Decimal", maxWidth: "40%", popoverPosition: "left" }, - argTypes: { - popoverPosition: { - options: ["left", "right", "center"], - control: { type: "select" }, - }, - }, - name: "With Popover Position", -}; - -export const ReadOnly: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, readOnly: true }, - name: "Read Only", -}; - -export const Empty: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, allowEmptyValue: true }, - name: "Empty", -}; -Empty.parameters = { - chromatic: { disableSnapshot: true }, -}; - -export const WithCustomPrecision: Story = () => { - const [state, setState] = useState("0.0001"); - const setValue = ({ target }: CustomEvent) => { - setState(target.value.rawValue); - }; - return ( - - ); -}; -WithCustomPrecision.storyName = "With Custom Precision"; - -export const LabelInline: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, labelInline: true }, - parameters: { chromatic: { disableSnapshot: true } }, - name: "Label Inline", -}; - -export const WithCustomMaxWidth: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, maxWidth: "50%" }, - name: "With Custom Max Width", -}; - -export const WithFieldHelp: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, fieldHelp: "Help" }, - name: "With Field Help", -}; - -export const WithInputHint: Story = { - ...DefaultStory, - args: { - ...DefaultStory.args, - inputHint: "Hint text (optional).", - helpAriaLabel: "Help", - }, - name: "With Input Hint", -}; - -export const Required: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, required: true, helpAriaLabel: "Help" }, - name: "Required", -}; -Required.parameters = { - chromatic: { disableSnapshot: true }, -}; - -export const LeftAligned: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, required: true, align: "left" }, - name: "Left Aligned", -}; From 068649cc94f0e039379e36817f2c2f91c604851c Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:49 +0100 Subject: [PATCH 07/19] docs(icon): add playground example --- skills/carbon-react/components/icon.md | 121 --------------------- src/components/icon/icon-test.stories.tsx | 127 ++++++++++++++++++++++ src/components/icon/icon.mdx | 53 ++++----- src/components/icon/icon.stories.tsx | 112 ++++++------------- 4 files changed, 183 insertions(+), 230 deletions(-) diff --git a/skills/carbon-react/components/icon.md b/skills/carbon-react/components/icon.md index ddfb546a86..1223efd4f4 100644 --- a/skills/carbon-react/components/icon.md +++ b/skills/carbon-react/components/icon.md @@ -53,127 +53,6 @@ description: Carbon Icon component props and usage examples. | tooltipVisible | boolean \| undefined | No | | Yes | | | | ## Examples -### Default - -**Render** - -```tsx -() => { - return ; -} -``` - - -### Sizes - -**Render** - -```tsx -() => { - return ( - <> - {(["small", "medium", "large"] as const).map((size) => ( - - ))} - - ); -} -``` - - -### Inverse - -**Render** - -```tsx -() => { - return ( - - - - ); -} -``` - - -### Various Background Shapes - -**Render** - -```tsx -() => { - return ( - <> - {(["circle", "rounded-rect", "square"] as const).map((bgShape) => ( - - ))} - - ); -} -``` - - -### Various Background Sizes - -**Render** - -```tsx -() => { - return ( - <> - {(["small", "medium", "large"] as const).map((bgSize) => ( - - ))} - - ); -} -``` - - -### Background Sizes and Font Sizes - -**Render** - -```tsx -() => { - return ( - <> - {(["small", "medium", "large"] as const).map((fontSize) => { - return (["small", "medium", "large"] as const).map((bgSize) => ( - - )); - })} - - ); -} -``` - - -### Color Presets - -**Render** - -```tsx -() => ( - - {ICON_COLOR_TYPES.map((color) => ( - - - {color} - - ))} - -) -``` - - ### List of Icons **Render** diff --git a/src/components/icon/icon-test.stories.tsx b/src/components/icon/icon-test.stories.tsx index 830db615f2..41e37aca78 100644 --- a/src/components/icon/icon-test.stories.tsx +++ b/src/components/icon/icon-test.stories.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { StoryObj } from "@storybook/react-vite"; import { ICONS, @@ -8,6 +9,7 @@ import { ICON_FONT_SIZES, } from "./icon-config"; import Icon, { ICON_COLOR_TYPES } from "."; +import Box from "../box"; export default { title: "Icon/Test", @@ -109,3 +111,128 @@ All.story = { themeProvider: { chromatic: { theme: "sage" } }, }, }; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +export const DocumentationDefault: Story = () => { + return ; +}; +DocumentationDefault.storyName = "DocumentationDefault"; + +export const DocumentationColorPresets: Story = () => ( + + {ICON_COLOR_TYPES.map((color) => ( + + + {color} + + ))} + +); +DocumentationColorPresets.storyName = "Color Presets"; + +export const Sizes: Story = () => { + return ( + <> + {(["small", "medium", "large"] as const).map((size) => ( + + ))} + + ); +}; +Sizes.storyName = "Sizes"; + +export const Inverse: Story = () => { + return ( + + + + ); +}; +Inverse.storyName = "Inverse"; + +export const VariousBgShapes: Story = () => { + return ( + <> + {(["circle", "rounded-rect", "square"] as const).map((bgShape) => ( + + ))} + + ); +}; +VariousBgShapes.storyName = "Various Background Shapes"; + +export const VariousBgSizes: Story = () => { + return ( + <> + {(["small", "medium", "large"] as const).map((bgSize) => ( + + ))} + + ); +}; +VariousBgSizes.storyName = "Various Background Sizes"; + +export const BgSizesAndFontSizes: Story = () => { + return ( + <> + {(["small", "medium", "large"] as const).map((fontSize) => { + return (["small", "medium", "large"] as const).map((bgSize) => ( + + )); + })} + + ); +}; +BgSizesAndFontSizes.storyName = "Background Sizes and Font Sizes"; + +export const CustomColors: Story = () => ( + <> + + + + + + + + + + + + + + + + + + + +); +CustomColors.storyName = "Custom Colors"; +CustomColors.parameters = { + info: { disable: true }, + chromatic: { disableSnapshot: true }, +}; + +DocumentationDefault.parameters = { chromatic: { disableSnapshot: false } }; +DocumentationColorPresets.parameters = { + chromatic: { disableSnapshot: false }, +}; +Sizes.parameters = { chromatic: { disableSnapshot: false } }; +Inverse.parameters = { chromatic: { disableSnapshot: false } }; +VariousBgShapes.parameters = { chromatic: { disableSnapshot: false } }; +VariousBgSizes.parameters = { chromatic: { disableSnapshot: false } }; +BgSizesAndFontSizes.parameters = { chromatic: { disableSnapshot: false } }; diff --git a/src/components/icon/icon.mdx b/src/components/icon/icon.mdx index 95783f58f1..3d96d5901d 100644 --- a/src/components/icon/icon.mdx +++ b/src/components/icon/icon.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as IconStories from "./icon.stories"; @@ -22,8 +22,7 @@ Many other components allow you to specify one of the standard Carbon icons to a ## Contents - [Quick Start](#quick-start) -- [Examples](#examples) -- [Color presets](#color-presets) +- [Playground](#playground) - [Icon naming conventions](#icon-naming-conventions) - [List of icons](#list-of-icons) - [Props](#props) @@ -34,44 +33,34 @@ Many other components allow you to specify one of the standard Carbon icons to a import Icon from "carbon-react/lib/components/icon"; ``` -## Examples +## Playground -### Default +Use this interactive example to explore Icon props with Storybook controls. - +The `color` control supports the semantic presets `neutral`, `subtle`, +`caution`, `info`, `negative`, and `positive`. -### Sizes + -Use the `size` prop to control the Icon's rendered dimensions. - - - -### Inverse - -Use the `inverse` prop when rendering an Icon on a dark background. - - - -### Color presets - -Use the `color` prop to apply one of the supported semantic icon colors: `neutral`, `subtle`, `caution`, `info`, `negative`, or `positive`. - - + ## Icon naming conventions Icons use a `snake_case` naming convention and are organised into the following categories: -| Category | Examples | -|---|---| -| **Navigation** | `arrow_*`, `chevron_*`, `caret_*`, `caret_large_*` | -| **Actions** | `add`, `bin`, `close`, `copy`, `create`, `delete`, `drag`, `download`, `edit`, `export`, `filter`, `search`, `settings`, `upload` | -| **Status** | `alert`, `blocked`, `double_tick`, `error`, `error_square`, `info`, `tick`, `warning` | -| **Communication** | `call`, `chat`, `email`, `fax`, `message` | -| **Finance** | `bank`, `cash`, `coins`, `credit_card`, `euro`, `receipt` | -| **Social / App** | `app_facebook`, `app_instagram`, `app_tiktok`, `app_twitter`, `app_youtube` | -| **Documents & Files** | `attach`, `document_*`, `file_*` | -| **Charts** | `chart_bar`, `chart_bar_arrow_up`, `chart_line`, `chart_pie` | +| Category | Examples | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| **Navigation** | `arrow_*`, `chevron_*`, `caret_*`, `caret_large_*` | +| **Actions** | `add`, `bin`, `close`, `copy`, `create`, `delete`, `drag`, `download`, `edit`, `export`, `filter`, `search`, `settings`, `upload` | +| **Status** | `alert`, `blocked`, `double_tick`, `error`, `error_square`, `info`, `tick`, `warning` | +| **Communication** | `call`, `chat`, `email`, `fax`, `message` | +| **Finance** | `bank`, `cash`, `coins`, `credit_card`, `euro`, `receipt` | +| **Social / App** | `app_facebook`, `app_instagram`, `app_tiktok`, `app_twitter`, `app_youtube` | +| **Documents & Files** | `attach`, `document_*`, `file_*` | +| **Charts** | `chart_bar`, `chart_bar_arrow_up`, `chart_line`, `chart_pie` | ## List of icons diff --git a/src/components/icon/icon.stories.tsx b/src/components/icon/icon.stories.tsx index e1e7daa5c9..f2e1e8934a 100644 --- a/src/components/icon/icon.stories.tsx +++ b/src/components/icon/icon.stories.tsx @@ -17,8 +17,20 @@ const meta: Meta = { component: Icon, argTypes: { ...styledSystemProps, + type: { + options: Object.keys(ICONS), + control: { type: "select" }, + }, + size: { + options: ["small", "medium", "large"], + control: { type: "radio" }, + }, color: { options: ICON_COLOR_TYPES, + control: { type: "select" }, + }, + inverse: { + control: "boolean", }, }, }; @@ -26,84 +38,30 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = () => { - return ; -}; -Default.storyName = "Default"; - -export const Sizes: Story = () => { - return ( - <> - {(["small", "medium", "large"] as const).map((size) => ( - - ))} - - ); -}; -Sizes.storyName = "Sizes"; - -export const Inverse: Story = () => { - return ( - - - - ); -}; -Inverse.storyName = "Inverse"; - -export const VariousBgShapes: Story = () => { - return ( - <> - {(["circle", "rounded-rect", "square"] as const).map((bgShape) => ( - - ))} - - ); -}; -VariousBgShapes.storyName = "Various Background Shapes"; - -export const VariousBgSizes: Story = () => { - return ( - <> - {(["small", "medium", "large"] as const).map((bgSize) => ( - - ))} - - ); -}; -VariousBgSizes.storyName = "Various Background Sizes"; - -export const BgSizesAndFontSizes: Story = () => { - return ( - <> - {(["small", "medium", "large"] as const).map((fontSize) => { - return (["small", "medium", "large"] as const).map((bgSize) => ( - - )); - })} - - ); -}; -BgSizesAndFontSizes.storyName = "Background Sizes and Font Sizes"; - -export const ColorPresets: Story = () => ( - - {ICON_COLOR_TYPES.map((color) => ( - - - {color} +export const Playground: Story = { + render: (args) => , + args: { + type: "add", + size: "medium", + color: "neutral", + inverse: false, + }, + decorators: [ + (Story, { args }) => ( + + - ))} - -); -ColorPresets.storyName = "Color Presets"; + ), + ], +}; +Playground.storyName = "Playground"; export const ListOfIcons: Story = () => { return ( From 4f4a5be7f8ffb168e5fcbeea7c6a1f46cd925098 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:16:56 +0100 Subject: [PATCH 08/19] docs(link): add playground example --- skills/carbon-react/components/link.md | 189 ---------------------- src/components/link/link-test.stories.tsx | 157 ++++++++++++++++++ src/components/link/link.mdx | 86 ++++------ src/components/link/link.stories.tsx | 166 +++---------------- 4 files changed, 207 insertions(+), 391 deletions(-) diff --git a/skills/carbon-react/components/link.md b/skills/carbon-react/components/link.md index 3742c6dce1..67d122013b 100644 --- a/skills/carbon-react/components/link.md +++ b/skills/carbon-react/components/link.md @@ -94,81 +94,6 @@ description: Carbon Link component props and usage examples. | aria-grabbed | Booleanish \| undefined | No | | Yes | in ARIA 1.1 | Indicates an element's "grabbed" state in a drag-and-drop operation. | | ## Examples -### Default - -**Args** - -```tsx -{ - children: "This is an anchor link", - href: "https://carbon.sage.com", - } -``` - -**Render** - -```tsx -(args) => {args.children} -``` - - -### WithUnderlineOnlyOnHover - -**Args** - -```tsx -{ - ...Default.args, - children: "This is an anchor link with an underline applied on hover", - underline: "hover", - } -``` - - -### WithNoUnderline - -**Args** - -```tsx -{ - ...Default.args, - children: "This is an anchor link with no underline", - underline: "never", - } -``` - - -### WithIcon - -**Args** - -```tsx -{ - href: "https://carbon.sage.com", - icon: "settings", - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - Link with left icon - - - - - Link with right icon - - - - ) -``` - - ### As Skip Link **Render** @@ -213,120 +138,6 @@ description: Carbon Link component props and usage examples. ``` -### LinkSize - -**Args** - -```tsx -{ - href: "https://carbon.sage.com", - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - This is a medium link - - - - - This is a large link - - - - ) -``` - - -### Bold - -**Args** - -```tsx -{ - ...Default.args, - children: "This is a bold link", - bold: true, - } -``` - - -### Variants - -**Args** - -```tsx -{ - href: "https://carbon.sage.com", - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - This is a typical link - - - - - This is a negative link - - - - - This is a subtle link - - - - ) -``` - - -### Inverse - -**Args** - -```tsx -{ - href: "https://carbon.sage.com", - inverse: true, - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - This is an inverse typical link - - - - - This is an inverse negative link - - - - - This is an inverse subtle link - - - - ) -``` - - ### With On Click **Render** diff --git a/src/components/link/link-test.stories.tsx b/src/components/link/link-test.stories.tsx index 4c2060f4a1..15e1db953e 100644 --- a/src/components/link/link-test.stories.tsx +++ b/src/components/link/link-test.stories.tsx @@ -323,3 +323,160 @@ LinkComponentWithAnImage.parameters = { focus: '[data-role="target"] a', }, }; + +// Documentation regression stories moved from the public docs. + +export const DocumentationDefault: Story = { + render: (args) => {args.children}, + args: { + children: "This is an anchor link", + href: "https://carbon.sage.com", + }, +}; + +export const WithUnderlineOnlyOnHover: Story = { + ...DocumentationDefault, + args: { + ...DocumentationDefault.args, + children: "This is an anchor link with an underline applied on hover", + underline: "hover", + }, +}; + +export const WithNoUnderline: Story = { + ...DocumentationDefault, + args: { + ...DocumentationDefault.args, + children: "This is an anchor link with no underline", + underline: "never", + }, +}; + +export const WithIcon: Story = { + render: (args) => ( + <> + + + Link with left icon + + + + + Link with right icon + + + + ), + args: { + href: "https://carbon.sage.com", + icon: "settings", + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export const LinkSize: Story = { + render: (args) => ( + <> + + + This is a medium link + + + + + This is a large link + + + + ), + args: { + href: "https://carbon.sage.com", + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export const Bold: Story = { + ...DocumentationDefault, + args: { + ...DocumentationDefault.args, + children: "This is a bold link", + bold: true, + }, +}; + +export const Variants: Story = { + render: (args) => ( + <> + + + This is a typical link + + + + + This is a negative link + + + + + This is a subtle link + + + + ), + args: { + href: "https://carbon.sage.com", + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export const Inverse: Story = { + render: (args) => ( + <> + + + This is an inverse typical link + + + + + This is an inverse negative link + + + + + This is an inverse subtle link + + + + ), + args: { + href: "https://carbon.sage.com", + inverse: true, + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; diff --git a/src/components/link/link.mdx b/src/components/link/link.mdx index 7c1737b73f..6c1053e7f7 100644 --- a/src/components/link/link.mdx +++ b/src/components/link/link.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as LinkStories from "./link.stories"; @@ -35,38 +35,32 @@ import Link from "carbon-react/lib/components/link"; To use Link with a routing library see our documentation on this here: [Usage with routing](../?path=/docs/documentation-usage-with-routing--docs). -## Examples - -### Default - -By default an anchor element will be rendered. - -`target` and `rel` props will be passed down to the anchor element when provided. - - - -### Underline - -By default, the `Link` component will always render with an underline. - -Setting the `underline` prop to `"hover"` will only show the underline when the user hovers over the link. - - - -Setting the `underline` prop to `"never"` removes the underline from the link. - -**Please note**: Without the underline, context must be provided to the user to indicate that the text is a link. - - - -### Icon - -You can render an icon alongside the `Link` text by passing a valid icon to the `icon` prop. -To see the list of available icons, please refer to the [Icon documentation](../?path=/docs/icon--docs). - -You can control the position of the icon using the `iconAlign` prop. +## Playground + +Use this interactive example to explore Link props with Storybook controls. + + + + - +## Examples ### Skip Link @@ -79,37 +73,13 @@ You can use the `link.skipLinkLabel` translation key to override the default lab -### Sizes - -Setting the `linkSize` prop to either `medium` or `large` will change the font size accordingly. - - - -### Bold - -Setting the `bold` prop to true will render the `Link` text in bold. - - - -### Variants - -You can use the `variant` prop to change the visual style of the Link. Available variants are `typical`, `negative` and `subtle`. - - - -### Inverse - -You can use the `inverse` prop to render the `Link` with the inverse color scheme. - - - ### onClick A custom `onClick` function can be passed to the Link component to render it as a button instead of an anchor. **Please note**: using the `onClick` prop with no `href` is bad for accessibility, as the output looks like a link but behaves like an HTML button. -If the `onClick` performs an on-page action, so you want a genuine button, please consider using the [Button component](../?path=/docs/button--docs) instead so that the styles match the semantics. -If the `onClick` function performs navigation, an `href` prop is necessary either instead or in addition, so that an HTML `` tag is used instead. +If the `onClick` performs an on-page action, so you want a genuine button, please consider using the [Button component](../?path=/docs/button--docs) instead so that the styles match the semantics. +If the `onClick` function performs navigation, an `href` prop is necessary either instead or in addition, so that an HTML `` tag is used instead. See for example our [React Router docs](../?path=/docs/documentation-usage-with-routing--docs), where we use `Link` with `onClick` to perform client-side routing, but still provide the `href` to render an HTML link rather than a button. diff --git a/src/components/link/link.stories.tsx b/src/components/link/link.stories.tsx index 8e36b78b52..21c68ff60a 100644 --- a/src/components/link/link.stories.tsx +++ b/src/components/link/link.stories.tsx @@ -18,59 +18,38 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = { +export const Playground: Story = { render: (args) => {args.children}, args: { - children: "This is an anchor link", + children: "This is a link", href: "https://carbon.sage.com", - }, -}; - -export const WithUnderlineOnlyOnHover: Story = { - ...Default, - args: { - ...Default.args, - children: "This is an anchor link with an underline applied on hover", - underline: "hover", - }, -}; - -export const WithNoUnderline: Story = { - ...Default, - args: { - ...Default.args, - children: "This is an anchor link with no underline", - underline: "never", - }, -}; - -export const WithIcon: Story = { - render: (args) => ( - <> - - - Link with left icon - - - - - Link with right icon - - - - ), - args: { - href: "https://carbon.sage.com", - icon: "settings", + underline: "always", + isSkipLink: false, + icon: undefined, + iconAlign: "left", + variant: "typical", + linkSize: "medium", + inverse: false, + bold: false, + target: undefined, + rel: undefined, }, decorators: [ - (Story) => ( - + (Story, { args }) => ( + ), ], }; +Playground.storyName = "Playground"; export const AsSkipLink: Story = () => { return ( @@ -110,107 +89,6 @@ export const AsSkipLink: Story = () => { }; AsSkipLink.storyName = "As Skip Link"; -export const LinkSize: Story = { - render: (args) => ( - <> - - - This is a medium link - - - - - This is a large link - - - - ), - args: { - href: "https://carbon.sage.com", - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export const Bold: Story = { - ...Default, - args: { - ...Default.args, - children: "This is a bold link", - bold: true, - }, -}; - -export const Variants: Story = { - render: (args) => ( - <> - - - This is a typical link - - - - - This is a negative link - - - - - This is a subtle link - - - - ), - args: { - href: "https://carbon.sage.com", - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - -export const Inverse: Story = { - render: (args) => ( - <> - - - This is an inverse typical link - - - - - This is an inverse negative link - - - - - This is an inverse subtle link - - - - ), - args: { - href: "https://carbon.sage.com", - inverse: true, - }, - decorators: [ - (Story) => ( - - - - ), - ], -}; - export const WithOnClick: Story = () => { return ( {}}> From ed70af3f5306600a64eb983017b82b4a465cd64d Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:01 +0100 Subject: [PATCH 09/19] docs(loader): add playground example --- skills/carbon-react/components/loader.md | 315 ------------------ .../loader/__next__/loader-test.stories.tsx | 230 +++++++++++++ src/components/loader/__next__/loader.mdx | 138 ++------ .../loader/__next__/loader.stories.tsx | 279 ++++------------ 4 files changed, 311 insertions(+), 651 deletions(-) diff --git a/skills/carbon-react/components/loader.md b/skills/carbon-react/components/loader.md index e88af11835..fd49f0d15c 100644 --- a/skills/carbon-react/components/loader.md +++ b/skills/carbon-react/components/loader.md @@ -40,321 +40,6 @@ description: Carbon Loader component props and usage examples. | data-role | string \| undefined | No | | Identifier used for testing purposes, applied to the root element of the component. | | ## Examples -### Default - -**Render** - -```tsx -(args: LoaderProps) => ( - - - - ) -``` - - -### Standalone - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Star - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Standalone Sizes - -**Render** - -```tsx -() => ( - <> - - - - - - - - - - - ) -``` - - -### Ring Sizes - -**Render** - -```tsx -() => ( - <> - - - - - - - - - - - - - - ) -``` - - -### Standalone Typical Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Standalone Typical Variant Inversed - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Standalone AI Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Standalone AI Variant Inversed - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring Stacked Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring Stacked Variant Inversed - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring Inline Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring Inline Variant Inversed - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring AI Stacked Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring AI Inline Variant - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Ring AI Inline Variant Inversed - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Is Tracked - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Tracked Error State - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Tracked Success State - -**Render** - -```tsx -() => ( - - - - ) -``` - - -### Animation Time - -**Render** - -```tsx -() => ( - - - - - - ) -``` - - -### Disabled Motion - -**Render** - -```tsx -() => ( - - - - - - ) -``` - - ### Inside Buttons **Render** diff --git a/src/components/loader/__next__/loader-test.stories.tsx b/src/components/loader/__next__/loader-test.stories.tsx index b29becb0e4..8f15d4c3ca 100644 --- a/src/components/loader/__next__/loader-test.stories.tsx +++ b/src/components/loader/__next__/loader-test.stories.tsx @@ -280,3 +280,233 @@ export const InsideButtons: Story = { ), }; InsideButtons.storyName = "Inside Buttons"; + +// Documentation regression stories moved from the public docs. + +export const DocumentationDefault: Story = { + render: (args: LoaderProps) => ( + + + + ), +}; +DocumentationDefault.storyName = "DocumentationDefault"; + +export const Standalone: Story = { + render: () => ( + + + + ), +}; +Standalone.storyName = "Standalone"; + +export const Ring: Story = { + render: () => ( + + + + ), +}; +Ring.storyName = "Ring"; + +export const Star: Story = { + render: () => ( + + + + ), +}; +Star.storyName = "Star"; + +export const StandaloneSizes: Story = { + render: () => ( + <> + + + + + + + + + + + ), +}; +StandaloneSizes.storyName = "Standalone Sizes"; + +export const RingSizes: Story = { + render: () => ( + <> + + + + + + + + + + + + + + ), +}; +RingSizes.storyName = "Ring Sizes"; + +export const StandaloneTypicalVariant: Story = { + render: () => ( + + + + ), +}; +StandaloneTypicalVariant.storyName = "Standalone Typical Variant"; + +export const StandaloneTypicalVariantInversed: Story = { + render: () => ( + + + + ), +}; +StandaloneTypicalVariantInversed.storyName = + "Standalone Typical Variant Inversed"; + +export const StandaloneAiVariant: Story = { + render: () => ( + + + + ), +}; +StandaloneAiVariant.storyName = "Standalone AI Variant"; + +export const StandaloneAiVariantInversed: Story = { + render: () => ( + + + + ), +}; +StandaloneAiVariantInversed.storyName = "Standalone AI Variant Inversed"; + +export const RingStackedVariant: Story = { + render: () => ( + + + + ), +}; +RingStackedVariant.storyName = "Ring Stacked Variant"; + +export const RingStackedVariantInversed: Story = { + render: () => ( + + + + ), +}; +RingStackedVariantInversed.storyName = "Ring Stacked Variant Inversed"; + +export const RingInlineVariant: Story = { + render: () => ( + + + + ), +}; +RingInlineVariant.storyName = "Ring Inline Variant"; + +export const RingInlineVariantInversed: Story = { + render: () => ( + + + + ), +}; +RingInlineVariantInversed.storyName = "Ring Inline Variant Inversed"; + +export const RingAiStackedVariant: Story = { + render: () => ( + + + + ), +}; +RingAiStackedVariant.storyName = "Ring AI Stacked Variant"; + +export const RingAiInlineVariant: Story = { + render: () => ( + + + + ), +}; +RingAiInlineVariant.storyName = "Ring AI Inline Variant"; + +export const RingAiInlineVariantInversed: Story = { + render: () => ( + + + + ), +}; +RingAiInlineVariantInversed.storyName = "Ring AI Inline Variant Inversed"; + +export const RingIsTracked: Story = { + render: () => ( + + + + ), +}; +RingIsTracked.storyName = "Is Tracked"; + +export const ErrorState: Story = { + render: () => ( + + + + ), +}; +ErrorState.storyName = "Tracked Error State"; + +export const SuccessState: Story = { + render: () => ( + + + + ), +}; +SuccessState.storyName = "Tracked Success State"; + +export const AnimationTime: Story = { + render: () => ( + + + + + + ), +}; +AnimationTime.storyName = "Animation Time"; + +export const DisabledMotion: Story = { + render: () => ( + + + + + + ), +}; +DisabledMotion.storyName = "Disabled Motion"; diff --git a/src/components/loader/__next__/loader.mdx b/src/components/loader/__next__/loader.mdx index fd7746772c..f8bbe53077 100644 --- a/src/components/loader/__next__/loader.mdx +++ b/src/components/loader/__next__/loader.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../../.storybook/utils/translation-keys-table"; import * as LoaderStories from "./loader.stories"; @@ -33,120 +33,30 @@ Import `Loader` into the project. import Loader from "carbon-react/lib/components/loader/__next__"; ``` -## Examples - -### Loader Types - -The `Loader` component offers three loading types: `standalone` (default), `ring` and `star`. - -#### Standalone - - - -#### Ring - - - -#### Star - - - -### Loader Variants - -#### Standalone variants - -The `standalone` loader type comes with two variants: `typical` (default) and `ai`. - -#### Typical - - - -#### A.I. - - - -#### Ring variants - -The `ring` loader type comes with four variants: `stacked` (default), `inline`, `ai-stacked` and `ai-inline`. - -#### Stacked - - - -#### Inline - - - -#### A.I. Stacked - - - -#### A.I. Inline - - - -### Loader Sizes - -The `Loader` component offers size flexibility based on the selected loader type: the Standalone loader supports three sizes—Small, Medium, and Large—while the Ring loader provides four options—Extra Small, Small, Medium, and Large. The Star loader type has a single default size, ensuring a consistent appearance across use cases. - -#### Standalone loader sizes - - -#### Ring loader sizes - - - -### Inverse - -You can use the `inverse` prop to render the Loader's standalone and ring types with the inverse color scheme. - -#### Standalone typical inversed - - - -#### Standalone A.I inversed - - - -#### Ring stacked inversed - - - -#### Ring inline inversed - - - -#### Ring A.I inline inversed - - - -### Is Tracked - -When the `isTracked` prop is set to `true`, the ring will become tracked, for specific use cases where wait/loading times are predictable. - - - -#### Success State - -When the loader is tracked the prop `isSuccess` can be passed to display the success state - - - -#### Error State - -When the loader is tracked the prop `isError` can be passed to display the error state - - - -### Animation time - -The `animationTime` prop can also be set to any number, which will extend the animation time to that number in seconds. - - - -### Disable Motion +## Playground + +Use this interactive example to explore Loader props with Storybook controls. + + + + - +## Examples ### Inside Buttons diff --git a/src/components/loader/__next__/loader.stories.tsx b/src/components/loader/__next__/loader.stories.tsx index 458eee9745..d026966c26 100644 --- a/src/components/loader/__next__/loader.stories.tsx +++ b/src/components/loader/__next__/loader.stories.tsx @@ -11,238 +11,73 @@ const meta: Meta = { title: "Loader", component: Loader, parameters: { chromatic: { disableSnapshot: true } }, + argTypes: { + loaderType: { + options: ["standalone", "ring", "star"], + control: { type: "radio" }, + }, + variant: { + options: [ + "typical", + "ai", + "stacked", + "inline", + "ai-stacked", + "ai-inline", + ], + control: { type: "select" }, + }, + size: { + options: ["extra-small", "small", "medium", "large"], + control: { type: "radio" }, + }, + inverse: { + control: "boolean", + }, + showLabel: { + control: "boolean", + }, + }, }; export default meta; type Story = StoryObj; -export const Default: Story = { +export const Playground: Story = { render: (args: LoaderProps) => ( ), -}; -Default.storyName = "Default"; - -export const Standalone: Story = { - render: () => ( - - - - ), -}; -Standalone.storyName = "Standalone"; - -export const Ring: Story = { - render: () => ( - - - - ), -}; -Ring.storyName = "Ring"; - -export const Star: Story = { - render: () => ( - - - - ), -}; -Star.storyName = "Star"; - -export const StandaloneSizes: Story = { - render: () => ( - <> - - - - - - - - - - - ), -}; -StandaloneSizes.storyName = "Standalone Sizes"; - -export const RingSizes: Story = { - render: () => ( - <> - - - - - - - - - - - - - - ), -}; -RingSizes.storyName = "Ring Sizes"; - -export const StandaloneTypicalVariant: Story = { - render: () => ( - - - - ), -}; -StandaloneTypicalVariant.storyName = "Standalone Typical Variant"; - -export const StandaloneTypicalVariantInversed: Story = { - render: () => ( - - - - ), -}; -StandaloneTypicalVariantInversed.storyName = - "Standalone Typical Variant Inversed"; - -export const StandaloneAiVariant: Story = { - render: () => ( - - - - ), -}; -StandaloneAiVariant.storyName = "Standalone AI Variant"; - -export const StandaloneAiVariantInversed: Story = { - render: () => ( - - - - ), -}; -StandaloneAiVariantInversed.storyName = "Standalone AI Variant Inversed"; - -export const RingStackedVariant: Story = { - render: () => ( - - - - ), -}; -RingStackedVariant.storyName = "Ring Stacked Variant"; - -export const RingStackedVariantInversed: Story = { - render: () => ( - - - - ), -}; -RingStackedVariantInversed.storyName = "Ring Stacked Variant Inversed"; - -export const RingInlineVariant: Story = { - render: () => ( - - - - ), -}; -RingInlineVariant.storyName = "Ring Inline Variant"; - -export const RingInlineVariantInversed: Story = { - render: () => ( - - - - ), -}; -RingInlineVariantInversed.storyName = "Ring Inline Variant Inversed"; - -export const RingAiStackedVariant: Story = { - render: () => ( - - - - ), -}; -RingAiStackedVariant.storyName = "Ring AI Stacked Variant"; - -export const RingAiInlineVariant: Story = { - render: () => ( - - - - ), -}; -RingAiInlineVariant.storyName = "Ring AI Inline Variant"; - -export const RingAiInlineVariantInversed: Story = { - render: () => ( - - - - ), -}; -RingAiInlineVariantInversed.storyName = "Ring AI Inline Variant Inversed"; - -export const RingIsTracked: Story = { - render: () => ( - - - - ), -}; -RingIsTracked.storyName = "Is Tracked"; - -export const ErrorState: Story = { - render: () => ( - - - - ), -}; -ErrorState.storyName = "Tracked Error State"; - -export const SuccessState: Story = { - render: () => ( - - - - ), -}; -SuccessState.storyName = "Tracked Success State"; - -export const AnimationTime: Story = { - render: () => ( - - - - - - ), -}; -AnimationTime.storyName = "Animation Time"; - -export const DisabledMotion: Story = { - render: () => ( - - - - - - ), -}; -DisabledMotion.storyName = "Disabled Motion"; + args: { + loaderType: "standalone", + variant: "typical", + size: "medium", + inverse: false, + showLabel: true, + loaderLabel: "Loading", + hasMotion: true, + isTracked: false, + animationTime: 3, + isSuccess: false, + isError: false, + }, + decorators: [ + (Story, { args }) => ( + + + + ), + ], +}; +Playground.storyName = "Playground"; export const InsideButtons: Story = { render: () => ( From df1ddd2a9f74ffd02abec193ab0b950301796226 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:07 +0100 Subject: [PATCH 10/19] docs(message): add playground example --- skills/carbon-react/components/message.md | 151 ------------------ .../message/message-test.stories.tsx | 120 ++++++++++++++ src/components/message/message.mdx | 54 +++---- src/components/message/message.stories.tsx | 140 ++++------------ 4 files changed, 167 insertions(+), 298 deletions(-) diff --git a/skills/carbon-react/components/message.md b/skills/carbon-react/components/message.md index e4cfc90faa..54f4bcecba 100644 --- a/skills/carbon-react/components/message.md +++ b/skills/carbon-react/components/message.md @@ -44,28 +44,6 @@ description: Carbon Message component props and usage examples. | transparent | boolean \| undefined | No | | Yes | The transparent prop is deprecated and will be removed in a future release, please use the subtle variants instead. | Set transparent styling. | | ## Examples -### Default - -**Render** - -```tsx -(args) => Some custom message -``` - - -### WithCloseButton - -**Render** - -```tsx -(args) => ( - {}} {...args}> - Some custom message - - ) -``` - - ### Programmatic Focus **Render** @@ -115,132 +93,3 @@ description: Carbon Message component props and usage examples. } ``` - -### WithTitle - -**Args** - -```tsx -{ - title: "Title", - } -``` - - -### Variant - -**Args** - -```tsx -{ - mb: 2, - } -``` - -**Render** - -```tsx -(args) => ( - <> - {}} variant="success" title="Success" {...args}> - Some custom message - - {}} variant="error" title="Error" {...args}> - Some custom message - - {}} variant="warning" title="Warning" {...args}> - Some custom message - - {}} variant="info" title="Info" {...args}> - Some custom message - - {}} variant="ai" title="AI" {...args}> - Some custom message - - - ) -``` - - -### SubtleVariant - -**Args** - -```tsx -{ - mb: 2, - } -``` - -**Render** - -```tsx -(args) => ( - <> - {}} - variant="success-subtle" - title="Success" - {...args} - > - Some custom message - - {}} - variant="warning-subtle" - title="Warning" - {...args} - > - Some custom message - - {}} - variant="info-subtle" - title="Info" - {...args} - > - Some custom message - - {}} variant="ai-subtle" title="AI" {...args}> - Some custom message - - {}} - variant="callout-subtle" - title="Callout" - {...args} - > - Some custom message - - - ) -``` - - -### SizeLarge - -**Args** - -```tsx -{ - title: "Large", - size: "large", - mb: 2, - } -``` - -**Render** - -```tsx -(args) => ( - <> - {}} {...args}> - Some custom message - - {}} variant="info-subtle" {...args}> - Some custom message - - - ) -``` - diff --git a/src/components/message/message-test.stories.tsx b/src/components/message/message-test.stories.tsx index ca17a45c7f..5dd3216b77 100644 --- a/src/components/message/message-test.stories.tsx +++ b/src/components/message/message-test.stories.tsx @@ -120,3 +120,123 @@ export const Transparent: Story = { }, parameters: { chromatic: { disableSnapshot: true } }, }; + +// Documentation regression stories moved from the public docs. + +export const Default: Story = { + render: (args) => Some custom message, +}; + +export const WithCloseButton: Story = { + render: (args) => ( + {}} {...args}> + Some custom message + + ), +}; + +export const WithTitle: Story = { + ...Default, + args: { + title: "Title", + }, +}; + +export const Variant: Story = { + render: (args) => ( + <> + {}} variant="success" title="Success" {...args}> + Some custom message + + {}} variant="error" title="Error" {...args}> + Some custom message + + {}} variant="warning" title="Warning" {...args}> + Some custom message + + {}} variant="info" title="Info" {...args}> + Some custom message + + {}} variant="ai" title="AI" {...args}> + Some custom message + + + ), + args: { + mb: 2, + }, + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +export const SubtleVariant: Story = { + render: (args) => ( + <> + {}} + variant="success-subtle" + title="Success" + {...args} + > + Some custom message + + {}} + variant="warning-subtle" + title="Warning" + {...args} + > + Some custom message + + {}} + variant="info-subtle" + title="Info" + {...args} + > + Some custom message + + {}} variant="ai-subtle" title="AI" {...args}> + Some custom message + + {}} + variant="callout-subtle" + title="Callout" + {...args} + > + Some custom message + + + ), + args: { + mb: 2, + }, + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +export const SizeLarge: Story = { + render: (args) => ( + <> + {}} {...args}> + Some custom message + + {}} variant="info-subtle" {...args}> + Some custom message + + + ), + args: { + title: "Large", + size: "large", + mb: 2, + }, + parameters: { chromatic: { disableSnapshot: false } }, +}; + +Default.parameters = { chromatic: { disableSnapshot: true } }; +WithCloseButton.parameters = { chromatic: { disableSnapshot: true } }; +WithTitle.parameters = { chromatic: { disableSnapshot: true } }; diff --git a/src/components/message/message.mdx b/src/components/message/message.mdx index c7121859ed..f14c6f24ee 100644 --- a/src/components/message/message.mdx +++ b/src/components/message/message.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as MessageStories from "./message.stories"; @@ -22,20 +22,27 @@ Presents a static message which stays on screen. import Message from "carbon-react/lib/components/message"; ``` -## Examples - -### Default - -To use `Message`, you can pass any valid content as children. +## Playground - +Use this interactive example to explore Message props with Storybook controls. -### With Close Button + -You can make the `Message` dismissible by providing an `onDismiss` handler, which will render a close button on the top right corner of the component. -To change the `aria-label` of the close button, you can use the `closeButtonAriaLabel` prop or make use of the `message.closeButtonAriaLabel` translation key. + - +## Examples ### Programmatic Focus and Aria-Live Regions @@ -50,29 +57,6 @@ This approach works well for success, warning, info, and other non-critical mess -### With Title - -A custom title can be set via the `title` prop, this prop accepts a string or any valid React node. - - - -### Variants - -The `Message` component supports several variants to convey different types of messages. You can set the variant using the `variant` prop. -The type of message will be announced by screen readers along with the content, the announcement for each variant can be customized using the provided translation keys. - - - -#### Subtle Variants - - - -### Large size - -You can use the `size` prop to change the size of the `Message` to "large". - - - ## Props ### Message @@ -135,5 +119,5 @@ The following keys are available to override the translations for this component returnType: "string", }, - ]} +]} /> diff --git a/src/components/message/message.stories.tsx b/src/components/message/message.stories.tsx index e5422896de..ff8995bfd5 100644 --- a/src/components/message/message.stories.tsx +++ b/src/components/message/message.stories.tsx @@ -10,7 +10,11 @@ const styledSystemProps = generateStyledSystemProps({ margin: true, }); -const meta: Meta = { +type MessageStoryArgs = React.ComponentProps & { + dismissible?: boolean; +}; + +const meta: Meta = { title: "Message", component: Message, parameters: { @@ -28,19 +32,33 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: (args) => Some custom message, -}; +type Story = StoryObj; -export const WithCloseButton: Story = { - render: (args) => ( - {}} {...args}> - Some custom message +export const Playground: Story = { + render: ({ dismissible, ...args }) => ( + {} : undefined}> + {args.children} ), + args: { + children: "Some custom message", + variant: "info", + title: "", + open: true, + dismissible: false, + closeButtonAriaLabel: "Close message", + width: "100%", + size: "medium", + }, + argTypes: { + dismissible: { + control: "boolean", + description: "Render the Message with a dismiss button", + table: { category: "Story" }, + }, + }, }; +Playground.storyName = "Playground"; export const ProgrammaticFocus: Story = () => { const [isOpenError, setIsOpenError] = useState(false); @@ -85,105 +103,3 @@ export const ProgrammaticFocus: Story = () => { ); }; ProgrammaticFocus.storyName = "Programmatic Focus"; - -export const WithTitle: Story = { - ...Default, - args: { - title: "Title", - }, -}; - -export const Variant: Story = { - render: (args) => ( - <> - {}} variant="success" title="Success" {...args}> - Some custom message - - {}} variant="error" title="Error" {...args}> - Some custom message - - {}} variant="warning" title="Warning" {...args}> - Some custom message - - {}} variant="info" title="Info" {...args}> - Some custom message - - {}} variant="ai" title="AI" {...args}> - Some custom message - - - ), - args: { - mb: 2, - }, - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -export const SubtleVariant: Story = { - render: (args) => ( - <> - {}} - variant="success-subtle" - title="Success" - {...args} - > - Some custom message - - {}} - variant="warning-subtle" - title="Warning" - {...args} - > - Some custom message - - {}} - variant="info-subtle" - title="Info" - {...args} - > - Some custom message - - {}} variant="ai-subtle" title="AI" {...args}> - Some custom message - - {}} - variant="callout-subtle" - title="Callout" - {...args} - > - Some custom message - - - ), - args: { - mb: 2, - }, - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; - -export const SizeLarge: Story = { - render: (args) => ( - <> - {}} {...args}> - Some custom message - - {}} variant="info-subtle" {...args}> - Some custom message - - - ), - args: { - title: "Large", - size: "large", - mb: 2, - }, - parameters: { chromatic: { disableSnapshot: false } }, -}; From a7cbdb630afb0b8c6839980b3139605910308e60 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:15 +0100 Subject: [PATCH 11/19] docs(multi-action-button): add playground example --- .../components/multi-action-button.md | 205 ------------------ .../multi-action-button-test.stories.tsx | 180 +++++++++++++++ .../multi-action-button.mdx | 77 ++----- .../multi-action-button.stories.tsx | 154 +------------ 4 files changed, 216 insertions(+), 400 deletions(-) diff --git a/skills/carbon-react/components/multi-action-button.md b/skills/carbon-react/components/multi-action-button.md index 7b2169789b..01afe38bd0 100644 --- a/skills/carbon-react/components/multi-action-button.md +++ b/skills/carbon-react/components/multi-action-button.md @@ -315,29 +315,6 @@ description: Carbon MultiActionButton component props and usage examples. | aria-grabbed | Booleanish \| undefined | No | | Yes | in ARIA 1.1 | Indicates an element's "grabbed" state in a drag-and-drop operation. | | ## Examples -### DefaultStory - -**Args** - -```tsx -{ text: "Multi Action Button" } -``` - -**Render** - -```tsx -(args: MultiActionButtonProps) => { - return ( - - - - - - ); - } -``` - - ### Focusing Main Button Programmatically **Render** @@ -367,188 +344,6 @@ description: Carbon MultiActionButton component props and usage examples. ``` -### Disabled - -**Args** - -```tsx -{ ...DefaultStory.args, text: "Multi Action Button", disabled: true } -``` - - -### Sizes - -**Render** - -```tsx -() => { - return (["small", "medium", "large"] as const).map( - (size: MultiActionButtonProps["size"]) => ( - - - - - - - - ), - ); -} -``` - - -### Custom Width - -**Args** - -```tsx -{ - text: "Multi Action Button", - width: 0.7, -} -``` - -**Render** - -```tsx -(args: MultiActionButtonProps) => { - return ( - - - - - - ); -} -``` - - -### Button Types - -**Render** - -```tsx -() => { - return (["primary", "secondary", "tertiary"] as const).map( - (buttonType: MultiActionButtonProps["buttonType"]) => ( - - - - - - - - ), - ); -} -``` - - -### Child Button Types - -**Render** - -```tsx -() => { - return ( - - - - - - - - - - - ); -} -``` - - -### Alignment - -**Render** - -```tsx -() => { - return (["left", "right"] as const).map( - (align: MultiActionButtonProps["align"]) => ( - - - - - - - - ), - ); -} -``` - - -### Position - -**Render** - -```tsx -() => { - return ( - - - - - - - - - - - - - - ); -} -``` - - -### Subtext - -**Args** - -```tsx -{ - ...DefaultStory.args, - size: "large", - text: "Multi Action Button", - subtext: "subtext", - children: ( - <> - - - - - ), - } -``` - - ### With Children Buttons With Icons **Render** diff --git a/src/components/multi-action-button/multi-action-button-test.stories.tsx b/src/components/multi-action-button/multi-action-button-test.stories.tsx index b409f6eeae..80999571c6 100644 --- a/src/components/multi-action-button/multi-action-button-test.stories.tsx +++ b/src/components/multi-action-button/multi-action-button-test.stories.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { action } from "storybook/actions"; +import { StoryObj } from "@storybook/react-vite"; import MultiActionButton, { MultiActionButtonProps, } from "./multi-action-button.component"; @@ -110,3 +111,182 @@ export const WithinDialog = { ); }, }; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +export const DefaultStory: Story = { + render: (args: MultiActionButtonProps) => { + return ( + + + + + + ); + }, + args: { text: "Multi Action Button" }, + name: "Default", + parameters: { chromatic: { disableSnapshot: true } }, +}; + +export const Disabled: Story = { + ...DefaultStory, + args: { ...DefaultStory.args, text: "Multi Action Button", disabled: true }, + name: "Disabled", +}; + +export const Sizes: Story = () => { + return (["small", "medium", "large"] as const).map( + (size: MultiActionButtonProps["size"]) => ( + + + + + + + + ), + ); +}; +Sizes.storyName = "Sizes"; + +export const CustomWidth: Story = (args: MultiActionButtonProps) => { + return ( + + + + + + ); +}; +CustomWidth.storyName = "Custom Width"; +CustomWidth.args = { + text: "Multi Action Button", + width: 0.7, +}; + +export const ButtonTypes: Story = () => { + return (["primary", "secondary", "tertiary"] as const).map( + (buttonType: MultiActionButtonProps["buttonType"]) => ( + + + + + + + + ), + ); +}; +ButtonTypes.storyName = "Button Types"; + +export const ChildButtonTypes: Story = () => { + return ( + + + + + + + + + + + ); +}; +ChildButtonTypes.storyName = "Child Button Types"; +ChildButtonTypes.parameters = { chromatic: { disableSnapshot: true } }; + +export const Alignment: Story = () => { + return (["left", "right"] as const).map( + (align: MultiActionButtonProps["align"]) => ( + + + + + + + + ), + ); +}; +Alignment.storyName = "Alignment"; +Alignment.parameters = { chromatic: { disableSnapshot: true } }; + +export const Position: Story = () => { + return ( + + + + + + + + + + + + + + ); +}; +Position.storyName = "Position"; +Position.parameters = { chromatic: { disableSnapshot: true } }; + +export const Subtext: Story = { + ...DefaultStory, + args: { + ...DefaultStory.args, + size: "large", + text: "Multi Action Button", + subtext: "subtext", + children: ( + <> + + + + + ), + }, + name: "Subtext", +}; + +Disabled.parameters = { chromatic: { disableSnapshot: false } }; +Sizes.parameters = { chromatic: { disableSnapshot: false } }; +CustomWidth.parameters = { chromatic: { disableSnapshot: false } }; +ButtonTypes.parameters = { chromatic: { disableSnapshot: false } }; +Subtext.parameters = { chromatic: { disableSnapshot: false } }; + +const documentationDecorator = (StoryToRender: React.ComponentType) => ( + + + +); + +DefaultStory.decorators = [documentationDecorator]; +Disabled.decorators = [documentationDecorator]; +Sizes.decorators = [documentationDecorator]; +CustomWidth.decorators = [documentationDecorator]; +ButtonTypes.decorators = [documentationDecorator]; +ChildButtonTypes.decorators = [documentationDecorator]; +Alignment.decorators = [documentationDecorator]; +Position.decorators = [documentationDecorator]; +Subtext.decorators = [documentationDecorator]; diff --git a/src/components/multi-action-button/multi-action-button.mdx b/src/components/multi-action-button/multi-action-button.mdx index 34ed696415..93058a5195 100644 --- a/src/components/multi-action-button/multi-action-button.mdx +++ b/src/components/multi-action-button/multi-action-button.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as ButtonStories from "../button/button.stories.tsx"; import * as MultiActionButtonStories from "./multi-action-button.stories.tsx"; @@ -29,65 +29,36 @@ import * as MultiActionButtonStories from "./multi-action-button.stories.tsx"; import MultiActionButton, { type MultiActionButtonHandle } from "carbon-react/lib/components/multi-action-button"; ``` -## Examples +## Playground + +Use this interactive example to explore MultiActionButton props with Storybook controls. -### Default + - + + +## Examples ### Focusing Main Button Programmatically -The `MultiActionButtonHandle` type provides an imperative handle for programmatic control over `MultiActionButton`. +The `MultiActionButtonHandle` type provides an imperative handle for programmatic control over `MultiActionButton`. Using a `ref`, you can access its `focusMainButton()` method to set focus on the main button as needed. -### Disabled - - - -### Custom width - -Numbers from 0-1 are converted to percentage widths. Numbers greater than 1 are converted to pixel values. -String values are passed as raw CSS values. `size` prop can still be used to set desired height. - - - -### Button types - - - -### Child Button types - -Buttons used as a content of `MultiActionButton` can be of any type. - - - -### Sizes - - - -### Button text alignment - -Use the `align` prop to change the alignment of the text in the menu Buttons. - - - -### Menu position - -By default, the menu will open aligned to the "left" of the main button, but you can use the `position` prop to change this to the "right". - -Note: The position of the menu will also be automatically adjusted to fit within the viewport. - - - -### Subtext - -Subtext only works when `size` is `large` - - - -## When children buttons render icons +### Child buttons with icons @@ -124,6 +95,6 @@ Subtext only works when `size` is `large` `MultiActionButton`'s forwarded ref exposes the following imperative methods: -| Method Name | Description | -| --------------------- | ----------------------------------------- | +| Method Name | Description | +| ------------------- | ----------------------------------------- | | `focusMainButton()` | Programmatically focuses the main button. | diff --git a/src/components/multi-action-button/multi-action-button.stories.tsx b/src/components/multi-action-button/multi-action-button.stories.tsx index 73bb698f23..e06ad73784 100644 --- a/src/components/multi-action-button/multi-action-button.stories.tsx +++ b/src/components/multi-action-button/multi-action-button.stories.tsx @@ -33,7 +33,7 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const DefaultStory: Story = { +export const Playground: Story = { render: (args: MultiActionButtonProps) => { return ( @@ -43,10 +43,18 @@ export const DefaultStory: Story = { ); }, - args: { text: "Multi Action Button" }, - name: "Default", - parameters: { chromatic: { disableSnapshot: true } }, + args: { + text: "Multi Action Button", + disabled: false, + buttonType: "primary", + size: "medium", + subtext: "Additional information", + width: "fit-content", + align: "left", + position: "left", + }, }; +Playground.storyName = "Playground"; export const ProgrammaticFocus: Story = () => { const multiActionButtonHandle = useRef(null); @@ -72,144 +80,6 @@ export const ProgrammaticFocus: Story = () => { ProgrammaticFocus.storyName = "Focusing Main Button Programmatically"; ProgrammaticFocus.parameters = { chromatic: { disableSnapshot: true } }; -export const Disabled: Story = { - ...DefaultStory, - args: { ...DefaultStory.args, text: "Multi Action Button", disabled: true }, - name: "Disabled", -}; - -export const Sizes: Story = () => { - return (["small", "medium", "large"] as const).map( - (size: MultiActionButtonProps["size"]) => ( - - - - - - - - ), - ); -}; -Sizes.storyName = "Sizes"; - -export const CustomWidth: Story = (args: MultiActionButtonProps) => { - return ( - - - - - - ); -}; -CustomWidth.storyName = "Custom Width"; -CustomWidth.args = { - text: "Multi Action Button", - width: 0.7, -}; - -export const ButtonTypes: Story = () => { - return (["primary", "secondary", "tertiary"] as const).map( - (buttonType: MultiActionButtonProps["buttonType"]) => ( - - - - - - - - ), - ); -}; -ButtonTypes.storyName = "Button Types"; - -export const ChildButtonTypes: Story = () => { - return ( - - - - - - - - - - - ); -}; -ChildButtonTypes.storyName = "Child Button Types"; -ChildButtonTypes.parameters = { chromatic: { disableSnapshot: true } }; - -export const Alignment: Story = () => { - return (["left", "right"] as const).map( - (align: MultiActionButtonProps["align"]) => ( - - - - - - - - ), - ); -}; -Alignment.storyName = "Alignment"; -Alignment.parameters = { chromatic: { disableSnapshot: true } }; - -export const Position: Story = () => { - return ( - - - - - - - - - - - - - - ); -}; -Position.storyName = "Position"; -Position.parameters = { chromatic: { disableSnapshot: true } }; - -export const Subtext: Story = { - ...DefaultStory, - args: { - ...DefaultStory.args, - size: "large", - text: "Multi Action Button", - subtext: "subtext", - children: ( - <> - - - - - ), - }, - name: "Subtext", -}; - export const WithChildrenButtonsWithIcons: Story = () => { return ( <> From 61dc59e47a05d5de07e40e14caec3d26950c6676 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:20 +0100 Subject: [PATCH 12/19] docs(progress-tracker): add playground example --- .../components/progress-tracker.md | 137 +----------------- .../progress-tracker-test.stories.tsx | 103 +++++++++++++ .../progress-tracker/progress-tracker.mdx | 71 +++------ .../progress-tracker.stories.tsx | 82 ++--------- 4 files changed, 136 insertions(+), 257 deletions(-) diff --git a/skills/carbon-react/components/progress-tracker.md b/skills/carbon-react/components/progress-tracker.md index b731f4be70..bb99f3ac20 100644 --- a/skills/carbon-react/components/progress-tracker.md +++ b/skills/carbon-react/components/progress-tracker.md @@ -44,139 +44,4 @@ description: Carbon ProgressTracker component props and usage examples. | error | boolean \| undefined | No | | Yes | Please use variant="error" instead. | Flag to control error state. | | ## Examples -### Default - -**Args** - -```tsx -{ - progress: 50, - } -``` - -**Render** - -```tsx -(args) => -``` - - -### WithDescription - -**Args** - -```tsx -{ - ...Default.args, - description: "Description", - } -``` - - -### CustomLabelValues - -**Args** - -```tsx -{ - currentProgressLabel: "£75", - maxProgressLabel: "£200", - customValuePreposition: "out of", - progress: Math.round((75 / 200) * 100), - } -``` - -**Render** - -```tsx -(args) => -``` - - -### CustomLength - -**Args** - -```tsx -{ - ...Default.args, - length: "500px", - } -``` - - -### LabelsPosition - -**Args** - -```tsx -{ - progress: 50, - currentProgressLabel: "50%", - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - - - ) -``` - - -### Sizes - -**Args** - -```tsx -{ - progress: 50, - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - - - ) -``` - - -### Variants - -**Args** - -```tsx -{ - progress: 50, - } -``` - -**Render** - -```tsx -(args) => ( - <> - - - - - - - ) -``` - +No Storybook examples found. \ No newline at end of file diff --git a/src/components/progress-tracker/progress-tracker-test.stories.tsx b/src/components/progress-tracker/progress-tracker-test.stories.tsx index a4f62f34b9..925c93ec24 100644 --- a/src/components/progress-tracker/progress-tracker-test.stories.tsx +++ b/src/components/progress-tracker/progress-tracker-test.stories.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { StoryObj } from "@storybook/react-vite"; import ProgressTracker from "."; import Box from "../box"; import { @@ -86,3 +87,105 @@ export const SnapshotCapture = () => { }; SnapshotCapture.storyName = "Snapshot Capture"; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +export const Default: Story = { + render: (args) => , + args: { + progress: 50, + }, +}; + +export const WithDescription: Story = { + ...Default, + args: { + ...Default.args, + description: "Description", + }, +}; + +export const CustomLabelValues: Story = { + render: (args) => , + args: { + currentProgressLabel: "£75", + maxProgressLabel: "£200", + customValuePreposition: "out of", + progress: Math.round((75 / 200) * 100), + }, +}; + +export const CustomLength: Story = { + ...Default, + args: { + ...Default.args, + length: "500px", + }, +}; + +export const LabelsPosition: Story = { + render: (args) => ( + <> + + + + + ), + args: { + progress: 50, + currentProgressLabel: "50%", + }, +}; + +export const Sizes: Story = { + render: (args) => ( + <> + + + + + ), + args: { + progress: 50, + }, +}; + +export const Variants: Story = { + render: (args) => ( + <> + + + + + + + ), + args: { + progress: 50, + }, +}; + +const documentationDecorator = (StoryToRender: React.ComponentType) => ( + + + +); + +[ + Default, + WithDescription, + CustomLabelValues, + CustomLength, + LabelsPosition, + Sizes, + Variants, +].forEach((story) => { + story.parameters = { chromatic: { disableSnapshot: true } }; + story.decorators = [documentationDecorator]; +}); diff --git a/src/components/progress-tracker/progress-tracker.mdx b/src/components/progress-tracker/progress-tracker.mdx index 2a28aadcab..067426aed7 100644 --- a/src/components/progress-tracker/progress-tracker.mdx +++ b/src/components/progress-tracker/progress-tracker.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as ProgressTrackerStories from "./progress-tracker.stories"; @@ -23,7 +23,6 @@ In general, place a `ProgressTracker` in the centre and middle of the page or co ## Contents - [Quick Start](#quick-start) -- [Examples](#examples) - [Props](#props) - [Translation keys](#translation-keys) @@ -33,53 +32,27 @@ In general, place a `ProgressTracker` in the centre and middle of the page or co import ProgressTracker from "carbon-react/lib/components/progress-tracker"; ``` -## Examples - -### Default - -To use this component pass the `progress` prop to set a progress value as a percentage. - -By default, the component will display a label with the provided percentage progress out of 100%. - - - -### With Description - -Use the `description` prop to provide a description of the progress being tracked. - - - -### Custom Label values - -To display custom progress values, pass the values as a string to the `currentProgressLabel` and `maxProgressLabel` props. - -To customize the "of" text, you can pass a custom string to the `customValuePreposition` prop or make use of the `progressTracker.of` translation key. - - - -### Custom Length - -By default, the length of the component is "256px". However, you can use the `length` prop to override this with any valid css string. - - - -### Labels Position - -To change the position of the label, pass the `labelsPosition` prop with a value of "top" (default), "bottom", or "left". - - - -### Sizes - -Use the `size` prop to change the size of the component, available sizes are "small", "medium" (default) and "large". - - - -### Variants - -Use the `variant` prop to change the variant of the component, available variants are "neutral" (default), "warning", "information", "error" and "success". - - +## Playground + +Use this interactive example to explore ProgressTracker props with Storybook controls. + + + + ## Props diff --git a/src/components/progress-tracker/progress-tracker.stories.tsx b/src/components/progress-tracker/progress-tracker.stories.tsx index 8880cf251f..2e9827f937 100644 --- a/src/components/progress-tracker/progress-tracker.stories.tsx +++ b/src/components/progress-tracker/progress-tracker.stories.tsx @@ -37,81 +37,19 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = { +export const Playground: Story = { render: (args) => , args: { progress: 50, - }, -}; - -export const WithDescription: Story = { - ...Default, - args: { - ...Default.args, - description: "Description", - }, -}; - -export const CustomLabelValues: Story = { - render: (args) => , - args: { - currentProgressLabel: "£75", - maxProgressLabel: "£200", - customValuePreposition: "out of", - progress: Math.round((75 / 200) * 100), - }, -}; - -export const CustomLength: Story = { - ...Default, - args: { - ...Default.args, - length: "500px", - }, -}; - -export const LabelsPosition: Story = { - render: (args) => ( - <> - - - - - ), - args: { - progress: 50, + description: "Completed", currentProgressLabel: "50%", + maxProgressLabel: "100%", + customValuePreposition: "of", + labelsPosition: "bottom", + labelWidth: "100px", + length: "256px", + size: "medium", + variant: "neutral", }, }; - -export const Sizes: Story = { - render: (args) => ( - <> - - - - - ), - args: { - progress: 50, - }, -}; - -export const Variants: Story = { - render: (args) => ( - <> - - - - - - - ), - args: { - progress: 50, - }, -}; +Playground.storyName = "Playground"; From 017dabbb4540920dc8fde1f3f72d35cc8a7f38f6 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:27 +0100 Subject: [PATCH 13/19] docs(radio-button): add playground example --- .../carbon-react/components/radio-button.md | 173 ---------------- .../radio-button-test.stories.tsx | 174 ++++++++++++++++ src/components/radio-button/radio-button.mdx | 63 ++---- .../radio-button/radio-button.stories.tsx | 195 ++++-------------- 4 files changed, 230 insertions(+), 375 deletions(-) diff --git a/skills/carbon-react/components/radio-button.md b/skills/carbon-react/components/radio-button.md index 0cd362c0aa..56b21ac5c3 100644 --- a/skills/carbon-react/components/radio-button.md +++ b/skills/carbon-react/components/radio-button.md @@ -331,153 +331,6 @@ description: Carbon RadioButton component props and usage examples. | aria-grabbed | Booleanish \| undefined | No | | Yes | in ARIA 1.1 | Indicates an element's "grabbed" state in a drag-and-drop operation. | | ## Examples -### WithLegend - -**Args** - -```tsx -{ - id: "with-legend", - legend: "RadioButtonGroup Legend", - } -``` - -**Render** - -```tsx -ControlledRadioButtonGroup -``` - - -### WithLegendHint - -**Args** - -```tsx -{ - ...WithLegend.args, - id: "with-legend-hint", - legendHint: "Legend Hint", - } -``` - - -### With Input Hint - -**Render** - -```tsx -({ ...args }) => { - const [value, setValue] = useState(""); - return ( - setValue(ev.target.value)} - {...args} - > - - - - - ); -} -``` - - -### InlineRadioButtons - -**Args** - -```tsx -{ - ...WithLegend.args, - id: "inline", - legendHint: "Legend Hint", - inline: true, - } -``` - - -### Sizes - -**Render** - -```tsx -() => { - const [valueSmall, setValueSmall] = useState(""); - const [valueMedium, setValueMedium] = useState(""); - const [valueLarge, setValueLarge] = useState(""); - - return ( - - setValueSmall(ev.target.value)} - size="small" - > - - - - - setValueMedium(ev.target.value)} - size="medium" - > - - - - - setValueLarge(ev.target.value)} - size="large" - > - - - - - - ); -} -``` - - ### Progressive Disclosure **Render** @@ -577,29 +430,3 @@ ControlledRadioButtonGroup } ``` - -### Required - -**Args** - -```tsx -{ - ...WithLegend.args, - id: "required", - required: true, - } -``` - - -### Disabled - -**Args** - -```tsx -{ - ...WithLegend.args, - id: "disabled", - disabled: true, - } -``` - diff --git a/src/components/radio-button/radio-button-test.stories.tsx b/src/components/radio-button/radio-button-test.stories.tsx index 93fc7b44f1..34ea608eaf 100755 --- a/src/components/radio-button/radio-button-test.stories.tsx +++ b/src/components/radio-button/radio-button-test.stories.tsx @@ -303,3 +303,177 @@ export const InTabs: Story = () => { ); }; InTabs.storyName = "In Tabs"; + +// Documentation regression stories moved from the public docs. + +interface TemplateProps + extends Omit< + RadioButtonGroupProps, + "children" | "value" | "onChange" | "name" + > { + id?: string; +} + +const ControlledRadioButtonGroup = ({ + id = "default", + ...args +}: TemplateProps) => { + const [value, setValue] = useState(""); + return ( + setValue(ev.target.value)} + {...args} + > + + + + + ); +}; + +export const WithLegend: Story = { + render: ControlledRadioButtonGroup, + args: { + id: "with-legend", + legend: "RadioButtonGroup Legend", + }, +}; + +export const WithLegendHint: Story = { + ...WithLegend, + args: { + ...WithLegend.args, + id: "with-legend-hint", + legendHint: "Legend Hint", + }, +}; + +export const WithInputHint: Story = ({ ...args }) => { + const [value, setValue] = useState(""); + return ( + setValue(ev.target.value)} + {...args} + > + + + + + ); +}; +WithInputHint.storyName = "With Input Hint"; + +export const InlineRadioButtons: Story = { + ...WithLegend, + args: { + ...WithLegend.args, + id: "inline", + legendHint: "Legend Hint", + inline: true, + }, +}; + +export const Sizes: Story = () => { + const [valueSmall, setValueSmall] = useState(""); + const [valueMedium, setValueMedium] = useState(""); + const [valueLarge, setValueLarge] = useState(""); + + return ( + + setValueSmall(ev.target.value)} + size="small" + > + + + + + setValueMedium(ev.target.value)} + size="medium" + > + + + + + setValueLarge(ev.target.value)} + size="large" + > + + + + + + ); +}; +Sizes.storyName = "Sizes"; + +export const Required: Story = { + ...WithLegend, + args: { + ...WithLegend.args, + id: "required", + required: true, + }, +}; + +export const Disabled: Story = { + ...WithLegend, + args: { + ...WithLegend.args, + id: "disabled", + disabled: true, + }, + parameters: { + chromatic: { disableSnapshot: false }, + }, +}; + +WithLegend.parameters = { chromatic: { disableSnapshot: true } }; +WithLegendHint.parameters = { chromatic: { disableSnapshot: true } }; +WithInputHint.parameters = { chromatic: { disableSnapshot: true } }; +InlineRadioButtons.parameters = { chromatic: { disableSnapshot: true } }; +Sizes.parameters = { chromatic: { disableSnapshot: true } }; +Required.parameters = { chromatic: { disableSnapshot: true } }; diff --git a/src/components/radio-button/radio-button.mdx b/src/components/radio-button/radio-button.mdx index afe86d0340..57730c50d0 100644 --- a/src/components/radio-button/radio-button.mdx +++ b/src/components/radio-button/radio-button.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as RadioButtonGroupStories from "./radio-button.stories"; @@ -33,41 +33,32 @@ import { } from "carbon-react/lib/components/radio-button"; ``` -## Examples - -### With Legend - -A legend can be set on the group by passing the `legend` prop to `RadioButtonGroup` with a string value. - - - -### With Legend Hint - -To add a hint below the legend, pass the `legendHint` prop to `RadioButtonGroup` with a string value. - - - -### With Input Hint - -To add a hint to an individual radio button, pass the `inputHint` prop to `RadioButton` with a string value. - - +## Playground -### Inline +Use this interactive example to explore RadioButtonGroup props with Storybook controls. -`RadioButton` children can be displayed inline by passing the `inline` prop to `RadioButtonGroup`. + - + -### Sizes - -The size of the component can be changed by passing the `size` prop to `RadioButtonGroup`. Available options are `small`, `medium`, and `large`. - - +## Examples ### Progressive Disclosure -`RadioButton` can be used to conditionally reveal additional content when selected. +`RadioButton` can be used to conditionally reveal additional content when selected. This can be achieved by passing the content to the `progressiveDisclosure` prop in `RadioButton`, which accepts any valid ReactNode. It is recommended that you provide limited amounts of content within conditionally revealed sections, if you need to disclose larger amounts of content, please consider using a different pattern. @@ -82,20 +73,6 @@ The `label` prop in `RadioButton` accepts a ReactNode, which allows for custom c -### Required - -`RadioButtonGroup` can be marked as required by passing the `required` prop. - - - -### Disabled - -`RadioButton`s can be disabled using the `disabled` prop either on the `RadioButtonGroup` to disable all radio buttons, or on individual `RadioButton`s to disable them individually. - -**Please Note**: Even though Carbon does support disabled radio buttons, their use is not generally recommended by Design System. - - - ## Validation States This component supports input validation, see our [Validations](../?path=/docs/documentation-validations--docs) documentation page for more information. diff --git a/src/components/radio-button/radio-button.stories.tsx b/src/components/radio-button/radio-button.stories.tsx index f5c55d1efe..ed1526a482 100644 --- a/src/components/radio-button/radio-button.stories.tsx +++ b/src/components/radio-button/radio-button.stories.tsx @@ -12,7 +12,12 @@ const styledSystemProps = generateStyledSystemProps({ margin: true, }); -const meta: Meta = { +type RadioButtonGroupStoryArgs = RadioButtonGroupProps & { + inputHint?: string; + progressiveDisclosure?: string; +}; + +const meta: Meta = { title: "Radio Button", component: RadioButtonGroup, subcomponents: { RadioButton }, @@ -31,151 +36,44 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; - -interface TemplateProps - extends Omit< - RadioButtonGroupProps, - "children" | "value" | "onChange" | "name" - > { - id?: string; -} - -const ControlledRadioButtonGroup = ({ - id = "default", - ...args -}: TemplateProps) => { - const [value, setValue] = useState(""); - return ( - setValue(ev.target.value)} - {...args} - > - - - - - ); -}; - -export const WithLegend: Story = { - render: ControlledRadioButtonGroup, - args: { - id: "with-legend", - legend: "RadioButtonGroup Legend", - }, -}; - -export const WithLegendHint: Story = { - ...WithLegend, - args: { - ...WithLegend.args, - id: "with-legend-hint", - legendHint: "Legend Hint", - }, -}; - -export const WithInputHint: Story = ({ ...args }) => { - const [value, setValue] = useState(""); - return ( - setValue(ev.target.value)} - {...args} - > - - - - - ); -}; -WithInputHint.storyName = "With Input Hint"; - -export const InlineRadioButtons: Story = { - ...WithLegend, - args: { - ...WithLegend.args, - id: "inline", - legendHint: "Legend Hint", - inline: true, - }, -}; +type Story = StoryObj; -export const Sizes: Story = () => { - const [valueSmall, setValueSmall] = useState(""); - const [valueMedium, setValueMedium] = useState(""); - const [valueLarge, setValueLarge] = useState(""); - - return ( - - setValueSmall(ev.target.value)} - size="small" - > - - - - +export const Playground: Story = { + render: (args) => { + const { inputHint, progressiveDisclosure, ...groupProps } = args; + const [value, setValue] = useState(""); + return ( setValueMedium(ev.target.value)} - size="medium" + {...groupProps} + name="playground-group" + value={value} + onChange={(ev) => setValue(ev.target.value)} > - - + + - setValueLarge(ev.target.value)} - size="large" - > - - - - - - ); + ); + }, + args: { + legend: "RadioButtonGroup Legend", + legendHint: "Choose one option", + inline: false, + disabled: false, + required: false, + size: "medium", + error: "", + inputHint: "Option hint", + progressiveDisclosure: "Additional content for option 1", + }, }; -Sizes.storyName = "Sizes"; +Playground.storyName = "Playground"; export const ProgressiveDisclosure: Story = () => { const [value, setValue] = useState("radio1"); @@ -267,24 +165,3 @@ WithCustomLabels.storyName = "With Custom Labels"; WithCustomLabels.parameters = { chromatic: { disableSnapshot: false }, }; - -export const Required: Story = { - ...WithLegend, - args: { - ...WithLegend.args, - id: "required", - required: true, - }, -}; - -export const Disabled: Story = { - ...WithLegend, - args: { - ...WithLegend.args, - id: "disabled", - disabled: true, - }, - parameters: { - chromatic: { disableSnapshot: false }, - }, -}; From 5552b411af1d7d25efb978f6addea26500b2e7bc Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:32 +0100 Subject: [PATCH 14/19] docs(search): add playground example --- skills/carbon-react/components/search.md | 316 ------------------ src/components/search/search-test.stories.tsx | 279 ++++++++++++++++ src/components/search/search.mdx | 90 ++--- src/components/search/search.stories.tsx | 309 ++--------------- 4 files changed, 350 insertions(+), 644 deletions(-) diff --git a/skills/carbon-react/components/search.md b/skills/carbon-react/components/search.md index 9b250953d2..9dfe52c8c5 100644 --- a/skills/carbon-react/components/search.md +++ b/skills/carbon-react/components/search.md @@ -69,19 +69,6 @@ description: Carbon Search component props and usage examples. | warning | string \| boolean \| undefined | No | | Yes | This prop no longer has any effect. This prop will eventually be removed. | | | ## Examples -### Default - -**Render** - -```tsx -() => { - const [value, setValue] = useState(""); - - return setValue(e.target.value)} />; -} -``` - - ### With Dropdown **Render** @@ -169,306 +156,3 @@ description: Carbon Search component props and usage examples. } ``` - -### With Label and Input Hint - -**Render** - -```tsx -() => { - const [value, setValue] = useState("Here is some text"); - return ( - setValue(e.target.value)} - value={value} - /> - ); -} -``` - - -### Sizes - -**Render** - -```tsx -() => { - const [valueS, setValueS] = useState(""); - const [valueM, setValueM] = useState(""); - const [valueL, setValueL] = useState(""); - return ( - - setValueS(e.target.value)} - value={valueS} - /> - setValueM(e.target.value)} - value={valueM} - /> - setValueL(e.target.value)} - value={valueL} - /> - - ); -} -``` - - -### Sizes with Dropdown - -**Render** - -```tsx -() => { - const minQueryLength = 2; - - const recentItems = [ - { value: "Recent term 1", label: "Recent term 1" }, - { value: "Recent term 2", label: "Recent term 2" }, - { value: "Recent term 3", label: "Recent term 3" }, - ]; - - const suggestedItems = [ - { - value: "Suggested term 1", - label: "Suggested term 1", - }, - { - value: "Suggested term 2", - label: "Suggested term 2", - }, - { - value: "Suggested term 3", - label: "Suggested term 3", - }, - { - value: "Suggested term 4", - label: "Suggested term 4", - }, - { - value: "Suggested term 5", - label: "Suggested term 5", - }, - ]; - - const [valueS, setValueS] = useState(""); - const [valueM, setValueM] = useState(""); - const [valueL, setValueL] = useState(""); - const [dismissedS, setDismissedS] = useState(false); - const [dismissedM, setDismissedM] = useState(false); - const [dismissedL, setDismissedL] = useState(false); - - const getListData = (val: string) => { - const filteredRecent = recentItems.filter((item) => - item.label.toLowerCase().includes(val.toLowerCase()), - ); - const filteredSuggested = suggestedItems.filter((item) => - item.label.toLowerCase().includes(val.toLowerCase()), - ); - - return [ - ...(filteredRecent.length > 0 - ? [ - { - heading: "Recent searches", - icon: , - items: filteredRecent, - }, - ] - : []), - ...(filteredSuggested.length > 0 - ? [ - { - heading: "Suggested", - icon: , - items: filteredSuggested, - }, - ] - : []), - ]; - }; - - return ( - - { - setValueS(e.target.value); - setDismissedS(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueS(""); - setDismissedS(true); - } - }} - onFocus={() => setDismissedS(false)} - open={ - valueS.length >= minQueryLength && - getListData(valueS).length > 0 && - !dismissedS - } - minQueryLength={minQueryLength} - listData={getListData(valueS)} - onListItemSelect={(val) => { - setValueS(val); - setDismissedS(true); - }} - onClose={() => setDismissedS(true)} - /> - { - setValueM(e.target.value); - setDismissedM(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueM(""); - setDismissedM(true); - } - }} - onFocus={() => setDismissedM(false)} - open={ - valueM.length >= minQueryLength && - getListData(valueM).length > 0 && - !dismissedM - } - minQueryLength={minQueryLength} - listData={getListData(valueM)} - onListItemSelect={(val) => { - setValueM(val); - setDismissedM(true); - }} - onClose={() => setDismissedM(true)} - /> - { - setValueL(e.target.value); - setDismissedL(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueL(""); - setDismissedL(true); - } - }} - onFocus={() => setDismissedL(false)} - open={ - valueL.length >= minQueryLength && - getListData(valueL).length > 0 && - !dismissedL - } - minQueryLength={minQueryLength} - listData={getListData(valueL)} - onListItemSelect={(val) => { - setValueL(val); - setDismissedL(true); - }} - onClose={() => setDismissedL(true)} - /> - - ); -} -``` - - -### Custom Widths - -**Render** - -```tsx -() => { - const [value, setValue] = useState(""); - return ( - - setValue(e.target.value)} - value={value} - inputWidth={25} - /> - setValue(e.target.value)} - value={value} - maxWidth="75%" - /> - - ); -} -``` - - -### Inverse - -**Render** - -```tsx -() => { - const [value, setValue] = useState("Here is some text"); - return ( - - setValue(e.target.value)} - value={value} - inverse - /> - - ); -} -``` - - -### Label Inline - -**Render** - -```tsx -() => { - const [value, setValue] = useState(""); - return ( - - setValue(e.target.value)} - value={value} - /> - setValue(e.target.value)} - value={value} - /> - - ); -} -``` - diff --git a/src/components/search/search-test.stories.tsx b/src/components/search/search-test.stories.tsx index e52e58d2eb..f98f621457 100644 --- a/src/components/search/search-test.stories.tsx +++ b/src/components/search/search-test.stories.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import { StoryObj } from "@storybook/react-vite"; import Box from "../box"; import Search from "."; import { SearchProps, SearchHandle, SearchListGroup } from "./search.component"; @@ -510,3 +511,281 @@ export const OpenWithListDataCustomHeight = () => { ); }; OpenWithListDataCustomHeight.storyName = "Open With List Data - Custom Height"; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +export const DocumentationDefault: Story = () => { + const [value, setValue] = useState(""); + + return setValue(e.target.value)} />; +}; +DocumentationDefault.storyName = "DocumentationDefault"; + +export const WithLabelAndInputHint: Story = () => { + const [value, setValue] = useState("Here is some text"); + return ( + setValue(e.target.value)} + value={value} + /> + ); +}; +WithLabelAndInputHint.storyName = "With Label and Input Hint"; + +export const Sizes: Story = () => { + const [valueS, setValueS] = useState(""); + const [valueM, setValueM] = useState(""); + const [valueL, setValueL] = useState(""); + return ( + + setValueS(e.target.value)} + value={valueS} + /> + setValueM(e.target.value)} + value={valueM} + /> + setValueL(e.target.value)} + value={valueL} + /> + + ); +}; +Sizes.storyName = "Sizes"; + +export const SizesWithDropdown: Story = () => { + const minQueryLength = 2; + + const recentItems = [ + { value: "Recent term 1", label: "Recent term 1" }, + { value: "Recent term 2", label: "Recent term 2" }, + { value: "Recent term 3", label: "Recent term 3" }, + ]; + + const suggestedItems = [ + { + value: "Suggested term 1", + label: "Suggested term 1", + }, + { + value: "Suggested term 2", + label: "Suggested term 2", + }, + { + value: "Suggested term 3", + label: "Suggested term 3", + }, + { + value: "Suggested term 4", + label: "Suggested term 4", + }, + { + value: "Suggested term 5", + label: "Suggested term 5", + }, + ]; + + const [valueS, setValueS] = useState(""); + const [valueM, setValueM] = useState(""); + const [valueL, setValueL] = useState(""); + const [dismissedS, setDismissedS] = useState(false); + const [dismissedM, setDismissedM] = useState(false); + const [dismissedL, setDismissedL] = useState(false); + + const getListData = (val: string) => { + const filteredRecent = recentItems.filter((item) => + item.label.toLowerCase().includes(val.toLowerCase()), + ); + const filteredSuggested = suggestedItems.filter((item) => + item.label.toLowerCase().includes(val.toLowerCase()), + ); + + return [ + ...(filteredRecent.length > 0 + ? [ + { + heading: "Recent searches", + icon: , + items: filteredRecent, + }, + ] + : []), + ...(filteredSuggested.length > 0 + ? [ + { + heading: "Suggested", + icon: , + items: filteredSuggested, + }, + ] + : []), + ]; + }; + + return ( + + { + setValueS(e.target.value); + setDismissedS(false); + }} + onKeyDown={(e) => { + if (e.key === "Escape") { + setValueS(""); + setDismissedS(true); + } + }} + onFocus={() => setDismissedS(false)} + open={ + valueS.length >= minQueryLength && + getListData(valueS).length > 0 && + !dismissedS + } + minQueryLength={minQueryLength} + listData={getListData(valueS)} + onListItemSelect={(val) => { + setValueS(val); + setDismissedS(true); + }} + onClose={() => setDismissedS(true)} + /> + { + setValueM(e.target.value); + setDismissedM(false); + }} + onKeyDown={(e) => { + if (e.key === "Escape") { + setValueM(""); + setDismissedM(true); + } + }} + onFocus={() => setDismissedM(false)} + open={ + valueM.length >= minQueryLength && + getListData(valueM).length > 0 && + !dismissedM + } + minQueryLength={minQueryLength} + listData={getListData(valueM)} + onListItemSelect={(val) => { + setValueM(val); + setDismissedM(true); + }} + onClose={() => setDismissedM(true)} + /> + { + setValueL(e.target.value); + setDismissedL(false); + }} + onKeyDown={(e) => { + if (e.key === "Escape") { + setValueL(""); + setDismissedL(true); + } + }} + onFocus={() => setDismissedL(false)} + open={ + valueL.length >= minQueryLength && + getListData(valueL).length > 0 && + !dismissedL + } + minQueryLength={minQueryLength} + listData={getListData(valueL)} + onListItemSelect={(val) => { + setValueL(val); + setDismissedL(true); + }} + onClose={() => setDismissedL(true)} + /> + + ); +}; +SizesWithDropdown.storyName = "Sizes with Dropdown"; + +export const CustomWidths: Story = () => { + const [value, setValue] = useState(""); + return ( + + setValue(e.target.value)} + value={value} + inputWidth={25} + /> + setValue(e.target.value)} + value={value} + maxWidth="75%" + /> + + ); +}; +CustomWidths.storyName = "Custom Widths"; + +export const Inverse: Story = () => { + const [value, setValue] = useState("Here is some text"); + return ( + + setValue(e.target.value)} + value={value} + inverse + /> + + ); +}; +Inverse.storyName = "Inverse"; + +export const LabelInline: Story = () => { + const [value, setValue] = useState(""); + return ( + + setValue(e.target.value)} + value={value} + /> + setValue(e.target.value)} + value={value} + /> + + ); +}; +LabelInline.storyName = "Label Inline"; diff --git a/src/components/search/search.mdx b/src/components/search/search.mdx index 752ebb08a3..73e2fd1a6c 100644 --- a/src/components/search/search.mdx +++ b/src/components/search/search.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as SearchStories from "./search.stories"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; @@ -33,11 +33,28 @@ it provides a familiar input pattern with a built-in search button. import Search, { type SearchHandle, type SearchListGroup, type SearchListData } from "carbon-react/lib/components/search"; ``` -## Examples +## Playground + +Use this interactive example to explore Search props with Storybook controls. -### Default + - + + +## Examples ### With Dropdown @@ -71,62 +88,22 @@ Each group in `listData` has the following shape: Each item within a group has the following shape: -| Property | Type | Required | Description | -| ------------- | ----------------- | -------- | -------------------------------------------------------- | -| `value` | `string` | Yes | The value passed to `onListItemSelect` when chosen. | -| `label` | `string` | Yes | The primary display text for the item. | -| `labelPrefix` | `string` | No | An optional prefix rendered before the label. | -| `subtext` | `string` | No | Secondary text rendered below the label. | -| `selectedIcon`| `boolean` | No | Enables selected-state tick icon behavior for that item. | -| `leading` | `React.ReactNode` | No | Optional leading slot content, e.g. an icon or avatar. | -| `disabled` | `boolean` | No | When `true`, the item is non-interactive. | -| `id` | `string` | No | Optional `id` attribute for the list item element. | -| `onClick` | `(event, value) => void` | No | Optional per-item click handler. | +| Property | Type | Required | Description | +| -------------- | ------------------------ | -------- | -------------------------------------------------------- | +| `value` | `string` | Yes | The value passed to `onListItemSelect` when chosen. | +| `label` | `string` | Yes | The primary display text for the item. | +| `labelPrefix` | `string` | No | An optional prefix rendered before the label. | +| `subtext` | `string` | No | Secondary text rendered below the label. | +| `selectedIcon` | `boolean` | No | Enables selected-state tick icon behavior for that item. | +| `leading` | `React.ReactNode` | No | Optional leading slot content, e.g. an icon or avatar. | +| `disabled` | `boolean` | No | When `true`, the item is non-interactive. | +| `id` | `string` | No | Optional `id` attribute for the list item element. | +| `onClick` | `(event, value) => void` | No | Optional per-item click handler. | Search `"term"` below to see the dropdown in action. -### With Label and Hint Text - -You can pass a visible label and hint text to the `Search` component via the `label` & `inputHint` props. - -When the `inputHint` prop is passed, please use a full stop `.` at the end. This forces a pause -before any other announcements, which helps screen reader users understand the hint fully. - - - -### Sizes - -The `Search` component supports three size options: `"small"`, `"medium"`, and `"large"`. -The size prop controls the height and spacing of both the input and button. - - - -### Sizes with Dropdown - -Each size prop also controls the default max-height of the dropdown. Search `"term"` in any of the inputs below to see the dropdown in action. - - - -### Custom Width & Max Width - -You can set a custom width for the `Search` input by using `inputWidth` prop, or set a custom max-width using the `maxWidth` prop. - - - -### Inverse - -The `Search` component can be styled to render on dark backgrounds by passing the `inverse` prop. - - - -### Label Inline - -You can render the label inline with the input by passing the `labelInline` prop. - - - ## Props ### Search @@ -142,7 +119,8 @@ to the [i18nProvider](../?path=/docs/documentation-i18n--docs). translationData={[ { name: "search.searchButtonText", - description: "The text for the `Search` button (now only used as the default accessible name for the Search button if no `ariaLabel` prop is provided).", + description: + "The text for the `Search` button (now only used as the default accessible name for the Search button if no `ariaLabel` prop is provided).", type: "func", returnType: "string", }, diff --git a/src/components/search/search.stories.tsx b/src/components/search/search.stories.tsx index 9512bade16..6866ba13d6 100644 --- a/src/components/search/search.stories.tsx +++ b/src/components/search/search.stories.tsx @@ -25,12 +25,44 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = () => { - const [value, setValue] = useState(""); - - return setValue(e.target.value)} />; +export const Playground: Story = { + render: (args) => { + const [value, setValue] = useState(""); + return ( + setValue(e.target.value)} + /> + ); + }, + args: { + label: "Search", + inputHint: "Enter a search term", + size: "medium", + inputWidth: 100, + maxWidth: "100%", + required: false, + triggerOnClear: false, + inverse: false, + error: "", + }, + decorators: [ + (Story, { args }) => ( + + + + ), + ], }; -Default.storyName = "Default"; +Playground.storyName = "Playground"; export const WithDropdown: Story = () => { const minQueryLength = 2; @@ -113,270 +145,3 @@ export const WithDropdown: Story = () => { ); }; WithDropdown.storyName = "With Dropdown"; - -export const WithLabelAndInputHint: Story = () => { - const [value, setValue] = useState("Here is some text"); - return ( - setValue(e.target.value)} - value={value} - /> - ); -}; -WithLabelAndInputHint.storyName = "With Label and Input Hint"; - -export const Sizes: Story = () => { - const [valueS, setValueS] = useState(""); - const [valueM, setValueM] = useState(""); - const [valueL, setValueL] = useState(""); - return ( - - setValueS(e.target.value)} - value={valueS} - /> - setValueM(e.target.value)} - value={valueM} - /> - setValueL(e.target.value)} - value={valueL} - /> - - ); -}; -Sizes.storyName = "Sizes"; - -export const SizesWithDropdown: Story = () => { - const minQueryLength = 2; - - const recentItems = [ - { value: "Recent term 1", label: "Recent term 1" }, - { value: "Recent term 2", label: "Recent term 2" }, - { value: "Recent term 3", label: "Recent term 3" }, - ]; - - const suggestedItems = [ - { - value: "Suggested term 1", - label: "Suggested term 1", - }, - { - value: "Suggested term 2", - label: "Suggested term 2", - }, - { - value: "Suggested term 3", - label: "Suggested term 3", - }, - { - value: "Suggested term 4", - label: "Suggested term 4", - }, - { - value: "Suggested term 5", - label: "Suggested term 5", - }, - ]; - - const [valueS, setValueS] = useState(""); - const [valueM, setValueM] = useState(""); - const [valueL, setValueL] = useState(""); - const [dismissedS, setDismissedS] = useState(false); - const [dismissedM, setDismissedM] = useState(false); - const [dismissedL, setDismissedL] = useState(false); - - const getListData = (val: string) => { - const filteredRecent = recentItems.filter((item) => - item.label.toLowerCase().includes(val.toLowerCase()), - ); - const filteredSuggested = suggestedItems.filter((item) => - item.label.toLowerCase().includes(val.toLowerCase()), - ); - - return [ - ...(filteredRecent.length > 0 - ? [ - { - heading: "Recent searches", - icon: , - items: filteredRecent, - }, - ] - : []), - ...(filteredSuggested.length > 0 - ? [ - { - heading: "Suggested", - icon: , - items: filteredSuggested, - }, - ] - : []), - ]; - }; - - return ( - - { - setValueS(e.target.value); - setDismissedS(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueS(""); - setDismissedS(true); - } - }} - onFocus={() => setDismissedS(false)} - open={ - valueS.length >= minQueryLength && - getListData(valueS).length > 0 && - !dismissedS - } - minQueryLength={minQueryLength} - listData={getListData(valueS)} - onListItemSelect={(val) => { - setValueS(val); - setDismissedS(true); - }} - onClose={() => setDismissedS(true)} - /> - { - setValueM(e.target.value); - setDismissedM(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueM(""); - setDismissedM(true); - } - }} - onFocus={() => setDismissedM(false)} - open={ - valueM.length >= minQueryLength && - getListData(valueM).length > 0 && - !dismissedM - } - minQueryLength={minQueryLength} - listData={getListData(valueM)} - onListItemSelect={(val) => { - setValueM(val); - setDismissedM(true); - }} - onClose={() => setDismissedM(true)} - /> - { - setValueL(e.target.value); - setDismissedL(false); - }} - onKeyDown={(e) => { - if (e.key === "Escape") { - setValueL(""); - setDismissedL(true); - } - }} - onFocus={() => setDismissedL(false)} - open={ - valueL.length >= minQueryLength && - getListData(valueL).length > 0 && - !dismissedL - } - minQueryLength={minQueryLength} - listData={getListData(valueL)} - onListItemSelect={(val) => { - setValueL(val); - setDismissedL(true); - }} - onClose={() => setDismissedL(true)} - /> - - ); -}; -SizesWithDropdown.storyName = "Sizes with Dropdown"; - -export const CustomWidths: Story = () => { - const [value, setValue] = useState(""); - return ( - - setValue(e.target.value)} - value={value} - inputWidth={25} - /> - setValue(e.target.value)} - value={value} - maxWidth="75%" - /> - - ); -}; -CustomWidths.storyName = "Custom Widths"; - -export const Inverse: Story = () => { - const [value, setValue] = useState("Here is some text"); - return ( - - setValue(e.target.value)} - value={value} - inverse - /> - - ); -}; -Inverse.storyName = "Inverse"; - -export const LabelInline: Story = () => { - const [value, setValue] = useState(""); - return ( - - setValue(e.target.value)} - value={value} - /> - setValue(e.target.value)} - value={value} - /> - - ); -}; -LabelInline.storyName = "Label Inline"; From 5ccda03379b86d7eea29f4177d927397d22824ef Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:37 +0100 Subject: [PATCH 15/19] docs(switch): add playground example --- skills/carbon-react/components/switch.md | 228 +----------------- src/components/switch/switch-test.stories.tsx | 182 +++++++++++++- src/components/switch/switch.mdx | 84 ++----- src/components/switch/switch.stories.tsx | 204 ++++------------ 4 files changed, 251 insertions(+), 447 deletions(-) diff --git a/skills/carbon-react/components/switch.md b/skills/carbon-react/components/switch.md index 15d4bf062d..bd30b97e2e 100644 --- a/skills/carbon-react/components/switch.md +++ b/skills/carbon-react/components/switch.md @@ -64,230 +64,4 @@ description: Carbon Switch component props and usage examples. | warning | string \| boolean \| undefined | No | | Yes | This prop is no longer supported. | | | ## Examples -### Default - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -} -``` - - -### Checked - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(true); - return ( - setChecked(e.target.checked)} - /> - ); -} -``` - - -### Disabled - -**Render** - -```tsx -() => ( - {}} - /> -) -``` - - -### Disabled (checked) - -**Render** - -```tsx -() => ( - {}} - /> -) -``` - - -### Required - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - required - /> - ); -} -``` - - -### Large size - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -} -``` - - -### Label inline - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -} -``` - - -### Label inline with hint - -**Render** - -```tsx -() => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -} -``` - - -### Loading - -**Render** - -```tsx -() => ( - <> - {}} - /> - - {}} - ml="8px" - /> - - {}} - ml="8px" - /> - - {}} - ml="8px" - /> - -) -``` - - -### Loading with custom processingLabel - -**Render** - -```tsx -() => ( - {}} - /> -) -``` - - -### Loading with processingLabel below switch - -**Render** - -```tsx -() => ( - {}} - /> -) -``` - +No Storybook examples found. \ No newline at end of file diff --git a/src/components/switch/switch-test.stories.tsx b/src/components/switch/switch-test.stories.tsx index e2fba194cf..30578074ac 100644 --- a/src/components/switch/switch-test.stories.tsx +++ b/src/components/switch/switch-test.stories.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { Meta, StoryObj } from "@storybook/react-vite"; import Switch from "./switch.component"; @@ -334,3 +334,183 @@ export const LabelHelpAndFieldHelp: Story = () => ( ); LabelHelpAndFieldHelp.storyName = "Label help and field help"; LabelHelpAndFieldHelp.parameters = { chromatic: { disableSnapshot: false } }; + +// Documentation regression stories moved from the public docs. + +export const Default: Story = () => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + /> + ); +}; +Default.storyName = "Default"; + +export const Checked: Story = () => { + const [checked, setChecked] = useState(true); + return ( + setChecked(e.target.checked)} + /> + ); +}; +Checked.storyName = "Checked"; + +export const Disabled: Story = () => ( + {}} + /> +); +Disabled.storyName = "Disabled"; + +export const DisabledChecked: Story = () => ( + {}} + /> +); +DisabledChecked.storyName = "Disabled (checked)"; + +export const Required: Story = () => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + required + /> + ); +}; +Required.storyName = "Required"; + +export const LargeSize: Story = () => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + /> + ); +}; +LargeSize.storyName = "Large size"; + +export const LabelInline: Story = () => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + /> + ); +}; +LabelInline.storyName = "Label inline"; + +export const LabelInlineWithHint: Story = () => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + /> + ); +}; +LabelInlineWithHint.storyName = "Label inline with hint"; + +export const Loading: Story = () => ( + <> + {}} + /> + + {}} + ml="8px" + /> + + {}} + ml="8px" + /> + + {}} + ml="8px" + /> + +); +Loading.storyName = "Loading"; + +export const LoadingCustomLabel: Story = () => ( + {}} + /> +); +LoadingCustomLabel.storyName = "Loading with custom processingLabel"; + +export const LoadingLabelBelow: Story = () => ( + {}} + /> +); +LoadingLabelBelow.storyName = "Loading with processingLabel below switch"; + +[ + Default, + Checked, + Disabled, + DisabledChecked, + Required, + LargeSize, + LabelInline, + LabelInlineWithHint, + Loading, + LoadingCustomLabel, + LoadingLabelBelow, +].forEach((story) => { + story.parameters = { chromatic: { disableSnapshot: true } }; +}); diff --git a/src/components/switch/switch.mdx b/src/components/switch/switch.mdx index 935052b27e..8cee5bf568 100644 --- a/src/components/switch/switch.mdx +++ b/src/components/switch/switch.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as SwitchStories from "./switch.stories.tsx"; @@ -21,7 +21,6 @@ A Switch lets a user toggle a single setting on or off. It gives an immediate re ## Contents - [Quick Start](#quick-start) -- [Examples](#examples) - [Accessibility](#accessibility) - [Props](#props) @@ -31,60 +30,28 @@ A Switch lets a user toggle a single setting on or off. It gives an immediate re import Switch from "carbon-react/lib/components/switch"; ``` -## Examples - -### Default - -The switch renders unchecked by default. The current state is shown by **On** and **Off** labels displayed outside the track. The component is controlled — you must manage `checked` state yourself and provide an `onChange` handler. - - - -### Checked - - - -### Disabled - -Set `disabled` to prevent user interaction. Both checked and unchecked disabled states are shown below. - - - - -### Size - -Two sizes are available: `small` (default) and `large`. - - - -### Label inline - -Set `labelInline` to place the text label beside the switch instead of above it. Use `labelSpacing` (1 or 2) to control the gap, and `labelWidth` to set the label width as a percentage when inline. - - - -#### Label inline with hint text - -When `labelInline` is set alongside `inputHint`, the hint text is displayed directly below the label, keeping it visually grouped with the label while the switch sits beside them. - - - -### Loading - -Set `loading` to show a loading spinner in place of the On/Off labels. The switch input is automatically disabled while loading. A `"Processing..."` label is shown to the right of the spinner by default. - - - -#### Custom processing label - -Use the `processingLabel` prop to override the default `"Processing..."` text with your own copy. - - - -#### Processing label below switch - -On small screens you may want the processing label to appear below the switch rather than beside it. Set `processingLabelBelowSwitch` to move it there. - - +## Playground + +Use this interactive example to explore Switch props with Storybook controls. + + + + ## Accessibility @@ -130,10 +97,9 @@ The following keys are available to override the translations for this component }, { name: "switch.processingLabel", - description: - "The text to display to indicate action is processing", + description: "The text to display to indicate action is processing", type: "func", returnType: "string", }, ]} -/> \ No newline at end of file +/> diff --git a/src/components/switch/switch.stories.tsx b/src/components/switch/switch.stories.tsx index 856b0285a2..4c39ad1f8d 100644 --- a/src/components/switch/switch.stories.tsx +++ b/src/components/switch/switch.stories.tsx @@ -12,6 +12,25 @@ const meta: Meta = { component: Switch, argTypes: { ...styledSystemProps, + size: { + options: ["small", "large"], + control: { type: "radio" }, + }, + label: { + control: "text", + }, + inputHint: { + control: "text", + }, + disabled: { + control: "boolean", + }, + loading: { + control: "boolean", + }, + required: { + control: "boolean", + }, }, parameters: { themeProvider: { chromatic: { theme: "sage" } }, @@ -22,164 +41,29 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = () => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -}; -Default.storyName = "Default"; - -export const Checked: Story = () => { - const [checked, setChecked] = useState(true); - return ( - setChecked(e.target.checked)} - /> - ); -}; -Checked.storyName = "Checked"; - -export const Disabled: Story = () => ( - {}} - /> -); -Disabled.storyName = "Disabled"; - -export const DisabledChecked: Story = () => ( - {}} - /> -); -DisabledChecked.storyName = "Disabled (checked)"; - -export const Required: Story = () => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - required - /> - ); -}; -Required.storyName = "Required"; - -export const LargeSize: Story = () => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -}; -LargeSize.storyName = "Large size"; - -export const LabelInline: Story = () => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); -}; -LabelInline.storyName = "Label inline"; - -export const LabelInlineWithHint: Story = () => { - const [checked, setChecked] = useState(false); - return ( - setChecked(e.target.checked)} - /> - ); +export const Playground: Story = { + render: (args) => { + const [checked, setChecked] = useState(false); + return ( + setChecked(e.target.checked)} + /> + ); + }, + args: { + label: "Toggle notifications", + inputHint: "Hint text", + disabled: false, + loading: false, + required: false, + size: "small", + processingLabel: "Processing...", + processingLabelBelowSwitch: false, + labelInline: false, + labelSpacing: 1, + labelWidth: 30, + }, }; -LabelInlineWithHint.storyName = "Label inline with hint"; - -export const Loading: Story = () => ( - <> - {}} - /> - - {}} - ml="8px" - /> - - {}} - ml="8px" - /> - - {}} - ml="8px" - /> - -); -Loading.storyName = "Loading"; - -export const LoadingCustomLabel: Story = () => ( - {}} - /> -); -LoadingCustomLabel.storyName = "Loading with custom processingLabel"; - -export const LoadingLabelBelow: Story = () => ( - {}} - /> -); -LoadingLabelBelow.storyName = "Loading with processingLabel below switch"; +Playground.storyName = "Playground"; From a2bee6d218bfc6219175863bb92ee2739b2d470c Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:44 +0100 Subject: [PATCH 16/19] docs(textbox): add playground example --- skills/carbon-react/components/textbox.md | 325 ------------------ .../textbox/textbox-test.stories.tsx | 247 +++++++++++++ src/components/textbox/textbox.mdx | 93 ++--- src/components/textbox/textbox.stories.tsx | 267 ++------------ 4 files changed, 301 insertions(+), 631 deletions(-) diff --git a/skills/carbon-react/components/textbox.md b/skills/carbon-react/components/textbox.md index 669204d60b..df2d9688d4 100644 --- a/skills/carbon-react/components/textbox.md +++ b/skills/carbon-react/components/textbox.md @@ -359,52 +359,6 @@ description: Carbon Textbox component props and usage examples. | aria-grabbed | Booleanish \| undefined | No | | Yes | in ARIA 1.1 | Indicates an element's "grabbed" state in a drag-and-drop operation. | | ## Examples -### Default - -**Render** - -```tsx -() => { - const [state, setState] = useState(""); - - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - -### Character Counter - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - ### Character Counter Translations **Render** @@ -445,282 +399,3 @@ description: Carbon Textbox component props and usage examples. } ``` - -### Prefix - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - -### Sizes - -**Render** - -```tsx -() => { - const [smallState, setSmallState] = useState(""); - const [mediumState, setMediumState] = useState(""); - const [largeState, setLargeState] = useState(""); - const setValue = ( - { target }: React.ChangeEvent, - size: string, - ) => { - if (size === "small") setSmallState(target.value); - else if (size === "medium") setMediumState(target.value); - else if (size === "large") setLargeState(target.value); - }; - return ( - - setValue(e, "small")} - placeholder="Textbox" - /> - - setValue(e, "medium")} - placeholder="Textbox" - /> - - setValue(e, "large")} - placeholder="Textbox" - /> - - ); -} -``` - - -### Margins - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -} -``` - - -### Disabled - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - -### Read Only - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - -### With Label Inline - -**Render** - -```tsx -() => { - const [state, setState] = useState(""); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -} -``` - - -### With Custom Label Width And Input Width - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -} -``` - - -### With Custom Max Width - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -} -``` - - -### With Field Help - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -} -``` - - -### With Input Hint - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -} -``` - - -### Required - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -} -``` - - -### IsOptional - -**Render** - -```tsx -() => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -} -``` - diff --git a/src/components/textbox/textbox-test.stories.tsx b/src/components/textbox/textbox-test.stories.tsx index 689decec3e..e0a94d577f 100644 --- a/src/components/textbox/textbox-test.stories.tsx +++ b/src/components/textbox/textbox-test.stories.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { action } from "storybook/actions"; +import { StoryObj } from "@storybook/react-vite"; import Textbox, { TextboxProps } from "."; import Box from "../box"; import Link from "../link"; @@ -274,3 +275,249 @@ export const FormFieldRelativePosition = () => { ); }; FormFieldRelativePosition.storyName = "Form Field Relative Position"; + +type Story = StoryObj; + +// Documentation regression stories moved from the public docs. + +export const DocumentationDefault: Story = () => { + const [state, setState] = useState(""); + + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +DocumentationDefault.storyName = "DocumentationDefault"; + +export const CharacterCounter: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +CharacterCounter.storyName = "Character Counter"; + +export const Prefix: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +Prefix.storyName = "Prefix"; + +export const Sizes: Story = () => { + const [smallState, setSmallState] = useState(""); + const [mediumState, setMediumState] = useState(""); + const [largeState, setLargeState] = useState(""); + const setValue = ( + { target }: React.ChangeEvent, + size: string, + ) => { + if (size === "small") setSmallState(target.value); + else if (size === "medium") setMediumState(target.value); + else if (size === "large") setLargeState(target.value); + }; + return ( + + setValue(e, "small")} + placeholder="Textbox" + /> + + setValue(e, "medium")} + placeholder="Textbox" + /> + + setValue(e, "large")} + placeholder="Textbox" + /> + + ); +}; +Sizes.storyName = "Sizes"; + +export const Margins: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ; +}; +Margins.storyName = "Margins"; + +export const Disabled: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +Disabled.storyName = "Disabled"; + +export const ReadOnly: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +ReadOnly.storyName = "Read Only"; + +export const WithLabelInline: Story = () => { + const [state, setState] = useState(""); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ( + + ); +}; +WithLabelInline.storyName = "With Label Inline"; + +export const WithCustomLabelWidthAndInputWidth: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + + return ( + + ); +}; +WithCustomLabelWidthAndInputWidth.storyName = + "With Custom Label Width And Input Width"; + +export const WithCustomMaxWidth: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + + return ( + + ); +}; +WithCustomMaxWidth.storyName = "With Custom Max Width"; + +export const WithFieldHelp: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + + return ( + + ); +}; +WithFieldHelp.storyName = "With Field Help"; + +export const WithInputHint: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + + return ( + + ); +}; +WithInputHint.storyName = "With Input Hint"; + +export const Required: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ; +}; +Required.storyName = "Required"; + +export const IsOptional: Story = () => { + const [state, setState] = useState("Textbox"); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ; +}; +IsOptional.storyName = "IsOptional"; diff --git a/src/components/textbox/textbox.mdx b/src/components/textbox/textbox.mdx index e817cc771f..9b46d35286 100644 --- a/src/components/textbox/textbox.mdx +++ b/src/components/textbox/textbox.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; import * as TextboxStories from "./textbox.stories"; @@ -32,6 +32,33 @@ Captures a single line of text. import Textbox from "carbon-react/lib/components/textbox"; ``` +## Playground + +Use this interactive example to explore Textbox props with Storybook controls. + + + + + ## Designer Notes - Use placeholder text to give the user examples of data formats (e.g. AB123456C for a UK National Insurance number). @@ -42,24 +69,6 @@ import Textbox from "carbon-react/lib/components/textbox"; ## Examples -### Default - - - -### With inputHint - -When the `inputHint` prop is passed, please use a full stop `.` at the end. This forces a pause -before any other announcements, this well help screen reader users understand the hint fully. - - - -### Character counter - -If you use the `inputHint` prop to provide the user with a hint before the input, please use a full stop `.` at the end, -as it forces a pause before any other announcements, this well help screen reader users understand the hint fully. - - - ### Character counter with translations Various translations can be applied to both the visually hidden hint message, and the character @@ -71,52 +80,6 @@ Include the formatted number count wherever makes sense for the language you're -### Prefix - - - -### Sizes - - - -### Margins - - - -### Disabled - - - -### ReadOnly - - - -### With custom maxWidth - - - -### Required - -You can use the `required` prop to indicate if the field is mandatory. - - - -### With labelInline - -Use the `labelInline` prop to display the label on the same horizontal row as the input. - - - -### With custom labelWidth and inputWidth - - - -### With fieldHelp (legacy) - -**Note:** This is a legacy feature and will only render if the `validationRedesignOptIn` feature flag on an ancestor [CarbonProvider](../?path=/docs/carbon-provider--docs) is *false*. - - - ## Validation States This component supports input validation, see our [Validations](../?path=/docs/documentation-validations--docs) documentation page for more information. diff --git a/src/components/textbox/textbox.stories.tsx b/src/components/textbox/textbox.stories.tsx index 127a637349..36610a83a8 100644 --- a/src/components/textbox/textbox.stories.tsx +++ b/src/components/textbox/textbox.stories.tsx @@ -4,7 +4,6 @@ import { Meta, StoryObj } from "@storybook/react-vite"; import I18nProvider from "../i18n-provider"; import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props"; -import Box from "../box"; import Textbox from "."; const styledSystemProps = generateStyledSystemProps({ @@ -28,39 +27,33 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = () => { - const [state, setState] = useState(""); - - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -}; -Default.storyName = "Default"; - -export const CharacterCounter: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); +export const Playground: Story = { + render: (args) => { + const [state, setState] = useState(""); + const setValue = ({ target }: React.ChangeEvent) => { + setState(target.value); + }; + return ; + }, + args: { + label: "Textbox", + placeholder: "Placeholder", + disabled: false, + readOnly: false, + required: false, + size: "medium", + inputHint: "Hint text", + prefix: "£", + inputIcon: "search", + inputWidth: 100, + maxWidth: "100%", + labelInline: false, + characterLimit: 50, + error: "", + warning: "", + }, }; -CharacterCounter.storyName = "Character Counter"; +Playground.storyName = "Playground"; export const CharacterCounterTranslations: Story = () => { const [state, setState] = useState("Textbox"); @@ -96,211 +89,3 @@ export const CharacterCounterTranslations: Story = () => { ); }; CharacterCounterTranslations.storyName = "Character Counter Translations"; - -export const Prefix: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -}; -Prefix.storyName = "Prefix"; - -export const Sizes: Story = () => { - const [smallState, setSmallState] = useState(""); - const [mediumState, setMediumState] = useState(""); - const [largeState, setLargeState] = useState(""); - const setValue = ( - { target }: React.ChangeEvent, - size: string, - ) => { - if (size === "small") setSmallState(target.value); - else if (size === "medium") setMediumState(target.value); - else if (size === "large") setLargeState(target.value); - }; - return ( - - setValue(e, "small")} - placeholder="Textbox" - /> - - setValue(e, "medium")} - placeholder="Textbox" - /> - - setValue(e, "large")} - placeholder="Textbox" - /> - - ); -}; -Sizes.storyName = "Sizes"; - -export const Margins: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -}; -Margins.storyName = "Margins"; - -export const Disabled: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -}; -Disabled.storyName = "Disabled"; - -export const ReadOnly: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -}; -ReadOnly.storyName = "Read Only"; - -export const WithLabelInline: Story = () => { - const [state, setState] = useState(""); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ( - - ); -}; -WithLabelInline.storyName = "With Label Inline"; - -export const WithCustomLabelWidthAndInputWidth: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -}; -WithCustomLabelWidthAndInputWidth.storyName = - "With Custom Label Width And Input Width"; - -export const WithCustomMaxWidth: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -}; -WithCustomMaxWidth.storyName = "With Custom Max Width"; - -export const WithFieldHelp: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -}; -WithFieldHelp.storyName = "With Field Help"; - -export const WithInputHint: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - - return ( - - ); -}; -WithInputHint.storyName = "With Input Hint"; - -export const Required: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -}; -Required.storyName = "Required"; - -export const IsOptional: Story = () => { - const [state, setState] = useState("Textbox"); - const setValue = ({ target }: React.ChangeEvent) => { - setState(target.value); - }; - return ; -}; -IsOptional.storyName = "IsOptional"; From 0b8767526c910e8952ffcb0aa14c3d88ffc417ce Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:49 +0100 Subject: [PATCH 17/19] docs(time): add playground example --- skills/carbon-react/components/time.md | 185 ---------------------- src/components/time/time.mdx | 61 ++++---- src/components/time/time.stories.tsx | 203 ++++++------------------- 3 files changed, 80 insertions(+), 369 deletions(-) diff --git a/skills/carbon-react/components/time.md b/skills/carbon-react/components/time.md index 5c3abc3177..9547e7ad33 100644 --- a/skills/carbon-react/components/time.md +++ b/skills/carbon-react/components/time.md @@ -98,191 +98,6 @@ description: Carbon Time component props and usage examples. ``` -### Input Hint - -**Render** - -```tsx -() => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -} -``` - - -### Required - -**Render** - -```tsx -() => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -} -``` - - -### Disabled - -**Render** - -```tsx -() => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - period: "AM", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -} -``` - - -### Read Only - -**Render** - -```tsx -() => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - period: "AM", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -} -``` - - -### Sizes - -**Render** - -```tsx -() => { - const [value, setValue] = useState<{ - small: TimeValue; - medium: TimeValue; - large: TimeValue; - }>({ - small: { - hours: "", - minutes: "", - period: "AM", - }, - medium: { - hours: "", - minutes: "", - period: "AM", - }, - large: { - hours: "", - minutes: "", - period: "AM", - }, - }); - - const handleChange = ( - ev: TimeInputEvent, - size: "small" | "medium" | "large", - ) => { - setValue((p) => ({ - ...p, - [size]: ev.target.value, - })); - }; - - return ( - - - ); -} -``` - - ### Focusing Inputs Programmatically **Render** diff --git a/src/components/time/time.mdx b/src/components/time/time.mdx index fe29ce36b4..135375d4fa 100644 --- a/src/components/time/time.mdx +++ b/src/components/time/time.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import TranslationKeysTable from "../../../.storybook/utils/translation-keys-table"; @@ -31,6 +31,35 @@ import * as TimeStories from "./time.stories"; import { Time, type TimeHandle } from "carbon-react/lib/components/time"; ``` +## Designer Notes + +A Time input captures time entered in hours and minutes. +Use it to capture: + +- Start and end times. For example, in a HR timesheet. +- Duration. For example, in an invoice. + +## Playground + +Use this interactive example to explore Time props with Storybook controls. + + + + + ### Validation This component supports input validation, see our [Validations](../?path=/docs/documentation-validations--docs) documentation page for more information. @@ -50,28 +79,6 @@ In order to render the AM/PM toggle controls you should set the `period` propert -### Input hint - -Passing a string to the `inputHint` prop will render some additional hint text above the inputs. - - - -### Required - - - -### Disabled - - - -### Read only - - - -### Sizes - - - ### Focusing the inputs programmatically The component exposes `focusHoursInput` and `focusMinutesInput` functions that support programmatically @@ -141,7 +148,7 @@ to the [i18nProvider](../?path=/docs/documentation-i18n--docs). `Time`'s forwarded ref exposes the following imperative methods: -| Method Name | Description | -| ----------------------- | ----------------------------------------------- | -| `focusHoursInput()` | Programmatically focuses the hours input. | -| `focusMinutesInput()` | Programmatically focuses the minutes input. | +| Method Name | Description | +| --------------------- | ------------------------------------------- | +| `focusHoursInput()` | Programmatically focuses the hours input. | +| `focusMinutesInput()` | Programmatically focuses the minutes input. | diff --git a/src/components/time/time.stories.tsx b/src/components/time/time.stories.tsx index 73c9a405c5..70a37887f5 100644 --- a/src/components/time/time.stories.tsx +++ b/src/components/time/time.stories.tsx @@ -4,7 +4,7 @@ import { ArgTypes, Meta, StoryObj } from "@storybook/react-vite"; import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props"; import I18nProvider from "../i18n-provider"; import Box from "../box"; -import Button from "../button"; +import Button from "../button/__next__"; import { TimeHandle, TimeInputEvent, @@ -29,12 +29,56 @@ const meta: Meta = { }, argTypes: { ...styledSystemProps, - }, + showAmPmToggle: { + control: "boolean", + description: "Show AM/PM toggle", + table: { + category: "Story", + }, + }, + } as never, }; export default meta; type Story = StoryObj; +export const Playground: Story = { + render: ({ + showAmPmToggle, + ...args + }: TimeProps & { showAmPmToggle?: boolean }) => { + const [value, setValue] = useState({ + hours: "", + minutes: "", + period: "AM", + }); + + const handleChange = (ev: TimeInputEvent) => { + setValue(ev.target.value); + }; + + // Conditionally include or exclude the period based on the toggle + const displayValue: TimeValue = showAmPmToggle + ? value + : { hours: value.hours, minutes: value.minutes }; + + return ( + + + ); + }, + args: { + label: "Time", + disabled: false, + readOnly: false, + required: false, + size: "medium", + showAmPmToggle: true, + } as never, +}; +Playground.storyName = "Playground"; + export const Default: Story = ({ ...args }) => { const [value, setValue] = useState({ hours: "", @@ -75,161 +119,6 @@ AmPmToggle.parameters = { themeProvider: { chromatic: { theme: "sage" } }, }; -export const InputHint: Story = () => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -}; -InputHint.storyName = "Input Hint"; - -export const Required: Story = () => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -}; -Required.storyName = "Required"; - -export const Disabled: Story = () => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - period: "AM", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -}; -Disabled.storyName = "Disabled"; - -export const ReadOnly: Story = () => { - const [value, setValue] = useState({ - hours: "", - minutes: "", - period: "AM", - }); - - const handleChange = (ev: TimeInputEvent) => { - setValue(ev.target.value); - }; - - return ( - - - ); -}; -ReadOnly.storyName = "Read Only"; - -export const Sizes: Story = () => { - const [value, setValue] = useState<{ - small: TimeValue; - medium: TimeValue; - large: TimeValue; - }>({ - small: { - hours: "", - minutes: "", - period: "AM", - }, - medium: { - hours: "", - minutes: "", - period: "AM", - }, - large: { - hours: "", - minutes: "", - period: "AM", - }, - }); - - const handleChange = ( - ev: TimeInputEvent, - size: "small" | "medium" | "large", - ) => { - setValue((p) => ({ - ...p, - [size]: ev.target.value, - })); - }; - - return ( - - - ); -}; -Sizes.storyName = "Sizes"; - export const FocusingInputs: Story = () => { const [value, setValue] = useState({ hours: "", From 25e9b85826248c8dc9ac0c6b353479d58315cc4c Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 31 Jul 2026 13:17:54 +0100 Subject: [PATCH 18/19] docs(typography): add playground example --- skills/carbon-react/components/typography.md | 405 ------------------ .../typography/typography-test.stories.tsx | 391 +++++++++++++++++ src/components/typography/typography.mdx | 66 ++- .../typography/typography.stories.tsx | 384 ++--------------- 4 files changed, 443 insertions(+), 803 deletions(-) diff --git a/skills/carbon-react/components/typography.md b/skills/carbon-react/components/typography.md index ee3f41f23e..3ac78607e1 100644 --- a/skills/carbon-react/components/typography.md +++ b/skills/carbon-react/components/typography.md @@ -77,24 +77,6 @@ description: Carbon Typography component props and usage examples. | truncate | boolean \| undefined | No | | Yes | Use `textOverflow` and `whiteSpace` props instead. This prop will eventually be removed. Apply truncation | | false | ## Examples -### Playground - -**Args** - -```tsx -{ - children: "Typography content", - variant: "p", - } -``` - -**Render** - -```tsx -(args) => {args.children} -``` - - ### Variants **Render** @@ -134,390 +116,3 @@ description: Carbon Typography component props and usage examples. ) ``` - -### Fluid - -**Render** - -```tsx -() => ( - - - Paragraph (Default) - - - Heading Level 1 - - - Heading Level 2 - - - Heading Level 3 - - - Heading Level 4 - - - Heading Level 5 - - - Segment Header - - - Segment Subheader - - - Strong Text - - - Bold Text - - - This text contains{" "} - - superscript - {" "} - content - - - This text contains{" "} - - subscript - {" "} - content - - -
  • Unordered List
  • -
  • Unordered List
  • -
  • Unordered List
  • -
    - -
  • Ordered List
  • -
  • Ordered List
  • -
  • Ordered List
  • -
    -
    -) -``` - - -### Inverse - -**Render** - -```tsx -() => ( - - - Paragraph (Default) - - - Heading Level 1 - - - Heading Level 2 - - - Heading Level 3 - - - Heading Level 4 - - - Heading Level 5 - - - Segment Header - - - Segment Subheader - - - Strong Text - - - Bold Text - - - This text contains{" "} - - superscript - {" "} - content - - - This text contains{" "} - - subscript - {" "} - content - - -
  • Unordered List
  • -
  • Unordered List
  • -
  • Unordered List
  • -
    - -
  • Ordered List
  • -
  • Ordered List
  • -
  • Ordered List
  • -
    -
    -) -``` - - -### Size - -**Render** - -```tsx -() => ( - - - M size paragraph text - - - L size paragraph text - - - Strong M - - - Strong L - - - Bold M - - - Bold L - - - Text with{" "} - - superscript - {" "} - M - - - Text with{" "} - - superscript - {" "} - L - - - Text with{" "} - - subscript - {" "} - M - - - Text with{" "} - - subscript - {" "} - L - - -
  • Unordered List M
  • -
  • Unordered List M
  • -
  • Unordered List M
  • -
    - -
  • Unordered List L
  • -
  • Unordered List L
  • -
  • Unordered List L
  • -
    - -
  • Ordered List M
  • -
  • Ordered List M
  • -
  • Ordered List M
  • -
    - -
  • Ordered List L
  • -
  • Ordered List L
  • -
  • Ordered List L
  • -
    -
    -) -``` - - -### Color - -**Render** - -```tsx -() => ( - - - H1 neutral - - - H2 subtle - - - H3 caution - - - Section heading info - - - Section subheading positive - - - Neutral paragraph text - - - Subtle paragraph text - - - Strong caution - - - Strong info - - - Bold negative - - - Bold positive - - - Text with{" "} - - superscript - {" "} - caution - - - Text with{" "} - - superscript - {" "} - info - - - Text with{" "} - - subscript - {" "} - negative - - - Text with{" "} - - subscript - {" "} - positive - - -
  • Unordered List Subtle
  • -
  • Unordered List Subtle
  • -
  • Unordered List Subtle
  • -
    - -
  • Unordered List Caution
  • -
  • Unordered List Caution
  • -
  • Unordered List Caution
  • -
    - -
  • Ordered List Info
  • -
  • Ordered List Info
  • -
  • Ordered List Info
  • -
    - -
  • Ordered List Positive
  • -
  • Ordered List Positive
  • -
  • Ordered List Positive
  • -
    -
    -) -``` - - -### Weight - -**Render** - -```tsx -() => ( - - - Regular weight paragraph text - - - Medium weight paragraph text - - - Strong Regular - - - Strong Medium - - - Bold Regular - - - Bold Medium - - - Text with{" "} - - superscript - {" "} - regular - - - Text with{" "} - - superscript - {" "} - medium - - - Text with{" "} - - subscript - {" "} - regular - - - Text with{" "} - - subscript - {" "} - medium - - -
  • Unordered List Regular
  • -
  • Unordered List Regular
  • -
  • Unordered List Regular
  • -
    - -
  • Unordered List Medium
  • -
  • Unordered List Medium
  • -
  • Unordered List Medium
  • -
    - -
  • Ordered List Regular
  • -
  • Ordered List Regular
  • -
  • Ordered List Regular
  • -
    - -
  • Ordered List Medium
  • -
  • Ordered List Medium
  • -
  • Ordered List Medium
  • -
    -
    -) -``` - diff --git a/src/components/typography/typography-test.stories.tsx b/src/components/typography/typography-test.stories.tsx index 6b843bf11a..57125fd0cc 100644 --- a/src/components/typography/typography-test.stories.tsx +++ b/src/components/typography/typography-test.stories.tsx @@ -469,3 +469,394 @@ VisualRegressionMatrix.storyName = "Visual Regression Matrix"; VisualRegressionMatrix.parameters = { chromatic: { viewports: [1800] }, }; + +export const VariantsStory = () => ( + + Paragraph (Default) + Heading Level 1 + Heading Level 2 + Heading Level 3 + Heading Level 4 + Heading Level 5 + Section Heading + Section Subheading + Strong Text + Bold Text + + This text contains superscript{" "} + content + + + This text contains subscript{" "} + content + + +
  • Unordered List
  • +
  • Unordered List
  • +
  • Unordered List
  • +
    + +
  • Ordered List
  • +
  • Ordered List
  • +
  • Ordered List
  • +
    +
    +); +VariantsStory.storyName = "Variants"; + +export const FluidStory = () => ( + + + Paragraph (Default) + + + Heading Level 1 + + + Heading Level 2 + + + Heading Level 3 + + + Heading Level 4 + + + Heading Level 5 + + + Segment Header + + + Segment Subheader + + + Strong Text + + + Bold Text + + + This text contains{" "} + + superscript + {" "} + content + + + This text contains{" "} + + subscript + {" "} + content + + +
  • Unordered List
  • +
  • Unordered List
  • +
  • Unordered List
  • +
    + +
  • Ordered List
  • +
  • Ordered List
  • +
  • Ordered List
  • +
    +
    +); +FluidStory.storyName = "Fluid"; + +export const InverseStory = () => ( + + + Paragraph (Default) + + + Heading Level 1 + + + Heading Level 2 + + + Heading Level 3 + + + Heading Level 4 + + + Heading Level 5 + + + Segment Header + + + Segment Subheader + + + Strong Text + + + Bold Text + + + This text contains{" "} + + superscript + {" "} + content + + + This text contains{" "} + + subscript + {" "} + content + + +
  • Unordered List
  • +
  • Unordered List
  • +
  • Unordered List
  • +
    + +
  • Ordered List
  • +
  • Ordered List
  • +
  • Ordered List
  • +
    +
    +); +InverseStory.storyName = "Inverse"; + +export const SizeStory = () => ( + + + M size paragraph text + + + L size paragraph text + + + Strong M + + + Strong L + + + Bold M + + + Bold L + + + Text with{" "} + + superscript + {" "} + M + + + Text with{" "} + + superscript + {" "} + L + + + Text with{" "} + + subscript + {" "} + M + + + Text with{" "} + + subscript + {" "} + L + + +
  • Unordered List M
  • +
  • Unordered List M
  • +
  • Unordered List M
  • +
    + +
  • Unordered List L
  • +
  • Unordered List L
  • +
  • Unordered List L
  • +
    + +
  • Ordered List M
  • +
  • Ordered List M
  • +
  • Ordered List M
  • +
    + +
  • Ordered List L
  • +
  • Ordered List L
  • +
  • Ordered List L
  • +
    +
    +); +SizeStory.storyName = "Size"; + +export const ColorStory = () => ( + + + H1 neutral + + + H2 subtle + + + H3 caution + + + Section heading info + + + Section subheading positive + + + Neutral paragraph text + + + Subtle paragraph text + + + Strong caution + + + Strong info + + + Bold negative + + + Bold positive + + + Text with{" "} + + superscript + {" "} + caution + + + Text with{" "} + + superscript + {" "} + info + + + Text with{" "} + + subscript + {" "} + negative + + + Text with{" "} + + subscript + {" "} + positive + + +
  • Unordered List Subtle
  • +
  • Unordered List Subtle
  • +
  • Unordered List Subtle
  • +
    + +
  • Unordered List Caution
  • +
  • Unordered List Caution
  • +
  • Unordered List Caution
  • +
    + +
  • Ordered List Info
  • +
  • Ordered List Info
  • +
  • Ordered List Info
  • +
    + +
  • Ordered List Positive
  • +
  • Ordered List Positive
  • +
  • Ordered List Positive
  • +
    +
    +); +ColorStory.storyName = "Color"; + +export const WeightStory = () => ( + + + Regular weight paragraph text + + + Medium weight paragraph text + + + Strong Regular + + + Strong Medium + + + Bold Regular + + + Bold Medium + + + Text with{" "} + + superscript + {" "} + regular + + + Text with{" "} + + superscript + {" "} + medium + + + Text with{" "} + + subscript + {" "} + regular + + + Text with{" "} + + subscript + {" "} + medium + + +
  • Unordered List Regular
  • +
  • Unordered List Regular
  • +
  • Unordered List Regular
  • +
    + +
  • Unordered List Medium
  • +
  • Unordered List Medium
  • +
  • Unordered List Medium
  • +
    + +
  • Ordered List Regular
  • +
  • Ordered List Regular
  • +
  • Ordered List Regular
  • +
    + +
  • Ordered List Medium
  • +
  • Ordered List Medium
  • +
  • Ordered List Medium
  • +
    +
    +); +WeightStory.storyName = "Weight"; diff --git a/src/components/typography/typography.mdx b/src/components/typography/typography.mdx index ec3a3c0419..68c0581954 100644 --- a/src/components/typography/typography.mdx +++ b/src/components/typography/typography.mdx @@ -1,4 +1,4 @@ -import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks"; +import { Meta, ArgTypes, Canvas, Controls } from "@storybook/addon-docs/blocks"; import * as TypographyStories from "./typography.stories.tsx"; @@ -20,6 +20,7 @@ Manages text styles and content hierarchies. ## Contents - [Quick Start](#quick-start) +- [Playground](#playground) - [Examples](#examples) - [Props](#props) @@ -34,55 +35,38 @@ import Typography, { ## Designer Notes -- The Typography component provides a consistent and flexible system for managing text styles across your design. +- The Typography component provides a consistent and flexible system for managing text styles across your design. - It includes multiple heading and body text options to accommodate various content hierarchies. -- With theme support for large screens and small screens, this helps ensure readability and brand alignment across different contexts. +- With theme support for large screens and small screens, this helps ensure readability and brand alignment across different contexts. -## Examples - -### Variants - -Use the `variant` prop to render an element and creates a visual style associated with said element. The `as` prop can also be -used to override the underlying HTML element. - - - - -## Fluid - -When set to `true`, the component uses fluid typography with CSS clamp() values for responsive sizing. -This allows the text to scale smoothly between breakpoints without requiring media queries. +## Playground - +Use this interactive example to explore Typography props with Storybook controls. -## Inverse + -When set to `true`, inverts the font colour for use on darker backgrounds. -This ensures sufficient contrast and readability when the `Typography` component is placed over dark container backgrounds. + - - -## Size - -The `size` prop controls the font size applied to text. -Available on the following variants: `"p"`, `"ul"`, `"ol"`, `"strong"`, `"b"`, `"sup"`, and `"sub"`. Choose between `"M"` for standard size and `"L"` for larger text. - - - -## Color - -The `color` prop applies token-based text colours. -Available options are `"neutral"`, `"subtle"`, `"caution"`, `"info"`, `"negative"`, and `"positive"`. -Available on all variants, including heading variants (`"h1"` to `"h5"`, `"section-heading"`, and `"section-subheading"`). - - +## Examples -## Weight +### Variants -The `weight` prop controls the font weight applied to text. -Available on the following variants: `"p"`, `"ul"`, `"ol"`,`"sup"`, and `"sub"`. Choose between `"regular"` for normal weight or `"medium"` for heavier emphasis. +Use the `variant` prop to render an element and creates a visual style associated with said element. The `as` prop can also be +used to override the underlying HTML element. - + ## Props diff --git a/src/components/typography/typography.stories.tsx b/src/components/typography/typography.stories.tsx index 4c4ef51eb3..3150fbc449 100644 --- a/src/components/typography/typography.stories.tsx +++ b/src/components/typography/typography.stories.tsx @@ -65,6 +65,33 @@ const meta: Meta = { export default meta; type Story = StoryObj; +export const Playground: Story = { + render: (args) => { + const content = {args.children}; + + if (args.inverse) { + return ( + + {content} + + ); + } + + return content; + }, + args: { + children: "Typography content", + variant: "p", + }, + argTypes: { + color: { + control: "select", + options: ["neutral", "subtle", "caution", "info", "negative", "positive"], + }, + }, +}; +Playground.storyName = "Playground"; + export const VariantsStory: Story = () => ( Paragraph (Default) @@ -98,360 +125,3 @@ export const VariantsStory: Story = () => ( ); VariantsStory.storyName = "Variants"; - -export const FluidStory: Story = () => ( - - - Paragraph (Default) - - - Heading Level 1 - - - Heading Level 2 - - - Heading Level 3 - - - Heading Level 4 - - - Heading Level 5 - - - Segment Header - - - Segment Subheader - - - Strong Text - - - Bold Text - - - This text contains{" "} - - superscript - {" "} - content - - - This text contains{" "} - - subscript - {" "} - content - - -
  • Unordered List
  • -
  • Unordered List
  • -
  • Unordered List
  • -
    - -
  • Ordered List
  • -
  • Ordered List
  • -
  • Ordered List
  • -
    -
    -); -FluidStory.storyName = "Fluid"; - -export const InverseStory: Story = () => ( - - - Paragraph (Default) - - - Heading Level 1 - - - Heading Level 2 - - - Heading Level 3 - - - Heading Level 4 - - - Heading Level 5 - - - Segment Header - - - Segment Subheader - - - Strong Text - - - Bold Text - - - This text contains{" "} - - superscript - {" "} - content - - - This text contains{" "} - - subscript - {" "} - content - - -
  • Unordered List
  • -
  • Unordered List
  • -
  • Unordered List
  • -
    - -
  • Ordered List
  • -
  • Ordered List
  • -
  • Ordered List
  • -
    -
    -); -InverseStory.storyName = "Inverse"; - -export const SizeStory: Story = () => ( - - - M size paragraph text - - - L size paragraph text - - - Strong M - - - Strong L - - - Bold M - - - Bold L - - - Text with{" "} - - superscript - {" "} - M - - - Text with{" "} - - superscript - {" "} - L - - - Text with{" "} - - subscript - {" "} - M - - - Text with{" "} - - subscript - {" "} - L - - -
  • Unordered List M
  • -
  • Unordered List M
  • -
  • Unordered List M
  • -
    - -
  • Unordered List L
  • -
  • Unordered List L
  • -
  • Unordered List L
  • -
    - -
  • Ordered List M
  • -
  • Ordered List M
  • -
  • Ordered List M
  • -
    - -
  • Ordered List L
  • -
  • Ordered List L
  • -
  • Ordered List L
  • -
    -
    -); -SizeStory.storyName = "Size"; - -export const ColorStory: Story = () => ( - - - H1 neutral - - - H2 subtle - - - H3 caution - - - Section heading info - - - Section subheading positive - - - Neutral paragraph text - - - Subtle paragraph text - - - Strong caution - - - Strong info - - - Bold negative - - - Bold positive - - - Text with{" "} - - superscript - {" "} - caution - - - Text with{" "} - - superscript - {" "} - info - - - Text with{" "} - - subscript - {" "} - negative - - - Text with{" "} - - subscript - {" "} - positive - - -
  • Unordered List Subtle
  • -
  • Unordered List Subtle
  • -
  • Unordered List Subtle
  • -
    - -
  • Unordered List Caution
  • -
  • Unordered List Caution
  • -
  • Unordered List Caution
  • -
    - -
  • Ordered List Info
  • -
  • Ordered List Info
  • -
  • Ordered List Info
  • -
    - -
  • Ordered List Positive
  • -
  • Ordered List Positive
  • -
  • Ordered List Positive
  • -
    -
    -); -ColorStory.storyName = "Color"; - -export const WeightStory: Story = () => ( - - - Regular weight paragraph text - - - Medium weight paragraph text - - - Strong Regular - - - Strong Medium - - - Bold Regular - - - Bold Medium - - - Text with{" "} - - superscript - {" "} - regular - - - Text with{" "} - - superscript - {" "} - medium - - - Text with{" "} - - subscript - {" "} - regular - - - Text with{" "} - - subscript - {" "} - medium - - -
  • Unordered List Regular
  • -
  • Unordered List Regular
  • -
  • Unordered List Regular
  • -
    - -
  • Unordered List Medium
  • -
  • Unordered List Medium
  • -
  • Unordered List Medium
  • -
    - -
  • Ordered List Regular
  • -
  • Ordered List Regular
  • -
  • Ordered List Regular
  • -
    - -
  • Ordered List Medium
  • -
  • Ordered List Medium
  • -
  • Ordered List Medium
  • -
    -
    -); -WeightStory.storyName = "Weight"; From 01d0066874d9094de3d09dc5250065ca5e4c12c8 Mon Sep 17 00:00:00 2001 From: Daniel Dipper Date: Fri, 28 Aug 2026 15:31:54 +0100 Subject: [PATCH 19/19] docs(badge): make comment changes to playground --- src/components/badge/badge.stories.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/badge/badge.stories.tsx b/src/components/badge/badge.stories.tsx index c572f01471..1acef2f131 100644 --- a/src/components/badge/badge.stories.tsx +++ b/src/components/badge/badge.stories.tsx @@ -22,15 +22,13 @@ const meta: Meta = { ...styledSystemProps, counter: { control: { - type: "number", + type: "text", }, }, size: { - options: ["small", "medium", "large"], control: { type: "radio" }, }, variant: { - options: ["typical", "subtle"], control: { type: "radio" }, }, }, @@ -106,7 +104,6 @@ export const Playground: StoryObj = { ), ], }; -Playground.storyName = "Playground"; export const Default: Story = ({ ...args }) => { return (