From 1aa05950553e39b1b54ce87287ed6acf39758b23 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Wed, 17 Jun 2026 19:41:02 -0700 Subject: [PATCH 01/11] feat: add Figma design-link toolbar tool Co-Authored-By: Claude --- .storybook/figma/constants.ts | 3 ++ .storybook/figma/figma-icon.tsx | 14 ++++++++ .storybook/figma/figma-tool.tsx | 35 ++++++++++++++++++++ .storybook/figma/figma.test.ts | 58 +++++++++++++++++++++++++++++++++ .storybook/figma/figma.ts | 33 +++++++++++++++++++ .storybook/figma/index.ts | 2 ++ .storybook/manager.ts | 11 +++++++ 7 files changed, 156 insertions(+) create mode 100644 .storybook/figma/constants.ts create mode 100644 .storybook/figma/figma-icon.tsx create mode 100644 .storybook/figma/figma-tool.tsx create mode 100644 .storybook/figma/figma.test.ts create mode 100644 .storybook/figma/figma.ts create mode 100644 .storybook/figma/index.ts diff --git a/.storybook/figma/constants.ts b/.storybook/figma/constants.ts new file mode 100644 index 00000000..0dfbe9da --- /dev/null +++ b/.storybook/figma/constants.ts @@ -0,0 +1,3 @@ +export const ADDON_ID = 'reactist/figma' +export const TOOL_ID = `${ADDON_ID}/tool` +export const FIGMA_PARAMETER = 'figma' diff --git a/.storybook/figma/figma-icon.tsx b/.storybook/figma/figma-icon.tsx new file mode 100644 index 00000000..9241a2f6 --- /dev/null +++ b/.storybook/figma/figma-icon.tsx @@ -0,0 +1,14 @@ +import * as React from 'react' + +// https://www.figma.com/using-the-figma-brand/ +export function FigmaIcon() { + return ( + + ) +} diff --git a/.storybook/figma/figma-tool.tsx b/.storybook/figma/figma-tool.tsx new file mode 100644 index 00000000..27e57417 --- /dev/null +++ b/.storybook/figma/figma-tool.tsx @@ -0,0 +1,35 @@ +import * as React from 'react' + +import { useParameter } from 'storybook/manager-api' + +import { Badge } from '../../src/badge/badge' +import { IconButton } from '../../src/button/button' +import { Inline } from '../../src/inline' + +import { FIGMA_PARAMETER } from './constants' +import { resolveFigmaLinks } from './figma' +import { FigmaIcon } from './figma-icon' + +export const FigmaTool = React.memo(function FigmaTool() { + const figmaParameter = useParameter(FIGMA_PARAMETER, undefined) + const links = React.useMemo(() => resolveFigmaLinks(figmaParameter), [figmaParameter]) + + return ( + + {links.length === 0 ? ( + + ) : ( + links.map((link) => ( + } + aria-label={`View in Figma: ${link.label}`} + tooltip={link.label} + render={} + /> + )) + )} + + ) +}) diff --git a/.storybook/figma/figma.test.ts b/.storybook/figma/figma.test.ts new file mode 100644 index 00000000..38d15f99 --- /dev/null +++ b/.storybook/figma/figma.test.ts @@ -0,0 +1,58 @@ +import { resolveFigmaLinks } from './figma' + +describe('resolveFigmaLinks', () => { + it('resolves a bare string as both url and label', () => { + expect(resolveFigmaLinks('https://figma.com/design/abc?node-id=1-2')).toEqual([ + { + label: 'https://figma.com/design/abc?node-id=1-2', + url: 'https://figma.com/design/abc?node-id=1-2', + }, + ]) + }) + + it('resolves an object with label and url', () => { + expect( + resolveFigmaLinks({ + label: 'Web › Buttons › Button', + url: 'https://figma.com/design/abc?node-id=1-2', + }), + ).toEqual([ + { + label: 'Web › Buttons › Button', + url: 'https://figma.com/design/abc?node-id=1-2', + }, + ]) + }) + + it('falls back to the url as label when label is missing', () => { + expect(resolveFigmaLinks({ url: 'https://figma.com/design/abc' })).toEqual([ + { label: 'https://figma.com/design/abc', url: 'https://figma.com/design/abc' }, + ]) + }) + + it('resolves an array, dropping malformed entries', () => { + expect( + resolveFigmaLinks([ + 'https://figma.com/a', + { label: 'B', url: 'https://figma.com/b' }, + { label: 'C' }, + { url: 42 }, + '', + null, + ]), + ).toEqual([ + { label: 'https://figma.com/a', url: 'https://figma.com/a' }, + { label: 'B', url: 'https://figma.com/b' }, + ]) + }) + + it('treats malformed and empty params as no links', () => { + expect(resolveFigmaLinks(undefined)).toEqual([]) + expect(resolveFigmaLinks(null)).toEqual([]) + expect(resolveFigmaLinks('')).toEqual([]) + expect(resolveFigmaLinks({})).toEqual([]) + expect(resolveFigmaLinks({ label: 'no url' })).toEqual([]) + expect(resolveFigmaLinks({ url: 42 })).toEqual([]) + expect(resolveFigmaLinks([])).toEqual([]) + }) +}) diff --git a/.storybook/figma/figma.ts b/.storybook/figma/figma.ts new file mode 100644 index 00000000..f920d6bb --- /dev/null +++ b/.storybook/figma/figma.ts @@ -0,0 +1,33 @@ +type ResolvedFigmaLink = { + label: string + url: string +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function resolveEntry(entry: unknown): ResolvedFigmaLink[] { + if (typeof entry === 'string') { + return entry.length > 0 ? [{ label: entry, url: entry }] : [] + } + + if (isRecord(entry) && typeof entry.url === 'string' && entry.url.length > 0) { + const label = + typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.url + return [{ label, url: entry.url }] + } + + return [] +} + +function resolveFigmaLinks(param: unknown): ResolvedFigmaLink[] { + if (Array.isArray(param)) { + return param.flatMap(resolveEntry) + } + + return resolveEntry(param) +} + +export { resolveFigmaLinks } +export type { ResolvedFigmaLink } diff --git a/.storybook/figma/index.ts b/.storybook/figma/index.ts new file mode 100644 index 00000000..d9a9a8df --- /dev/null +++ b/.storybook/figma/index.ts @@ -0,0 +1,2 @@ +export { FigmaTool } from './figma-tool' +export { ADDON_ID, TOOL_ID } from './constants' diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 2011d3dd..d569db37 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,11 +1,22 @@ import { addons, types } from 'storybook/manager-api' +import '../src/styles/design-tokens.css' import { ADDON_ID, BadgesTool, TOOL_ID } from './badges' +import { ADDON_ID as FIGMA_ADDON_ID, FigmaTool, TOOL_ID as FIGMA_TOOL_ID } from './figma' import theme from './theme' addons.setConfig({ theme, }) +addons.register(FIGMA_ADDON_ID, () => { + addons.add(FIGMA_TOOL_ID, { + title: 'Figma', + type: types.TOOL, + match: ({ viewMode }) => viewMode === 'story' || viewMode === 'docs', + render: FigmaTool, + }) +}) + addons.register(ADDON_ID, () => { addons.add(TOOL_ID, { title: 'Badges', From 3f4653fb082b02c083834384a9b49a5377730c5c Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Wed, 17 Jun 2026 19:45:51 -0700 Subject: [PATCH 02/11] chore: add Figma parameter to plop story template Co-Authored-By: Claude --- .plop/templates/component/component.stories.tsx.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/.plop/templates/component/component.stories.tsx.hbs b/.plop/templates/component/component.stories.tsx.hbs index 5a03aade..83715630 100644 --- a/.plop/templates/component/component.stories.tsx.hbs +++ b/.plop/templates/component/component.stories.tsx.hbs @@ -8,6 +8,7 @@ const meta = { component: {{pascalCase name}}, parameters: { badges: ['accessible'], + // figma: { label: 'Figma file › Page › Section › Component', url: 'https://www.figma.com/design/…?node-id=…' }, }, } satisfies Meta From 11910f978fad8ed6e8d9597960d00248041785b8 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Wed, 17 Jun 2026 20:10:29 -0700 Subject: [PATCH 03/11] docs: link Figma designs from component stories Co-Authored-By: Claude --- src/avatar/avatar.stories.tsx | 4 ++++ src/badge/badge.stories.jsx | 4 ++++ src/banner/banner.stories.jsx | 4 ++++ src/button/button.stories.jsx | 4 ++++ src/button/icon-button.stories.jsx | 4 ++++ src/heading/heading.stories.tsx | 4 ++++ src/loading/loading.stories.jsx | 4 ++++ src/menu/menu.stories.jsx | 4 ++++ src/modal/modal-examples.stories.tsx | 4 ++++ src/notice/notice.stories.jsx | 4 ++++ src/password-field/password-field.stories.jsx | 4 ++++ src/prose/prose.stories.tsx | 4 ++++ src/select-field/select-field.stories.jsx | 4 ++++ src/switch-field/switch-field.stories.jsx | 4 ++++ src/tabs/tabs.stories.jsx | 4 ++++ src/text-area/text-area.stories.jsx | 4 ++++ src/text-field/text-field.stories.jsx | 4 ++++ src/text-link/text-link.stories.jsx | 4 ++++ src/text/text.stories.tsx | 4 ++++ src/toast/toast.stories.tsx | 4 ++++ src/tooltip/tooltip.stories.tsx | 4 ++++ stories/components/KeyboardShortcut.stories.tsx | 4 ++++ stories/components/ProgressBar.stories.tsx | 4 ++++ 23 files changed, 92 insertions(+) diff --git a/src/avatar/avatar.stories.tsx b/src/avatar/avatar.stories.tsx index 534ba12a..31fa6858 100644 --- a/src/avatar/avatar.stories.tsx +++ b/src/avatar/avatar.stories.tsx @@ -187,6 +187,10 @@ const meta = { component: Avatar, parameters: { badges: ['accessible'], + figma: { + label: 'Global › Avatar', + url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=6957-36532', + }, }, } satisfies Meta diff --git a/src/badge/badge.stories.jsx b/src/badge/badge.stories.jsx index 02bbfff4..a7a74f12 100644 --- a/src/badge/badge.stories.jsx +++ b/src/badge/badge.stories.jsx @@ -101,6 +101,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Badge', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=9038-280657', + }, }, } diff --git a/src/banner/banner.stories.jsx b/src/banner/banner.stories.jsx index 988c8dac..7f6818f5 100644 --- a/src/banner/banner.stories.jsx +++ b/src/banner/banner.stories.jsx @@ -388,6 +388,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Banner › Banner', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=15487-102766', + }, }, } diff --git a/src/button/button.stories.jsx b/src/button/button.stories.jsx index 0ec3fd9a..960de753 100644 --- a/src/button/button.stories.jsx +++ b/src/button/button.stories.jsx @@ -188,6 +188,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Buttons › Button', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=507-10', + }, }, } diff --git a/src/button/icon-button.stories.jsx b/src/button/icon-button.stories.jsx index b5306c38..5a944ace 100644 --- a/src/button/icon-button.stories.jsx +++ b/src/button/icon-button.stories.jsx @@ -106,6 +106,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Buttons › Button (Variant=Icon)', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=507-10', + }, }, } diff --git a/src/heading/heading.stories.tsx b/src/heading/heading.stories.tsx index 58363448..099bad3c 100644 --- a/src/heading/heading.stories.tsx +++ b/src/heading/heading.stories.tsx @@ -10,6 +10,10 @@ export default { component: Heading, parameters: { badges: ['accessible'], + figma: { + label: 'Global › Text Styles › SF *FOR WEB* › Header 1', + url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=2524-3589', + }, }, } diff --git a/src/loading/loading.stories.jsx b/src/loading/loading.stories.jsx index 73b10cb5..9eb6f661 100644 --- a/src/loading/loading.stories.jsx +++ b/src/loading/loading.stories.jsx @@ -21,6 +21,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Progress › Progress circular', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20195-258612', + }, }, } diff --git a/src/menu/menu.stories.jsx b/src/menu/menu.stories.jsx index 4c9a655d..572bd025 100644 --- a/src/menu/menu.stories.jsx +++ b/src/menu/menu.stories.jsx @@ -64,6 +64,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Context Menus › Context Menu', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=2057-40323', + }, }, } diff --git a/src/modal/modal-examples.stories.tsx b/src/modal/modal-examples.stories.tsx index f90ab2bb..c5fb7214 100644 --- a/src/modal/modal-examples.stories.tsx +++ b/src/modal/modal-examples.stories.tsx @@ -37,6 +37,10 @@ export default { parameters: { viewMode: 'story', badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Settings › Modal / Header', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=7443-241153', + }, layout: 'fullscreen', }, decorators: [ diff --git a/src/notice/notice.stories.jsx b/src/notice/notice.stories.jsx index 6e4751be..5ecc04b3 100644 --- a/src/notice/notice.stories.jsx +++ b/src/notice/notice.stories.jsx @@ -33,6 +33,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Banner › Banner', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=15487-102766', + }, }, } diff --git a/src/password-field/password-field.stories.jsx b/src/password-field/password-field.stories.jsx index b586c9cb..c8609c33 100644 --- a/src/password-field/password-field.stories.jsx +++ b/src/password-field/password-field.stories.jsx @@ -46,6 +46,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: '24Q3 Foundation › Password', + url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=2295-76983', + }, }, } diff --git a/src/prose/prose.stories.tsx b/src/prose/prose.stories.tsx index 89e63011..e351b3a4 100644 --- a/src/prose/prose.stories.tsx +++ b/src/prose/prose.stories.tsx @@ -12,6 +12,10 @@ export default { component: Prose, parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Markdown › MarkdownElement', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=7391-224517', + }, }, } diff --git a/src/select-field/select-field.stories.jsx b/src/select-field/select-field.stories.jsx index 33cff7e5..a757e6ab 100644 --- a/src/select-field/select-field.stories.jsx +++ b/src/select-field/select-field.stories.jsx @@ -45,6 +45,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Context Menus › Dropdown', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20744-678051', + }, }, } diff --git a/src/switch-field/switch-field.stories.jsx b/src/switch-field/switch-field.stories.jsx index 1fa64501..d62478ed 100644 --- a/src/switch-field/switch-field.stories.jsx +++ b/src/switch-field/switch-field.stories.jsx @@ -15,6 +15,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Forms › Input Field (Input type=Binary)', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=533-60', + }, }, } diff --git a/src/tabs/tabs.stories.jsx b/src/tabs/tabs.stories.jsx index 38451941..1cb61adb 100644 --- a/src/tabs/tabs.stories.jsx +++ b/src/tabs/tabs.stories.jsx @@ -51,6 +51,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Tabs › Tab Group', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=8371-275288', + }, }, } diff --git a/src/text-area/text-area.stories.jsx b/src/text-area/text-area.stories.jsx index e64dccb0..72f68d6a 100644 --- a/src/text-area/text-area.stories.jsx +++ b/src/text-area/text-area.stories.jsx @@ -87,6 +87,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: '24Q3 Foundation › Multi-line text field', + url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1987-546972', + }, }, } diff --git a/src/text-field/text-field.stories.jsx b/src/text-field/text-field.stories.jsx index af5f747f..633b6c19 100644 --- a/src/text-field/text-field.stories.jsx +++ b/src/text-field/text-field.stories.jsx @@ -190,6 +190,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: '24Q3 Foundation › Text field', + url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1987-64913', + }, }, } diff --git a/src/text-link/text-link.stories.jsx b/src/text-link/text-link.stories.jsx index 9d5b2131..cd7e192d 100644 --- a/src/text-link/text-link.stories.jsx +++ b/src/text-link/text-link.stories.jsx @@ -10,6 +10,10 @@ export default { parameters: { badges: ['accessible'], + figma: { + label: '24Q3 Foundation › Link styling', + url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1530-49073', + }, }, } diff --git a/src/text/text.stories.tsx b/src/text/text.stories.tsx index be318bc6..ec75dce6 100644 --- a/src/text/text.stories.tsx +++ b/src/text/text.stories.tsx @@ -10,6 +10,10 @@ export default { component: Text, parameters: { badges: ['accessible'], + figma: { + label: 'Global › Text Styles › SF *FOR WEB* › Body 1', + url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=2524-3594', + }, }, } diff --git a/src/toast/toast.stories.tsx b/src/toast/toast.stories.tsx index 7116c91c..15f9ba92 100644 --- a/src/toast/toast.stories.tsx +++ b/src/toast/toast.stories.tsx @@ -26,6 +26,10 @@ export default { title: '💬 Feedback/Toast', parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Toast › Toast', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=6654-220700', + }, layout: 'fullscreen', }, decorators: [ diff --git a/src/tooltip/tooltip.stories.tsx b/src/tooltip/tooltip.stories.tsx index 12637afc..0e7e715e 100644 --- a/src/tooltip/tooltip.stories.tsx +++ b/src/tooltip/tooltip.stories.tsx @@ -20,6 +20,10 @@ export default { title: '🪟 Overlays/Tooltip', parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Tooltips', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=14597-177215', + }, }, } diff --git a/stories/components/KeyboardShortcut.stories.tsx b/stories/components/KeyboardShortcut.stories.tsx index 0367983b..f083b95a 100644 --- a/stories/components/KeyboardShortcut.stories.tsx +++ b/stories/components/KeyboardShortcut.stories.tsx @@ -10,6 +10,10 @@ export default { title: '📊 Data display/KeyboardShortcut', parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Keyboard Shortcuts › Shortcut', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=9140-282739', + }, }, } diff --git a/stories/components/ProgressBar.stories.tsx b/stories/components/ProgressBar.stories.tsx index 427a90af..f74d786f 100644 --- a/stories/components/ProgressBar.stories.tsx +++ b/stories/components/ProgressBar.stories.tsx @@ -11,6 +11,10 @@ export default { component: ProgressBar, parameters: { badges: ['accessible'], + figma: { + label: 'Web › Components / Todoist › Progress › Progress bar', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20195-258607', + }, }, } From 9f4372aca97955ef2e123dffcc688930673ffadb Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Wed, 17 Jun 2026 20:36:21 -0700 Subject: [PATCH 04/11] refactor(storybook): render a11y badges with Reactist Badge Co-Authored-By: Claude --- .storybook/badges/BadgesTool.tsx | 57 ------------------------------- .storybook/badges/badges-tool.tsx | 34 ++++++++++++++++++ .storybook/badges/badges.test.ts | 30 ++++++++-------- .storybook/badges/badges.ts | 31 ++++++++--------- .storybook/badges/index.ts | 2 +- .storybook/preview.ts | 43 ++++++----------------- 6 files changed, 73 insertions(+), 124 deletions(-) delete mode 100644 .storybook/badges/BadgesTool.tsx create mode 100644 .storybook/badges/badges-tool.tsx diff --git a/.storybook/badges/BadgesTool.tsx b/.storybook/badges/BadgesTool.tsx deleted file mode 100644 index f9154e57..00000000 --- a/.storybook/badges/BadgesTool.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { memo, useMemo } from 'react' -import type { CSSProperties } from 'react' -import { useParameter } from 'storybook/manager-api' - -import { resolveBadges } from './badges' -import { BADGES_CONFIG_PARAMETER, BADGES_PARAMETER, TOOL_ID } from './constants' - -const containerStyle: CSSProperties = { - alignItems: 'center', - display: 'inline-flex', - gap: '4px', - marginInline: '6px', -} - -const badgeStyle: CSSProperties = { - alignItems: 'center', - border: '1px solid currentColor', - borderRadius: '3px', - boxSizing: 'border-box', - display: 'inline-flex', - fontWeight: 600, - minHeight: '20px', - padding: '2px 6px', - whiteSpace: 'nowrap', -} - -const emptyBadges: unknown[] = [] -const emptyBadgesConfig: Record = {} - -export const BadgesTool = memo(function BadgesTool() { - const badgesParameter = useParameter(BADGES_PARAMETER, emptyBadges) - const badgesConfigParameter = useParameter(BADGES_CONFIG_PARAMETER, emptyBadgesConfig) - - const badges = useMemo( - () => resolveBadges(badgesParameter, badgesConfigParameter), - [badgesParameter, badgesConfigParameter], - ) - - if (badges.length === 0) { - return null - } - - return ( -
title).join(', ')} - > - {badges.map(({ id, title, styles }, index) => ( - - {title} - - ))} -
- ) -}) diff --git a/.storybook/badges/badges-tool.tsx b/.storybook/badges/badges-tool.tsx new file mode 100644 index 00000000..0e6e525f --- /dev/null +++ b/.storybook/badges/badges-tool.tsx @@ -0,0 +1,34 @@ +import * as React from 'react' + +import { useParameter } from 'storybook/manager-api' + +import { Badge } from '../../src/badge/badge' +import { Inline } from '../../src/inline' + +import { resolveBadges } from './badges' +import { BADGES_CONFIG_PARAMETER, BADGES_PARAMETER } from './constants' + +const emptyBadges: unknown[] = [] +const emptyBadgesConfig: Record = {} + +export const BadgesTool = React.memo(function BadgesTool() { + const badgesParameter = useParameter(BADGES_PARAMETER, emptyBadges) + const badgesConfigParameter = useParameter(BADGES_CONFIG_PARAMETER, emptyBadgesConfig) + + const badges = React.useMemo( + () => resolveBadges(badgesParameter, badgesConfigParameter), + [badgesParameter, badgesConfigParameter], + ) + + if (badges.length === 0) { + return null + } + + return ( + + {badges.map(({ id, title, tone }) => ( + + ))} + + ) +}) diff --git a/.storybook/badges/badges.test.ts b/.storybook/badges/badges.test.ts index 277e0855..768643bf 100644 --- a/.storybook/badges/badges.test.ts +++ b/.storybook/badges/badges.test.ts @@ -6,42 +6,46 @@ describe('resolveBadges', () => { resolveBadges(['accessible', 'deprecated'], { accessible: { title: 'Accessible', - styles: { color: 'green' }, + tone: 'positive', }, deprecated: { title: 'Deprecated', - styles: { color: 'red' }, + tone: 'attention', }, }), ).toEqual([ { id: 'accessible', title: 'Accessible', - styles: { color: 'green' }, + tone: 'positive', }, { id: 'deprecated', title: 'Deprecated', - styles: { color: 'red' }, + tone: 'attention', }, ]) }) it('filters unknown badges and invalid badge entries', () => { expect( - resolveBadges(['accessible', 'missing', 42, 'untitled'], { + resolveBadges(['accessible', 'missing', 42, 'untitled', 'untoned'], { accessible: { title: 'Accessible', + tone: 'positive', }, untitled: { - styles: { color: 'orange' }, + tone: 'positive', + }, + untoned: { + title: 'Untoned', }, }), ).toEqual([ { id: 'accessible', title: 'Accessible', - styles: undefined, + tone: 'positive', }, ]) }) @@ -53,20 +57,14 @@ describe('resolveBadges', () => { expect(resolveBadges(['accessible'], [])).toEqual([]) }) - it('ignores non-object styles', () => { + it('rejects an unknown tone', () => { expect( resolveBadges(['accessible'], { accessible: { title: 'Accessible', - styles: 'color: green', + tone: 'rainbow', }, }), - ).toEqual([ - { - id: 'accessible', - title: 'Accessible', - styles: undefined, - }, - ]) + ).toEqual([]) }) }) diff --git a/.storybook/badges/badges.ts b/.storybook/badges/badges.ts index 2432a75c..89565b67 100644 --- a/.storybook/badges/badges.ts +++ b/.storybook/badges/badges.ts @@ -1,28 +1,21 @@ -import type { CSSProperties } from 'react' +import type { BadgeProps } from '../../src/badge/badge' -type UnknownRecord = Record - -type BadgeConfig = { - title?: unknown - styles?: unknown -} +type BadgeTone = BadgeProps['tone'] type ResolvedBadge = { id: string title: string - styles: CSSProperties | undefined + tone: BadgeTone } -function isRecord(value: unknown): value is UnknownRecord { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} +const VALID_TONES: readonly BadgeTone[] = ['info', 'positive', 'promote', 'attention', 'warning'] -function isBadgeConfig(value: unknown): value is BadgeConfig { - return isRecord(value) +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) } -function resolveStyles(styles: unknown): CSSProperties | undefined { - return isRecord(styles) ? (styles as CSSProperties) : undefined +function isBadgeTone(value: unknown): value is BadgeTone { + return typeof value === 'string' && (VALID_TONES as readonly string[]).includes(value) } function resolveBadges(badges: unknown, badgesConfig: unknown): ResolvedBadge[] { @@ -37,7 +30,11 @@ function resolveBadges(badges: unknown, badgesConfig: unknown): ResolvedBadge[] const badgeConfig = badgesConfig[badge] - if (!isBadgeConfig(badgeConfig) || typeof badgeConfig.title !== 'string') { + if (!isRecord(badgeConfig) || typeof badgeConfig.title !== 'string') { + return [] + } + + if (!isBadgeTone(badgeConfig.tone)) { return [] } @@ -45,7 +42,7 @@ function resolveBadges(badges: unknown, badgesConfig: unknown): ResolvedBadge[] { id: badge, title: badgeConfig.title, - styles: resolveStyles(badgeConfig.styles), + tone: badgeConfig.tone, }, ] }) diff --git a/.storybook/badges/index.ts b/.storybook/badges/index.ts index d77c0a38..62979061 100644 --- a/.storybook/badges/index.ts +++ b/.storybook/badges/index.ts @@ -1,2 +1,2 @@ -export { BadgesTool } from './BadgesTool' +export { BadgesTool } from './badges-tool' export { ADDON_ID, TOOL_ID } from './constants' diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 1b1e662c..9714e256 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -4,11 +4,6 @@ import BaseDecorator from './BaseDecorator' import '../src/styles/design-tokens.css' import '../stories/components/styles/story.css' -const badgeFontStyles = { - fontSize: '12px', - lineHeight: '14px', -} - const preview: Preview = { decorators: [BaseDecorator], parameters: { @@ -42,42 +37,24 @@ const preview: Preview = { chromatic: { disableSnapshot: true, }, + // The \uFE0E after each symbol forces text (not emoji) presentation, so the glyph + // inherits the badge tint color instead of falling back to the black emoji font. badgesConfig: { accessible: { - title: '✔ Accessible (WCAG 2.0 AA)', - styles: { - backgroundColor: 'rgba(5, 133, 39, 0.1)', - borderColor: 'rgb(5, 133, 39)', - color: 'rgb(5, 133, 39)', - ...badgeFontStyles, - }, + title: '✔\uFE0E Accessible (WCAG 2.0 AA)', + tone: 'positive', }, partiallyAccessible: { - title: '⚠ Partially Accessible', - styles: { - backgroundColor: 'rgba(235, 141, 19, 0.1)', - borderColor: 'rgb(235, 141, 19)', - color: 'rgb(235, 141, 19)', - ...badgeFontStyles, - }, + title: '⚠\uFE0E Partially Accessible', + tone: 'warning', }, notAccessible: { - title: '✖ Not accessible', - styles: { - backgroundColor: 'rgba(209, 69, 59, 0.1)', - borderColor: 'rgb(209, 69, 59)', - color: 'rgb(209, 69, 59)', - ...badgeFontStyles, - }, + title: '✖\uFE0E Not accessible', + tone: 'attention', }, deprecated: { - title: '✖ Deprecated', - styles: { - backgroundColor: 'rgba(209, 69, 59, 0.1)', - borderColor: 'rgb(209, 69, 59)', - color: 'rgb(209, 69, 59)', - ...badgeFontStyles, - }, + title: '✖\uFE0E Deprecated', + tone: 'attention', }, }, }, From 2f4cbcd0b95ca258dfc06ff7ac68b6ad37f2ebff Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:34:11 -0700 Subject: [PATCH 05/11] refactor(storybook): rename figma label to path --- .../component/component.stories.tsx.hbs | 2 +- .storybook/figma/figma-tool.tsx | 4 ++-- .storybook/figma/figma.test.ts | 24 +++++++++---------- .storybook/figma/figma.ts | 10 ++++---- src/avatar/avatar.stories.tsx | 2 +- src/badge/badge.stories.jsx | 2 +- src/banner/banner.stories.jsx | 2 +- src/button/button.stories.jsx | 2 +- src/button/icon-button.stories.jsx | 2 +- src/heading/heading.stories.tsx | 2 +- src/loading/loading.stories.jsx | 2 +- src/menu/menu.stories.jsx | 2 +- src/modal/modal-examples.stories.tsx | 2 +- src/notice/notice.stories.jsx | 2 +- src/password-field/password-field.stories.jsx | 2 +- src/prose/prose.stories.tsx | 2 +- src/select-field/select-field.stories.jsx | 2 +- src/switch-field/switch-field.stories.jsx | 2 +- src/tabs/tabs.stories.jsx | 2 +- src/text-area/text-area.stories.jsx | 2 +- src/text-field/text-field.stories.jsx | 2 +- src/text-link/text-link.stories.jsx | 2 +- src/text/text.stories.tsx | 2 +- src/toast/toast.stories.tsx | 2 +- src/tooltip/tooltip.stories.tsx | 2 +- .../components/KeyboardShortcut.stories.tsx | 2 +- stories/components/ProgressBar.stories.tsx | 2 +- 27 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.plop/templates/component/component.stories.tsx.hbs b/.plop/templates/component/component.stories.tsx.hbs index 83715630..e5fde728 100644 --- a/.plop/templates/component/component.stories.tsx.hbs +++ b/.plop/templates/component/component.stories.tsx.hbs @@ -8,7 +8,7 @@ const meta = { component: {{pascalCase name}}, parameters: { badges: ['accessible'], - // figma: { label: 'Figma file › Page › Section › Component', url: 'https://www.figma.com/design/…?node-id=…' }, + // figma: { path: 'Figma file › Page › Section › Component', url: 'https://www.figma.com/design/…?node-id=…' }, }, } satisfies Meta diff --git a/.storybook/figma/figma-tool.tsx b/.storybook/figma/figma-tool.tsx index 27e57417..24657cf0 100644 --- a/.storybook/figma/figma-tool.tsx +++ b/.storybook/figma/figma-tool.tsx @@ -24,8 +24,8 @@ export const FigmaTool = React.memo(function FigmaTool() { key={link.url} variant="quaternary" icon={} - aria-label={`View in Figma: ${link.label}`} - tooltip={link.label} + aria-label={`View in Figma: ${link.path}`} + tooltip={link.path} render={
} /> )) diff --git a/.storybook/figma/figma.test.ts b/.storybook/figma/figma.test.ts index 38d15f99..e288b901 100644 --- a/.storybook/figma/figma.test.ts +++ b/.storybook/figma/figma.test.ts @@ -1,32 +1,32 @@ import { resolveFigmaLinks } from './figma' describe('resolveFigmaLinks', () => { - it('resolves a bare string as both url and label', () => { + it('resolves a bare string as both url and path', () => { expect(resolveFigmaLinks('https://figma.com/design/abc?node-id=1-2')).toEqual([ { - label: 'https://figma.com/design/abc?node-id=1-2', + path: 'https://figma.com/design/abc?node-id=1-2', url: 'https://figma.com/design/abc?node-id=1-2', }, ]) }) - it('resolves an object with label and url', () => { + it('resolves an object with path and url', () => { expect( resolveFigmaLinks({ - label: 'Web › Buttons › Button', + path: 'Web › Buttons › Button', url: 'https://figma.com/design/abc?node-id=1-2', }), ).toEqual([ { - label: 'Web › Buttons › Button', + path: 'Web › Buttons › Button', url: 'https://figma.com/design/abc?node-id=1-2', }, ]) }) - it('falls back to the url as label when label is missing', () => { + it('falls back to the url as path when path is missing', () => { expect(resolveFigmaLinks({ url: 'https://figma.com/design/abc' })).toEqual([ - { label: 'https://figma.com/design/abc', url: 'https://figma.com/design/abc' }, + { path: 'https://figma.com/design/abc', url: 'https://figma.com/design/abc' }, ]) }) @@ -34,15 +34,15 @@ describe('resolveFigmaLinks', () => { expect( resolveFigmaLinks([ 'https://figma.com/a', - { label: 'B', url: 'https://figma.com/b' }, - { label: 'C' }, + { path: 'B', url: 'https://figma.com/b' }, + { path: 'C' }, { url: 42 }, '', null, ]), ).toEqual([ - { label: 'https://figma.com/a', url: 'https://figma.com/a' }, - { label: 'B', url: 'https://figma.com/b' }, + { path: 'https://figma.com/a', url: 'https://figma.com/a' }, + { path: 'B', url: 'https://figma.com/b' }, ]) }) @@ -51,7 +51,7 @@ describe('resolveFigmaLinks', () => { expect(resolveFigmaLinks(null)).toEqual([]) expect(resolveFigmaLinks('')).toEqual([]) expect(resolveFigmaLinks({})).toEqual([]) - expect(resolveFigmaLinks({ label: 'no url' })).toEqual([]) + expect(resolveFigmaLinks({ path: 'no url' })).toEqual([]) expect(resolveFigmaLinks({ url: 42 })).toEqual([]) expect(resolveFigmaLinks([])).toEqual([]) }) diff --git a/.storybook/figma/figma.ts b/.storybook/figma/figma.ts index f920d6bb..18913cda 100644 --- a/.storybook/figma/figma.ts +++ b/.storybook/figma/figma.ts @@ -1,5 +1,5 @@ type ResolvedFigmaLink = { - label: string + path: string url: string } @@ -9,13 +9,13 @@ function isRecord(value: unknown): value is Record { function resolveEntry(entry: unknown): ResolvedFigmaLink[] { if (typeof entry === 'string') { - return entry.length > 0 ? [{ label: entry, url: entry }] : [] + return entry.length > 0 ? [{ path: entry, url: entry }] : [] } if (isRecord(entry) && typeof entry.url === 'string' && entry.url.length > 0) { - const label = - typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.url - return [{ label, url: entry.url }] + const path = + typeof entry.path === 'string' && entry.path.length > 0 ? entry.path : entry.url + return [{ path, url: entry.url }] } return [] diff --git a/src/avatar/avatar.stories.tsx b/src/avatar/avatar.stories.tsx index 31fa6858..cd88227a 100644 --- a/src/avatar/avatar.stories.tsx +++ b/src/avatar/avatar.stories.tsx @@ -188,7 +188,7 @@ const meta = { parameters: { badges: ['accessible'], figma: { - label: 'Global › Avatar', + path: 'Global › Avatar', url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=6957-36532', }, }, diff --git a/src/badge/badge.stories.jsx b/src/badge/badge.stories.jsx index a7a74f12..6e01cbf6 100644 --- a/src/badge/badge.stories.jsx +++ b/src/badge/badge.stories.jsx @@ -102,7 +102,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Badge', + path: 'Web › Components / Todoist › Badge', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=9038-280657', }, }, diff --git a/src/banner/banner.stories.jsx b/src/banner/banner.stories.jsx index 7f6818f5..c8d406da 100644 --- a/src/banner/banner.stories.jsx +++ b/src/banner/banner.stories.jsx @@ -389,7 +389,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Banner › Banner', + path: 'Web › Components / Todoist › Banner › Banner', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=15487-102766', }, }, diff --git a/src/button/button.stories.jsx b/src/button/button.stories.jsx index 960de753..7fe436e1 100644 --- a/src/button/button.stories.jsx +++ b/src/button/button.stories.jsx @@ -189,7 +189,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Buttons › Button', + path: 'Web › Components / Todoist › Buttons › Button', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=507-10', }, }, diff --git a/src/button/icon-button.stories.jsx b/src/button/icon-button.stories.jsx index 5a944ace..1cb67f95 100644 --- a/src/button/icon-button.stories.jsx +++ b/src/button/icon-button.stories.jsx @@ -107,7 +107,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Buttons › Button (Variant=Icon)', + path: 'Web › Components / Todoist › Buttons › Button (Variant=Icon)', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=507-10', }, }, diff --git a/src/heading/heading.stories.tsx b/src/heading/heading.stories.tsx index 099bad3c..2a4238a9 100644 --- a/src/heading/heading.stories.tsx +++ b/src/heading/heading.stories.tsx @@ -11,7 +11,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Global › Text Styles › SF *FOR WEB* › Header 1', + path: 'Global › Text Styles › SF *FOR WEB* › Header 1', url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=2524-3589', }, }, diff --git a/src/loading/loading.stories.jsx b/src/loading/loading.stories.jsx index 9eb6f661..326ed38e 100644 --- a/src/loading/loading.stories.jsx +++ b/src/loading/loading.stories.jsx @@ -22,7 +22,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Progress › Progress circular', + path: 'Web › Components / Todoist › Progress › Progress circular', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20195-258612', }, }, diff --git a/src/menu/menu.stories.jsx b/src/menu/menu.stories.jsx index 572bd025..9b549abd 100644 --- a/src/menu/menu.stories.jsx +++ b/src/menu/menu.stories.jsx @@ -65,7 +65,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Context Menus › Context Menu', + path: 'Web › Components / Todoist › Context Menus › Context Menu', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=2057-40323', }, }, diff --git a/src/modal/modal-examples.stories.tsx b/src/modal/modal-examples.stories.tsx index c5fb7214..b30751f0 100644 --- a/src/modal/modal-examples.stories.tsx +++ b/src/modal/modal-examples.stories.tsx @@ -38,7 +38,7 @@ export default { viewMode: 'story', badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Settings › Modal / Header', + path: 'Web › Components / Todoist › Settings › Modal / Header', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=7443-241153', }, layout: 'fullscreen', diff --git a/src/notice/notice.stories.jsx b/src/notice/notice.stories.jsx index 5ecc04b3..2699581a 100644 --- a/src/notice/notice.stories.jsx +++ b/src/notice/notice.stories.jsx @@ -34,7 +34,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Banner › Banner', + path: 'Web › Components / Todoist › Banner › Banner', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=15487-102766', }, }, diff --git a/src/password-field/password-field.stories.jsx b/src/password-field/password-field.stories.jsx index c8609c33..6dba0af2 100644 --- a/src/password-field/password-field.stories.jsx +++ b/src/password-field/password-field.stories.jsx @@ -47,7 +47,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: '24Q3 Foundation › Password', + path: '24Q3 Foundation › Password', url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=2295-76983', }, }, diff --git a/src/prose/prose.stories.tsx b/src/prose/prose.stories.tsx index e351b3a4..81181990 100644 --- a/src/prose/prose.stories.tsx +++ b/src/prose/prose.stories.tsx @@ -13,7 +13,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Markdown › MarkdownElement', + path: 'Web › Components / Todoist › Markdown › MarkdownElement', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=7391-224517', }, }, diff --git a/src/select-field/select-field.stories.jsx b/src/select-field/select-field.stories.jsx index a757e6ab..c0545f4c 100644 --- a/src/select-field/select-field.stories.jsx +++ b/src/select-field/select-field.stories.jsx @@ -46,7 +46,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Context Menus › Dropdown', + path: 'Web › Components / Todoist › Context Menus › Dropdown', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20744-678051', }, }, diff --git a/src/switch-field/switch-field.stories.jsx b/src/switch-field/switch-field.stories.jsx index d62478ed..1fbcb6bf 100644 --- a/src/switch-field/switch-field.stories.jsx +++ b/src/switch-field/switch-field.stories.jsx @@ -16,7 +16,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Forms › Input Field (Input type=Binary)', + path: 'Web › Components / Todoist › Forms › Input Field (Input type=Binary)', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=533-60', }, }, diff --git a/src/tabs/tabs.stories.jsx b/src/tabs/tabs.stories.jsx index 1cb61adb..414ba263 100644 --- a/src/tabs/tabs.stories.jsx +++ b/src/tabs/tabs.stories.jsx @@ -52,7 +52,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Tabs › Tab Group', + path: 'Web › Components / Todoist › Tabs › Tab Group', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=8371-275288', }, }, diff --git a/src/text-area/text-area.stories.jsx b/src/text-area/text-area.stories.jsx index 72f68d6a..25886ff8 100644 --- a/src/text-area/text-area.stories.jsx +++ b/src/text-area/text-area.stories.jsx @@ -88,7 +88,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: '24Q3 Foundation › Multi-line text field', + path: '24Q3 Foundation › Multi-line text field', url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1987-546972', }, }, diff --git a/src/text-field/text-field.stories.jsx b/src/text-field/text-field.stories.jsx index 633b6c19..ee08a5c6 100644 --- a/src/text-field/text-field.stories.jsx +++ b/src/text-field/text-field.stories.jsx @@ -191,7 +191,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: '24Q3 Foundation › Text field', + path: '24Q3 Foundation › Text field', url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1987-64913', }, }, diff --git a/src/text-link/text-link.stories.jsx b/src/text-link/text-link.stories.jsx index cd7e192d..eb0ab333 100644 --- a/src/text-link/text-link.stories.jsx +++ b/src/text-link/text-link.stories.jsx @@ -11,7 +11,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: '24Q3 Foundation › Link styling', + path: '24Q3 Foundation › Link styling', url: 'https://www.figma.com/design/5gTX7MuUxhCIvL6WK87JVA/24Q3-Foundation?node-id=1530-49073', }, }, diff --git a/src/text/text.stories.tsx b/src/text/text.stories.tsx index ec75dce6..a4c51645 100644 --- a/src/text/text.stories.tsx +++ b/src/text/text.stories.tsx @@ -11,7 +11,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Global › Text Styles › SF *FOR WEB* › Body 1', + path: 'Global › Text Styles › SF *FOR WEB* › Body 1', url: 'https://www.figma.com/design/xo9yAsH8PQUpi0eTJh9pmR/Product-Library---Global?node-id=2524-3594', }, }, diff --git a/src/toast/toast.stories.tsx b/src/toast/toast.stories.tsx index 15f9ba92..fec548ee 100644 --- a/src/toast/toast.stories.tsx +++ b/src/toast/toast.stories.tsx @@ -27,7 +27,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Toast › Toast', + path: 'Web › Components / Todoist › Toast › Toast', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=6654-220700', }, layout: 'fullscreen', diff --git a/src/tooltip/tooltip.stories.tsx b/src/tooltip/tooltip.stories.tsx index 0e7e715e..17f70e50 100644 --- a/src/tooltip/tooltip.stories.tsx +++ b/src/tooltip/tooltip.stories.tsx @@ -21,7 +21,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Tooltips', + path: 'Web › Components / Todoist › Tooltips', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=14597-177215', }, }, diff --git a/stories/components/KeyboardShortcut.stories.tsx b/stories/components/KeyboardShortcut.stories.tsx index f083b95a..cad8236e 100644 --- a/stories/components/KeyboardShortcut.stories.tsx +++ b/stories/components/KeyboardShortcut.stories.tsx @@ -11,7 +11,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Keyboard Shortcuts › Shortcut', + path: 'Web › Components / Todoist › Keyboard Shortcuts › Shortcut', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=9140-282739', }, }, diff --git a/stories/components/ProgressBar.stories.tsx b/stories/components/ProgressBar.stories.tsx index f74d786f..04d83005 100644 --- a/stories/components/ProgressBar.stories.tsx +++ b/stories/components/ProgressBar.stories.tsx @@ -12,7 +12,7 @@ export default { parameters: { badges: ['accessible'], figma: { - label: 'Web › Components / Todoist › Progress › Progress bar', + path: 'Web › Components / Todoist › Progress › Progress bar', url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=20195-258607', }, }, From 69595fcc3b991c62d1d5d14346fcfc926284f65f Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:34:59 -0700 Subject: [PATCH 06/11] refactor(storybook): drop the figma link string shorthand --- .storybook/figma/figma.test.ts | 17 +++-------------- .storybook/figma/figma.ts | 4 ---- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.storybook/figma/figma.test.ts b/.storybook/figma/figma.test.ts index e288b901..d0323894 100644 --- a/.storybook/figma/figma.test.ts +++ b/.storybook/figma/figma.test.ts @@ -1,15 +1,6 @@ import { resolveFigmaLinks } from './figma' describe('resolveFigmaLinks', () => { - it('resolves a bare string as both url and path', () => { - expect(resolveFigmaLinks('https://figma.com/design/abc?node-id=1-2')).toEqual([ - { - path: 'https://figma.com/design/abc?node-id=1-2', - url: 'https://figma.com/design/abc?node-id=1-2', - }, - ]) - }) - it('resolves an object with path and url', () => { expect( resolveFigmaLinks({ @@ -33,22 +24,20 @@ describe('resolveFigmaLinks', () => { it('resolves an array, dropping malformed entries', () => { expect( resolveFigmaLinks([ - 'https://figma.com/a', { path: 'B', url: 'https://figma.com/b' }, { path: 'C' }, { url: 42 }, '', null, ]), - ).toEqual([ - { path: 'https://figma.com/a', url: 'https://figma.com/a' }, - { path: 'B', url: 'https://figma.com/b' }, - ]) + ).toEqual([{ path: 'B', url: 'https://figma.com/b' }]) }) it('treats malformed and empty params as no links', () => { expect(resolveFigmaLinks(undefined)).toEqual([]) expect(resolveFigmaLinks(null)).toEqual([]) + expect(resolveFigmaLinks(false)).toEqual([]) + expect(resolveFigmaLinks('https://figma.com/x')).toEqual([]) expect(resolveFigmaLinks('')).toEqual([]) expect(resolveFigmaLinks({})).toEqual([]) expect(resolveFigmaLinks({ path: 'no url' })).toEqual([]) diff --git a/.storybook/figma/figma.ts b/.storybook/figma/figma.ts index 18913cda..766ed666 100644 --- a/.storybook/figma/figma.ts +++ b/.storybook/figma/figma.ts @@ -8,10 +8,6 @@ function isRecord(value: unknown): value is Record { } function resolveEntry(entry: unknown): ResolvedFigmaLink[] { - if (typeof entry === 'string') { - return entry.length > 0 ? [{ path: entry, url: entry }] : [] - } - if (isRecord(entry) && typeof entry.url === 'string' && entry.url.length > 0) { const path = typeof entry.path === 'string' && entry.path.length > 0 ? entry.path : entry.url From 517cf27772fd4e85d8b3ee89b69ebe447aee1651 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:43:23 -0700 Subject: [PATCH 07/11] build(storybook): type-check the figma and badges tools --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index e60c62b6..82bde444 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "@doist/tsconfig", - "include": ["src", "types", ".storybook"], + "include": ["src", "types", ".storybook", ".storybook/figma/**/*", ".storybook/badges/**/*"], "compilerOptions": { "allowJs": true, "module": "esnext", @@ -17,6 +17,7 @@ "@storybook/react": ["node_modules/@storybook/react/dist/index.d.ts"], "@storybook/react-vite": ["node_modules/@storybook/react-vite/dist/index.d.ts"], "storybook/actions": ["node_modules/storybook/dist/actions/index.d.ts"], + "storybook/manager-api": ["node_modules/storybook/dist/manager-api/index.d.ts"], "storybook/test": ["node_modules/storybook/dist/test/index.d.ts"], "*": ["src/*", "node_modules/*"] }, From a74d315817d3f615c25ad480c8d316cf7e955e25 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:43:24 -0700 Subject: [PATCH 08/11] refactor(storybook): guard badge tones against type drift --- .storybook/badges/badges.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.storybook/badges/badges.ts b/.storybook/badges/badges.ts index 89565b67..b7b69f4b 100644 --- a/.storybook/badges/badges.ts +++ b/.storybook/badges/badges.ts @@ -8,7 +8,16 @@ type ResolvedBadge = { tone: BadgeTone } -const VALID_TONES: readonly BadgeTone[] = ['info', 'positive', 'promote', 'attention', 'warning'] +// Ensure valid tones are checked against the Badge's tone prop +const TONE_SET: Record = { + info: true, + positive: true, + promote: true, + attention: true, + warning: true, +} + +const VALID_TONES = Object.keys(TONE_SET) as BadgeTone[] function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) From bec3922f059881b530f3cfd50ecc3ecd30ac9e8e Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:44:05 -0700 Subject: [PATCH 09/11] feat(storybook): support figma:false to hide the figma tool --- .plop/templates/component/component.stories.tsx.hbs | 2 ++ .storybook/figma/figma-tool.tsx | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.plop/templates/component/component.stories.tsx.hbs b/.plop/templates/component/component.stories.tsx.hbs index e5fde728..9c093b58 100644 --- a/.plop/templates/component/component.stories.tsx.hbs +++ b/.plop/templates/component/component.stories.tsx.hbs @@ -8,6 +8,8 @@ const meta = { component: {{pascalCase name}}, parameters: { badges: ['accessible'], + // Link a Figma design to show it in the toolbar, or set `figma: false` + // to hide the tool on pages with no matching design (e.g. hook docs): // figma: { path: 'Figma file › Page › Section › Component', url: 'https://www.figma.com/design/…?node-id=…' }, }, } satisfies Meta diff --git a/.storybook/figma/figma-tool.tsx b/.storybook/figma/figma-tool.tsx index 24657cf0..dd5172c2 100644 --- a/.storybook/figma/figma-tool.tsx +++ b/.storybook/figma/figma-tool.tsx @@ -14,6 +14,10 @@ export const FigmaTool = React.memo(function FigmaTool() { const figmaParameter = useParameter(FIGMA_PARAMETER, undefined) const links = React.useMemo(() => resolveFigmaLinks(figmaParameter), [figmaParameter]) + if (figmaParameter === false) { + return null + } + return ( {links.length === 0 ? ( From 2a63b2073e0a66e081dc19158348a0f713efe6e6 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 18:45:01 -0700 Subject: [PATCH 10/11] docs: opt non-design pages out of the figma tool --- src/hooks/use-previous/use-previous.mdx | 2 +- src/modal/modal-docs.mdx | 4 ++++ stories/reactist/Reactist.mdx | 2 +- stories/reactist/nesting-vs-polymorphism.mdx | 2 +- stories/reactist/simplified-nested-markup.mdx | 5 ++++- stories/reactist/single-child-stack-columns.mdx | 5 ++++- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/hooks/use-previous/use-previous.mdx b/src/hooks/use-previous/use-previous.mdx index 08c356de..3e08e5c4 100644 --- a/src/hooks/use-previous/use-previous.mdx +++ b/src/hooks/use-previous/use-previous.mdx @@ -1,7 +1,7 @@ import { Meta } from '@storybook/addon-docs/blocks' import { usePrevious } from './use-previous' - + # usePrevious diff --git a/src/modal/modal-docs.mdx b/src/modal/modal-docs.mdx index 24c106f9..1c9921d3 100644 --- a/src/modal/modal-docs.mdx +++ b/src/modal/modal-docs.mdx @@ -6,6 +6,10 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, ModalActions, ModalCloseBut component={Modal} parameters={{ badges: ['accessible'], + figma: { + path: 'Web › Components / Todoist › Settings › Modal / Header', + url: 'https://www.figma.com/design/LYlWNzvhMDh907l07mPPQk/Product-Library---Web?node-id=7443-241153', + }, }} /> diff --git a/stories/reactist/Reactist.mdx b/stories/reactist/Reactist.mdx index 92c6fcba..6f4cec78 100644 --- a/stories/reactist/Reactist.mdx +++ b/stories/reactist/Reactist.mdx @@ -1,6 +1,6 @@ import { Story, Meta } from '@storybook/addon-docs/blocks' - + ## Welcome diff --git a/stories/reactist/nesting-vs-polymorphism.mdx b/stories/reactist/nesting-vs-polymorphism.mdx index 87a034a4..784302e6 100644 --- a/stories/reactist/nesting-vs-polymorphism.mdx +++ b/stories/reactist/nesting-vs-polymorphism.mdx @@ -1,6 +1,6 @@ import { Story, Meta } from '@storybook/addon-docs/blocks' - + # Nesting vs. combining via polymorphism diff --git a/stories/reactist/simplified-nested-markup.mdx b/stories/reactist/simplified-nested-markup.mdx index b323cf5f..8944c118 100644 --- a/stories/reactist/simplified-nested-markup.mdx +++ b/stories/reactist/simplified-nested-markup.mdx @@ -1,6 +1,9 @@ import { Story, Meta } from '@storybook/addon-docs/blocks' - + # Simplified markup of the `Stack` and `Inline` components diff --git a/stories/reactist/single-child-stack-columns.mdx b/stories/reactist/single-child-stack-columns.mdx index 78e47dee..92ad1c06 100644 --- a/stories/reactist/single-child-stack-columns.mdx +++ b/stories/reactist/single-child-stack-columns.mdx @@ -1,6 +1,9 @@ import { Story, Meta } from '@storybook/addon-docs/blocks' - + # About `Stack` or `Columns` with a single child From a3dd4b2c5eca0d6212cf91050fe01e8ef871f199 Mon Sep 17 00:00:00 2001 From: Frankie Yan Date: Thu, 18 Jun 2026 19:06:17 -0700 Subject: [PATCH 11/11] build(storybook): add manager-api path for react18 type-check --- tsconfig.react18.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.react18.json b/tsconfig.react18.json index 9594bbc4..7d9d292d 100644 --- a/tsconfig.react18.json +++ b/tsconfig.react18.json @@ -6,6 +6,7 @@ "@storybook/react": ["node_modules/@storybook/react/dist/index.d.ts"], "@storybook/react-vite": ["node_modules/@storybook/react-vite/dist/index.d.ts"], "storybook/actions": ["node_modules/storybook/dist/actions/index.d.ts"], + "storybook/manager-api": ["node_modules/storybook/dist/manager-api/index.d.ts"], "storybook/test": ["node_modules/storybook/dist/test/index.d.ts"], "react": ["node_modules/@types/react-18"], "react/*": ["node_modules/@types/react-18/*"],