@forumone/create-throughline 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/generator.d.ts +2 -0
  3. package/dist/generator.d.ts.map +1 -1
  4. package/dist/generator.js +23 -4
  5. package/dist/generator.js.map +1 -1
  6. package/dist/post-install.js +1 -1
  7. package/dist/post-install.js.map +1 -1
  8. package/dist/templates/base/README.md +8 -9
  9. package/dist/templates/base/apps/web/package.json.template +11 -10
  10. package/dist/templates/base/apps/web/src/app/api/inngest/route.ts.template +33 -4
  11. package/dist/templates/base/apps/web/src/payload.config.ts.template +21 -6
  12. package/dist/templates/base/apps/web/tsconfig.json +2 -1
  13. package/dist/templates/base/gitignore +15 -0
  14. package/dist/templates/base/package.json.template +4 -1
  15. package/dist/templates/base/pnpm-workspace.yaml +1 -0
  16. package/dist/templates/base/turbo.json +11 -0
  17. package/dist/templates/with-reference-ds/design-system/.storybook/main.ts +21 -0
  18. package/dist/templates/with-reference-ds/design-system/.storybook/preview.ts +19 -0
  19. package/dist/templates/with-reference-ds/design-system/README.md +33 -0
  20. package/dist/templates/with-reference-ds/design-system/gitignore +5 -0
  21. package/dist/templates/with-reference-ds/design-system/package.json.template +62 -0
  22. package/dist/templates/with-reference-ds/design-system/scripts/build-manifest.ts +89 -0
  23. package/dist/templates/with-reference-ds/design-system/scripts/build-styles.ts +28 -0
  24. package/dist/templates/with-reference-ds/design-system/scripts/build-tokens-css.ts +43 -0
  25. package/dist/templates/with-reference-ds/design-system/scripts/validate.ts +58 -0
  26. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.contract.ts +128 -0
  27. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.module.css +83 -0
  28. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.stories.tsx +39 -0
  29. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.test.tsx +27 -0
  30. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/CTASection.tsx +41 -0
  31. package/dist/templates/with-reference-ds/design-system/src/components/CTASection/index.ts +2 -0
  32. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.contract.ts +122 -0
  33. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.module.css +64 -0
  34. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.stories.tsx +45 -0
  35. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.test.tsx +37 -0
  36. package/dist/templates/with-reference-ds/design-system/src/components/Card/Card.tsx +39 -0
  37. package/dist/templates/with-reference-ds/design-system/src/components/Card/index.ts +2 -0
  38. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.contract.ts +78 -0
  39. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.module.css +34 -0
  40. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.stories.tsx +36 -0
  41. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.test.tsx +25 -0
  42. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/CardGrid.tsx +19 -0
  43. package/dist/templates/with-reference-ds/design-system/src/components/CardGrid/index.ts +2 -0
  44. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.contract.ts +87 -0
  45. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.module.css +18 -0
  46. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.stories.tsx +35 -0
  47. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.test.tsx +20 -0
  48. package/dist/templates/with-reference-ds/design-system/src/components/Divider/Divider.tsx +17 -0
  49. package/dist/templates/with-reference-ds/design-system/src/components/Divider/index.ts +2 -0
  50. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.contract.ts +104 -0
  51. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.module.css +66 -0
  52. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.stories.tsx +61 -0
  53. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.test.tsx +30 -0
  54. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/FAQ.tsx +41 -0
  55. package/dist/templates/with-reference-ds/design-system/src/components/FAQ/index.ts +2 -0
  56. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.contract.ts +182 -0
  57. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.module.css +124 -0
  58. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.stories.tsx +58 -0
  59. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.test.tsx +49 -0
  60. package/dist/templates/with-reference-ds/design-system/src/components/Hero/Hero.tsx +67 -0
  61. package/dist/templates/with-reference-ds/design-system/src/components/Hero/index.ts +2 -0
  62. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.contract.ts +94 -0
  63. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.module.css +49 -0
  64. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.stories.tsx +37 -0
  65. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.test.tsx +35 -0
  66. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/MediaBlock.tsx +49 -0
  67. package/dist/templates/with-reference-ds/design-system/src/components/MediaBlock/index.ts +7 -0
  68. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.contract.ts +107 -0
  69. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.module.css +72 -0
  70. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.stories.tsx +39 -0
  71. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.test.tsx +26 -0
  72. package/dist/templates/with-reference-ds/design-system/src/components/Prose/Prose.tsx +15 -0
  73. package/dist/templates/with-reference-ds/design-system/src/components/Prose/index.ts +2 -0
  74. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.contract.ts +101 -0
  75. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.module.css +59 -0
  76. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.stories.tsx +44 -0
  77. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.test.tsx +37 -0
  78. package/dist/templates/with-reference-ds/design-system/src/components/Quote/Quote.tsx +40 -0
  79. package/dist/templates/with-reference-ds/design-system/src/components/Quote/index.ts +2 -0
  80. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.contract.ts +108 -0
  81. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.module.css +36 -0
  82. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.stories.tsx +33 -0
  83. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.test.tsx +28 -0
  84. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/SectionIntro.tsx +28 -0
  85. package/dist/templates/with-reference-ds/design-system/src/components/SectionIntro/index.ts +2 -0
  86. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.contract.ts +76 -0
  87. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.module.css +23 -0
  88. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.stories.tsx +25 -0
  89. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.test.tsx +17 -0
  90. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/Spacer.tsx +19 -0
  91. package/dist/templates/with-reference-ds/design-system/src/components/Spacer/index.ts +2 -0
  92. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.contract.ts +103 -0
  93. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.module.css +88 -0
  94. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.stories.tsx +44 -0
  95. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.test.tsx +44 -0
  96. package/dist/templates/with-reference-ds/design-system/src/components/Stats/Stats.tsx +39 -0
  97. package/dist/templates/with-reference-ds/design-system/src/components/Stats/index.ts +2 -0
  98. package/dist/templates/with-reference-ds/design-system/src/css-modules.d.ts +5 -0
  99. package/dist/templates/with-reference-ds/design-system/src/foundations/Colors.stories.tsx +46 -0
  100. package/dist/templates/with-reference-ds/design-system/src/foundations/Elevation.stories.tsx +37 -0
  101. package/dist/templates/with-reference-ds/design-system/src/foundations/LayoutContainers.stories.tsx +86 -0
  102. package/dist/templates/with-reference-ds/design-system/src/foundations/Radii.stories.tsx +38 -0
  103. package/dist/templates/with-reference-ds/design-system/src/foundations/Spacing.stories.tsx +33 -0
  104. package/dist/templates/with-reference-ds/design-system/src/foundations/Typography.stories.tsx +55 -0
  105. package/dist/templates/with-reference-ds/design-system/src/foundations/_helpers.tsx +82 -0
  106. package/dist/templates/with-reference-ds/design-system/src/index.ts +14 -0
  107. package/dist/templates/with-reference-ds/design-system/src/styles/base.css +55 -0
  108. package/dist/templates/with-reference-ds/design-system/src/styles/reset.css +48 -0
  109. package/dist/templates/with-reference-ds/design-system/src/styles/tokens.css +116 -0
  110. package/dist/templates/with-reference-ds/design-system/src/tokens/colors.ts +54 -0
  111. package/dist/templates/with-reference-ds/design-system/src/tokens/index.ts +32 -0
  112. package/dist/templates/with-reference-ds/design-system/src/tokens/layout.ts +26 -0
  113. package/dist/templates/with-reference-ds/design-system/src/tokens/radii.ts +11 -0
  114. package/dist/templates/with-reference-ds/design-system/src/tokens/spacing.ts +26 -0
  115. package/dist/templates/with-reference-ds/design-system/src/tokens/typography.ts +32 -0
  116. package/dist/templates/with-reference-ds/design-system/tsconfig.json +24 -0
  117. package/dist/templates/with-reference-ds/design-system/vitest.config.ts +15 -0
  118. package/dist/templates/with-reference-ds/design-system/vitest.setup.ts +7 -0
  119. package/dist/templates/without-reference-ds/design-system/.storybook/main.ts +21 -0
  120. package/dist/templates/without-reference-ds/design-system/.storybook/preview.ts +16 -0
  121. package/dist/templates/without-reference-ds/design-system/README.md +20 -0
  122. package/dist/templates/without-reference-ds/design-system/gitignore +5 -0
  123. package/dist/templates/without-reference-ds/design-system/package.json.template +33 -0
  124. package/dist/templates/without-reference-ds/design-system/src/foundations/Welcome.stories.tsx +24 -0
  125. package/dist/templates/without-reference-ds/design-system/src/index.ts +9 -0
  126. package/dist/templates/without-reference-ds/design-system/tsconfig.json +11 -0
  127. package/dist/utils/versions.d.ts +20 -0
  128. package/dist/utils/versions.d.ts.map +1 -0
  129. package/dist/utils/versions.js +59 -0
  130. package/dist/utils/versions.js.map +1 -0
  131. package/dist/versions.json +14 -0
  132. package/package.json +7 -5
  133. package/dist/templates/with-reference-ds/packages/design-system/package.json.template +0 -18
  134. package/dist/templates/with-reference-ds/packages/design-system/src/index.ts +0 -6
  135. package/dist/templates/with-reference-ds/packages/design-system/src/manifest.ts +0 -6
  136. package/dist/templates/with-reference-ds/packages/design-system/tsconfig.json +0 -9
  137. package/dist/templates/without-reference-ds/packages/design-system/README.md +0 -19
  138. package/dist/templates/without-reference-ds/packages/design-system/package.json.template +0 -14
  139. package/dist/templates/without-reference-ds/packages/design-system/src/index.ts +0 -8
@@ -0,0 +1,103 @@
1
+ import type { ComponentContract } from '@forumone/throughline-design-contract'
2
+
3
+ export const contract: ComponentContract = {
4
+ name: 'Stats',
5
+ category: 'data',
6
+ description:
7
+ 'A numerical data display with two to four stat items, each with a value, a label, and an optional description.',
8
+ intent:
9
+ 'Surface a small set of headline metrics in a scannable row. Use for impact reports, program highlights, and annual summaries where numbers tell the story. Not appropriate for dense tabular data — that needs a real table component.',
10
+
11
+ composition: {
12
+ placement: ['section'],
13
+ maxPerPage: null,
14
+ requiredSiblings: [],
15
+ forbiddenAdjacent: [],
16
+ },
17
+
18
+ content: {
19
+ fields: [
20
+ {
21
+ name: 'eyebrow',
22
+ type: 'text',
23
+ required: false,
24
+ maxLength: 40,
25
+ },
26
+ {
27
+ name: 'headline',
28
+ type: 'text',
29
+ required: false,
30
+ maxLength: 100,
31
+ },
32
+ {
33
+ name: 'items',
34
+ type: 'array',
35
+ required: true,
36
+ constraints: 'Two to four items',
37
+ of: [
38
+ { name: 'value', type: 'text', required: true, maxLength: 20 },
39
+ { name: 'label', type: 'text', required: true, maxLength: 60 },
40
+ { name: 'description', type: 'text', required: false, maxLength: 80 },
41
+ ],
42
+ },
43
+ ],
44
+ },
45
+
46
+ tokens: {
47
+ consumes: [
48
+ 'color.text.primary',
49
+ 'color.text.secondary',
50
+ 'color.brand.primary',
51
+ 'spacing.16',
52
+ 'spacing.10',
53
+ 'spacing.8',
54
+ 'spacing.3',
55
+ 'spacing.2',
56
+ 'spacing.1',
57
+ 'spacing.container',
58
+ 'font.size.5xl',
59
+ 'font.size.3xl',
60
+ 'font.size.sm',
61
+ 'font.weight.bold',
62
+ 'font.weight.semibold',
63
+ 'line.height.tight',
64
+ 'letter.spacing.wide',
65
+ ],
66
+ },
67
+
68
+ accessibility: {
69
+ keyboardSupport: [],
70
+ screenReaderBehavior:
71
+ 'Statistics are rendered as a <dl> with <dt>/<dd> pairs. Screen readers traverse each value + label + description as a definition list entry.',
72
+ contentWarnings: [
73
+ 'Keep values concise — "4,200" instead of "4,200,000,000 dollars"',
74
+ 'Never invent numbers to fill the grid; use the real count even if unequal',
75
+ ],
76
+ },
77
+
78
+ examples: [
79
+ { label: 'Three stats', intent: 'Annual report highlights', storyId: 'stats--three-stats' },
80
+ { label: 'Two stats', intent: 'Minimal impact summary', storyId: 'stats--two-stats' },
81
+ { label: 'Four stats', intent: 'Dense overview', storyId: 'stats--four-stats' },
82
+ ],
83
+
84
+ antiExamples: [
85
+ {
86
+ label: 'Five or more stats',
87
+ why: 'Beyond four items the layout cramps on mobile and the individual numbers lose impact',
88
+ useInstead: 'Pick the top four; move the rest to a full report page',
89
+ },
90
+ {
91
+ label: 'Paragraph-length values',
92
+ why: 'Stats are designed for at-a-glance reading; long values overwhelm the layout',
93
+ useInstead: 'Use Prose for extended narrative',
94
+ },
95
+ ],
96
+
97
+ behavior: {
98
+ fetchesData: false,
99
+ hasClientState: false,
100
+ animates: false,
101
+ requiresAnalytics: false,
102
+ },
103
+ }
@@ -0,0 +1,88 @@
1
+ .stats {
2
+ padding: var(--spacing-16) 0;
3
+ }
4
+
5
+ .container {
6
+ max-width: 72rem;
7
+ margin: 0 auto;
8
+ padding: 0 var(--spacing-container);
9
+ }
10
+
11
+ .eyebrow {
12
+ font-size: var(--font-size-sm);
13
+ font-weight: var(--font-weight-semibold);
14
+ text-transform: uppercase;
15
+ letter-spacing: var(--letter-spacing-wide);
16
+ color: var(--color-text-secondary);
17
+ margin-bottom: var(--spacing-3);
18
+ text-align: center;
19
+ }
20
+
21
+ .headline {
22
+ font-size: var(--font-size-3xl);
23
+ font-weight: var(--font-weight-semibold);
24
+ margin-bottom: var(--spacing-10);
25
+ text-align: center;
26
+ }
27
+
28
+ .list {
29
+ display: grid;
30
+ gap: var(--spacing-8);
31
+ margin: 0;
32
+ }
33
+
34
+ .cols-2 {
35
+ grid-template-columns: repeat(2, 1fr);
36
+ }
37
+
38
+ .cols-3 {
39
+ grid-template-columns: repeat(3, 1fr);
40
+ }
41
+
42
+ .cols-4 {
43
+ grid-template-columns: repeat(4, 1fr);
44
+ }
45
+
46
+ @media (max-width: 720px) {
47
+ .cols-2,
48
+ .cols-3,
49
+ .cols-4 {
50
+ grid-template-columns: repeat(2, 1fr);
51
+ }
52
+ }
53
+
54
+ @media (max-width: 480px) {
55
+ .cols-2,
56
+ .cols-3,
57
+ .cols-4 {
58
+ grid-template-columns: 1fr;
59
+ }
60
+ }
61
+
62
+ .item {
63
+ text-align: center;
64
+ }
65
+
66
+ .value {
67
+ font-size: var(--font-size-5xl);
68
+ font-weight: var(--font-weight-bold);
69
+ line-height: var(--line-height-tight);
70
+ color: var(--color-brand-primary);
71
+ margin: 0;
72
+ }
73
+
74
+ .label {
75
+ margin: var(--spacing-2) 0 0;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: var(--spacing-1);
79
+ }
80
+
81
+ .labelText {
82
+ font-weight: var(--font-weight-semibold);
83
+ }
84
+
85
+ .description {
86
+ font-size: var(--font-size-sm);
87
+ color: var(--color-text-secondary);
88
+ }
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { Stats } from './Stats.js'
3
+
4
+ const meta: Meta<typeof Stats> = {
5
+ title: 'Stats',
6
+ component: Stats,
7
+ }
8
+
9
+ export default meta
10
+
11
+ type Story = StoryObj<typeof Stats>
12
+
13
+ export const ThreeStats: Story = {
14
+ args: {
15
+ eyebrow: 'Impact',
16
+ headline: 'Year in review',
17
+ items: [
18
+ { value: '128', label: 'Grants awarded', description: 'Across twelve programs' },
19
+ { value: '$4.2M', label: 'Total funded' },
20
+ { value: '87%', label: 'Completion rate', description: 'Measured over five years' },
21
+ ],
22
+ },
23
+ }
24
+
25
+ export const TwoStats: Story = {
26
+ args: {
27
+ items: [
28
+ { value: '30', label: 'Partner organizations' },
29
+ { value: '1,200', label: 'Researchers supported' },
30
+ ],
31
+ },
32
+ }
33
+
34
+ export const FourStats: Story = {
35
+ args: {
36
+ headline: 'By the numbers',
37
+ items: [
38
+ { value: '12', label: 'Countries' },
39
+ { value: '48', label: 'Programs' },
40
+ { value: '300+', label: 'Publications' },
41
+ { value: '5M', label: 'People reached' },
42
+ ],
43
+ },
44
+ }
@@ -0,0 +1,44 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { Stats } from './Stats.js'
4
+
5
+ describe('Stats', () => {
6
+ it('renders each item as a dt/dd pair inside a dl', () => {
7
+ const { container } = render(
8
+ <Stats
9
+ items={[
10
+ { value: '100', label: 'Projects' },
11
+ { value: '50', label: 'Partners' },
12
+ ]}
13
+ />,
14
+ )
15
+ expect(container.querySelector('dl')).not.toBeNull()
16
+ const dts = container.querySelectorAll('dt')
17
+ const dds = container.querySelectorAll('dd')
18
+ expect(dts).toHaveLength(2)
19
+ expect(dds).toHaveLength(2)
20
+ expect(dts[0]).toHaveTextContent('100')
21
+ expect(dds[0]).toHaveTextContent('Projects')
22
+ })
23
+
24
+ it('renders optional eyebrow + headline', () => {
25
+ render(
26
+ <Stats
27
+ eyebrow="Impact"
28
+ headline="Summary"
29
+ items={[{ value: '1', label: 'Thing' }]}
30
+ />,
31
+ )
32
+ expect(screen.getByText('Impact')).toBeInTheDocument()
33
+ expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Summary')
34
+ })
35
+
36
+ it('renders item description when present', () => {
37
+ render(
38
+ <Stats
39
+ items={[{ value: '1', label: 'Thing', description: 'Extra context' }]}
40
+ />,
41
+ )
42
+ expect(screen.getByText('Extra context')).toBeInTheDocument()
43
+ })
44
+ })
@@ -0,0 +1,39 @@
1
+ import { clsx } from 'clsx'
2
+ import styles from './Stats.module.css'
3
+
4
+ export interface StatItem {
5
+ value: string
6
+ label: string
7
+ description?: string
8
+ }
9
+
10
+ export interface StatsProps {
11
+ eyebrow?: string
12
+ headline?: string
13
+ items: StatItem[]
14
+ className?: string
15
+ }
16
+
17
+ export function Stats({ eyebrow, headline, items, className }: StatsProps) {
18
+ return (
19
+ <section className={clsx(styles.stats, className)}>
20
+ <div className={styles.container}>
21
+ {eyebrow ? <p className={styles.eyebrow}>{eyebrow}</p> : null}
22
+ {headline ? <h2 className={styles.headline}>{headline}</h2> : null}
23
+ <dl className={clsx(styles.list, styles[`cols-${items.length}`])}>
24
+ {items.map((item) => (
25
+ <div key={`${item.value}-${item.label}`} className={styles.item}>
26
+ <dt className={styles.value}>{item.value}</dt>
27
+ <dd className={styles.label}>
28
+ <span className={styles.labelText}>{item.label}</span>
29
+ {item.description ? (
30
+ <span className={styles.description}>{item.description}</span>
31
+ ) : null}
32
+ </dd>
33
+ </div>
34
+ ))}
35
+ </dl>
36
+ </div>
37
+ </section>
38
+ )
39
+ }
@@ -0,0 +1,2 @@
1
+ export { Stats, type StatsProps, type StatItem } from './Stats.js'
2
+ export { contract as statsContract } from './Stats.contract.js'
@@ -0,0 +1,5 @@
1
+ /* eslint-disable import-x/no-default-export -- CSS Modules loaders emit a default export. */
2
+ declare module '*.module.css' {
3
+ const classes: Record<string, string>
4
+ export default classes
5
+ }
@@ -0,0 +1,46 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { colors } from '../tokens'
3
+ import { FoundationPage } from './_helpers.tsx'
4
+
5
+ const meta: Meta = {
6
+ title: 'Foundations/Colors',
7
+ }
8
+ export default meta
9
+
10
+ function cssVar(name: string): string {
11
+ return `--${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\./g, '-')}`
12
+ }
13
+
14
+ const groups = ['neutral', 'brand', 'text', 'bg', 'border', 'state']
15
+
16
+ export const Colors: StoryObj = {
17
+ render: () => (
18
+ <FoundationPage
19
+ title="Colors"
20
+ intro="The palette is grouped by role: neutral ramp, brand accents, text, background, border, and state. Components read these as CSS custom properties so a brand override re-themes everything without touching component code."
21
+ >
22
+ {groups.map((group) => {
23
+ const entries = Object.entries(colors).filter(([k]) => k.startsWith(`color.${group}.`))
24
+ if (entries.length === 0) return null
25
+ return (
26
+ <section key={group} style={{ marginBottom: '2rem' }}>
27
+ <h2 style={{ fontSize: 'var(--font-size-lg)', margin: '0 0 0.75rem', textTransform: 'capitalize' }}>
28
+ {group}
29
+ </h2>
30
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(9rem, 1fr))', gap: '0.75rem' }}>
31
+ {entries.map(([name, value]) => (
32
+ <div key={name} style={{ border: '1px solid var(--color-border-default)', borderRadius: 'var(--radius-md)', overflow: 'hidden' }}>
33
+ <div style={{ height: '3.5rem', background: value }} />
34
+ <div style={{ padding: '0.5rem' }}>
35
+ <div style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)' }}>{cssVar(name)}</div>
36
+ <div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>{value}</div>
37
+ </div>
38
+ </div>
39
+ ))}
40
+ </div>
41
+ </section>
42
+ )
43
+ })}
44
+ </FoundationPage>
45
+ ),
46
+ }
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { FoundationPage } from './_helpers.tsx'
3
+
4
+ const meta: Meta = {
5
+ title: 'Foundations/Elevation',
6
+ }
7
+ export default meta
8
+
9
+ export const Elevation: StoryObj = {
10
+ render: () => (
11
+ <FoundationPage
12
+ title="Elevation"
13
+ intro="The reference design system uses flat depth: elevation is expressed through 1px borders and surface-color shifts rather than drop shadows. Reserve real shadows for transient overlays (modals, popovers)."
14
+ >
15
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(12rem, 1fr))', gap: '1rem' }}>
16
+ <div style={{ padding: '1.5rem', background: 'var(--color-bg-primary)', border: '1px solid var(--color-border-default)', borderRadius: 'var(--radius-lg)' }}>
17
+ <strong>Surface</strong>
18
+ <p style={{ margin: '0.5rem 0 0', color: 'var(--color-text-secondary)', fontSize: 'var(--font-size-sm)' }}>
19
+ bg.primary + border.default
20
+ </p>
21
+ </div>
22
+ <div style={{ padding: '1.5rem', background: 'var(--color-bg-secondary)', border: '1px solid var(--color-border-strong)', borderRadius: 'var(--radius-lg)' }}>
23
+ <strong>Raised</strong>
24
+ <p style={{ margin: '0.5rem 0 0', color: 'var(--color-text-secondary)', fontSize: 'var(--font-size-sm)' }}>
25
+ bg.secondary + border.strong
26
+ </p>
27
+ </div>
28
+ <div style={{ padding: '1.5rem', background: 'var(--color-bg-primary)', border: '1px solid var(--color-border-default)', borderRadius: 'var(--radius-lg)', boxShadow: '0 8px 24px rgba(0,0,0,0.12)' }}>
29
+ <strong>Overlay</strong>
30
+ <p style={{ margin: '0.5rem 0 0', color: 'var(--color-text-secondary)', fontSize: 'var(--font-size-sm)' }}>
31
+ shadow — modals / popovers only
32
+ </p>
33
+ </div>
34
+ </div>
35
+ </FoundationPage>
36
+ ),
37
+ }
@@ -0,0 +1,86 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { layout } from '../tokens'
3
+ import { FoundationPage, TokenTable } from './_helpers.tsx'
4
+
5
+ const meta: Meta = {
6
+ title: 'Foundations/Layout & Containers',
7
+ }
8
+ export default meta
9
+
10
+ /** Token name → CSS custom property (mirrors build-tokens-css). */
11
+ function cssVar(name: string): string {
12
+ return `--${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\./g, '-')}`
13
+ }
14
+
15
+ const containers = Object.entries(layout).filter(([k]) => k.startsWith('layout.container.'))
16
+ const breakpoints = Object.entries(layout).filter(([k]) => k.startsWith('layout.breakpoint.'))
17
+ const gutter = layout['layout.gutter']
18
+ const margin = layout['layout.margin']
19
+
20
+ export const LayoutAndContainers: StoryObj = {
21
+ render: () => (
22
+ <FoundationPage
23
+ title="Layout & Containers"
24
+ intro="Page content constrains to one of a small set of max-widths. The gutter is the space between grid columns; the page margin is the breathing room between the container and the viewport edge. Breakpoints are min-width and progressively widen the layout."
25
+ >
26
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '0 0 1rem' }}>Container widths</h2>
27
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '2.5rem' }}>
28
+ {containers.map(([name, value]) => (
29
+ <div key={name}>
30
+ <div
31
+ style={{
32
+ fontFamily: 'var(--font-family-mono)',
33
+ fontSize: 'var(--font-size-xs)',
34
+ color: 'var(--color-text-secondary)',
35
+ marginBottom: '0.25rem',
36
+ }}
37
+ >
38
+ {cssVar(name)} · {value}
39
+ </div>
40
+ <div
41
+ style={{
42
+ height: '2rem',
43
+ width: `min(100%, ${value})`,
44
+ background: 'var(--color-brand-primary)',
45
+ borderRadius: 'var(--radius-sm)',
46
+ }}
47
+ />
48
+ </div>
49
+ ))}
50
+ </div>
51
+
52
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '0 0 1rem' }}>Gutter & page margin</h2>
53
+ <TokenTable
54
+ rows={[
55
+ {
56
+ name: cssVar('layout.gutter'),
57
+ value: gutter,
58
+ preview: <div style={{ height: '1rem', width: gutter, background: 'var(--color-brand-secondary)' }} />,
59
+ },
60
+ {
61
+ name: cssVar('layout.margin'),
62
+ value: margin,
63
+ preview: <div style={{ height: '1rem', width: margin, background: 'var(--color-brand-secondary)' }} />,
64
+ },
65
+ ]}
66
+ />
67
+
68
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '2.5rem 0 1rem' }}>Breakpoints</h2>
69
+ <TokenTable
70
+ rows={breakpoints.map(([name, value]) => ({
71
+ name: cssVar(name),
72
+ value,
73
+ preview: (
74
+ <div
75
+ style={{
76
+ height: '0.5rem',
77
+ width: `min(100%, calc(${value} / 4))`,
78
+ background: 'var(--color-border-strong)',
79
+ }}
80
+ />
81
+ ),
82
+ }))}
83
+ />
84
+ </FoundationPage>
85
+ ),
86
+ }
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { radii } from '../tokens'
3
+ import { FoundationPage } from './_helpers.tsx'
4
+
5
+ const meta: Meta = {
6
+ title: 'Foundations/Radii',
7
+ }
8
+ export default meta
9
+
10
+ function cssVar(name: string): string {
11
+ return `--${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\./g, '-')}`
12
+ }
13
+
14
+ export const Radii: StoryObj = {
15
+ render: () => (
16
+ <FoundationPage
17
+ title="Radii"
18
+ intro="Corner radii available to components. A brand may collapse this to a binary set (sharp vs. pill); the tokens stay the same so components don't change."
19
+ >
20
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(10rem, 1fr))', gap: '1rem' }}>
21
+ {Object.entries(radii).map(([name, value]) => (
22
+ <div key={name}>
23
+ <div
24
+ style={{
25
+ height: '5rem',
26
+ background: 'var(--color-bg-tertiary)',
27
+ border: '1px solid var(--color-border-strong)',
28
+ borderRadius: value,
29
+ }}
30
+ />
31
+ <div style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', marginTop: '0.5rem' }}>{cssVar(name)}</div>
32
+ <div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>{value}</div>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ </FoundationPage>
37
+ ),
38
+ }
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { spacing } from '../tokens'
3
+ import { FoundationPage } from './_helpers.tsx'
4
+
5
+ const meta: Meta = {
6
+ title: 'Foundations/Spacing',
7
+ }
8
+ export default meta
9
+
10
+ function cssVar(name: string): string {
11
+ return `--${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\./g, '-')}`
12
+ }
13
+
14
+ export const Spacing: StoryObj = {
15
+ render: () => (
16
+ <FoundationPage
17
+ title="Spacing"
18
+ intro="A single spacing scale drives padding, margins, and gaps. Section and container spacing are semantic aliases on top of the numeric scale."
19
+ >
20
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
21
+ {Object.entries(spacing).map(([name, value]) => (
22
+ <div key={name} style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
23
+ <span style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', width: '11rem', flexShrink: 0 }}>
24
+ {cssVar(name)}
25
+ </span>
26
+ <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-secondary)', width: '4rem', flexShrink: 0 }}>{value}</span>
27
+ <div style={{ height: '1rem', width: value, background: 'var(--color-brand-primary)', borderRadius: 'var(--radius-sm)' }} />
28
+ </div>
29
+ ))}
30
+ </div>
31
+ </FoundationPage>
32
+ ),
33
+ }
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { typography } from '../tokens'
3
+ import { FoundationPage, TokenTable } from './_helpers.tsx'
4
+
5
+ const meta: Meta = {
6
+ title: 'Foundations/Typography',
7
+ }
8
+ export default meta
9
+
10
+ function cssVar(name: string): string {
11
+ return `--${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\./g, '-')}`
12
+ }
13
+
14
+ const sizes = Object.entries(typography).filter(([k]) => k.startsWith('font.size.'))
15
+ const families = Object.entries(typography).filter(([k]) => k.startsWith('font.family.'))
16
+ const weights = Object.entries(typography).filter(([k]) => k.startsWith('font.weight.'))
17
+
18
+ export const Typography: StoryObj = {
19
+ render: () => (
20
+ <FoundationPage
21
+ title="Typography"
22
+ intro="Type sizes form a single scale; hierarchy comes from size and weight, not from mixing typefaces. The font family is a token, so a brand swap (e.g. DM Sans) re-themes every component."
23
+ >
24
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '0 0 1rem' }}>Type scale</h2>
25
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '2.5rem' }}>
26
+ {sizes.map(([name, value]) => (
27
+ <div key={name} style={{ display: 'flex', alignItems: 'baseline', gap: '1rem' }}>
28
+ <span style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', width: '8rem', flexShrink: 0 }}>
29
+ {cssVar(name)}
30
+ </span>
31
+ <span style={{ fontSize: value, lineHeight: 1.1 }}>The quick brown fox</span>
32
+ </div>
33
+ ))}
34
+ </div>
35
+
36
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '0 0 1rem' }}>Families</h2>
37
+ <TokenTable
38
+ rows={families.map(([name, value]) => ({
39
+ name: cssVar(name),
40
+ value,
41
+ preview: <span style={{ fontFamily: value }}>Aa Bb Cc 123</span>,
42
+ }))}
43
+ />
44
+
45
+ <h2 style={{ fontSize: 'var(--font-size-xl)', margin: '2.5rem 0 1rem' }}>Weights</h2>
46
+ <TokenTable
47
+ rows={weights.map(([name, value]) => ({
48
+ name: cssVar(name),
49
+ value,
50
+ preview: <span style={{ fontWeight: value as number }}>The quick brown fox</span>,
51
+ }))}
52
+ />
53
+ </FoundationPage>
54
+ ),
55
+ }