Skip to content

Commit 1316c55

Browse files
OneHunnidclaude
andcommitted
feat(tokens): split typography story into Utility and Editorial sections
Replaces the single Typography story with two stories so each section renders with its own heading and description above its own canvas. Custom docs page (Title + Description + Stories with includePrimary and no section title) skips the default Primary canvas and removes the "Stories" separator heading. Editorial label rendering: getDisplayText now handles editorial keys by expanding h1/h2/h3 to "Heading 1/2/3" with alt and medium kept as variant suffixes. Component description mentions the two type sets and links to the Utility & Editorial Type System proposal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1e28a1d commit 1316c55

1 file changed

Lines changed: 63 additions & 4 deletions

File tree

packages/tokens/stories/typography.stories.tsx

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { vars } from '@launchpad-ui/vars';
2+
import { Description, Stories, Title } from '@storybook/addon-docs/blocks';
23

34
import { Button } from '../../components/src/Button';
45
import { ToastRegion, toastQueue } from '../../components/src/Toast';
@@ -11,8 +12,15 @@ export default {
1112
docs: {
1213
description: {
1314
component:
14-
'Typography tokens for the LaunchPad design system. For components using these tokens, see [Text](/docs/components-content-text--docs), [Heading](/docs/components-content-heading--docs), [Label](/docs/components-content-label--docs), and [Code](/docs/components-content-code--docs).',
15+
'Typography tokens for the LaunchPad design system. Our typography is split between two type sets: Utility and Editorial. For components using these tokens, see [Text](/docs/components-content-text--docs), [Heading](/docs/components-content-heading--docs), [Label](/docs/components-content-label--docs), and [Code](/docs/components-content-code--docs). For the full framework, see the [Utility & Editorial Type System for LaunchPad](https://launchdarkly.atlassian.net/wiki/spaces/~712020490f77e4363240f1888e975e52e895be/pages/4939022523/) proposal.',
1516
},
17+
page: () => (
18+
<>
19+
<Title />
20+
<Description />
21+
<Stories title="" includePrimary />
22+
</>
23+
),
1624
},
1725
},
1826
};
@@ -33,15 +41,33 @@ const flatten = (obj: Record<string, unknown>, prefix = ''): Record<string, stri
3341
return result;
3442
};
3543

44+
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
45+
3646
const getDisplayText = (key: string) => {
3747
const parts = key.split('-');
3848

49+
if (parts[0] === 'editorial') {
50+
const variant = parts[1]; // display, h1, h2, h3
51+
const modifiers = parts.slice(2); // alt, medium, etc. all treated as variant suffixes
52+
53+
let base: string;
54+
if (variant === 'display') {
55+
base = 'Display';
56+
} else if (/^h\d+$/.test(variant)) {
57+
base = `Heading ${variant.slice(1)}`;
58+
} else {
59+
base = capitalize(variant);
60+
}
61+
62+
return modifiers.length ? `${base} ${modifiers.map(capitalize).join(' ')}` : base;
63+
}
64+
3965
const category = parts[0]; // heading, body, etc.
4066
const size = parts[1]; // 1, 2, etc.
4167
const weight = parts[2]; // medium, semibold, etc.
4268

4369
if (weight) {
44-
return `${category.charAt(0).toUpperCase() + category.slice(1)} ${size} - ${weight.charAt(0).toUpperCase() + weight.slice(1)}`;
70+
return `${capitalize(category)} ${size} - ${capitalize(weight)}`;
4571
}
4672
if (category === 'display') {
4773
return `Display ${size}`;
@@ -167,6 +193,39 @@ const TokenTable = ({ tokens }: { tokens: Record<string, string> }) => {
167193
);
168194
};
169195

170-
export const Typography = {
171-
render: () => <TokenTable tokens={flatten(vars.text)} />,
196+
const UTILITY_DESCRIPTION = `The foundation of the LaunchDarkly product. Inter for text, SF Mono for numeric content where vertical alignment matters. Designed to feel invisible: when it's working correctly, users don't notice it. Used across navigation, forms, tables, modals, settings, and any task-driven surface.`;
197+
198+
const EDITORIAL_DESCRIPTION =
199+
'Carries the LaunchDarkly brand voice into the product by using Sora for display headings. Used on first-time onboarding empty states, in-app banners, feature announcements, and educational moments where the user has paused. Should feel intentional and slightly different from the rest of the UI.';
200+
201+
const utilityTokens = () =>
202+
Object.fromEntries(
203+
Object.entries(flatten(vars.text)).filter(([key]) => !key.startsWith('editorial-')),
204+
);
205+
206+
const editorialTokens = () =>
207+
Object.fromEntries(
208+
Object.entries(flatten(vars.text)).filter(([key]) => key.startsWith('editorial-')),
209+
);
210+
211+
export const Utility = {
212+
parameters: {
213+
docs: {
214+
description: {
215+
story: UTILITY_DESCRIPTION,
216+
},
217+
},
218+
},
219+
render: () => <TokenTable tokens={utilityTokens()} />,
220+
};
221+
222+
export const Editorial = {
223+
parameters: {
224+
docs: {
225+
description: {
226+
story: EDITORIAL_DESCRIPTION,
227+
},
228+
},
229+
},
230+
render: () => <TokenTable tokens={editorialTokens()} />,
172231
};

0 commit comments

Comments
 (0)