@forumone/throughline-reference-ds 0.0.2 → 0.3.0

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 (185) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +102 -28
  4. package/dist/components/CTASection/CTASection.contract.d.ts +3 -0
  5. package/dist/components/CTASection/CTASection.contract.d.ts.map +1 -0
  6. package/dist/components/CTASection/CTASection.contract.js +117 -0
  7. package/dist/components/CTASection/CTASection.contract.js.map +1 -0
  8. package/dist/components/CTASection/CTASection.d.ts +17 -0
  9. package/dist/components/CTASection/CTASection.d.ts.map +1 -0
  10. package/dist/components/CTASection/CTASection.js +7 -0
  11. package/dist/components/CTASection/CTASection.js.map +1 -0
  12. package/dist/components/CTASection/index.d.ts +3 -0
  13. package/dist/components/CTASection/index.d.ts.map +1 -0
  14. package/dist/components/CTASection/index.js +3 -0
  15. package/dist/components/CTASection/index.js.map +1 -0
  16. package/dist/components/Card/Card.contract.d.ts +3 -0
  17. package/dist/components/Card/Card.contract.d.ts.map +1 -0
  18. package/dist/components/Card/Card.contract.js +111 -0
  19. package/dist/components/Card/Card.contract.js.map +1 -0
  20. package/dist/components/Card/Card.d.ts +16 -0
  21. package/dist/components/Card/Card.d.ts.map +1 -0
  22. package/dist/components/Card/Card.js +11 -0
  23. package/dist/components/Card/Card.js.map +1 -0
  24. package/dist/components/Card/index.d.ts +3 -0
  25. package/dist/components/Card/index.d.ts.map +1 -0
  26. package/dist/components/Card/index.js +3 -0
  27. package/dist/components/Card/index.js.map +1 -0
  28. package/dist/components/CardGrid/CardGrid.contract.d.ts +3 -0
  29. package/dist/components/CardGrid/CardGrid.contract.d.ts.map +1 -0
  30. package/dist/components/CardGrid/CardGrid.contract.js +67 -0
  31. package/dist/components/CardGrid/CardGrid.contract.js.map +1 -0
  32. package/dist/components/CardGrid/CardGrid.d.ts +9 -0
  33. package/dist/components/CardGrid/CardGrid.d.ts.map +1 -0
  34. package/dist/components/CardGrid/CardGrid.js +7 -0
  35. package/dist/components/CardGrid/CardGrid.js.map +1 -0
  36. package/dist/components/CardGrid/index.d.ts +3 -0
  37. package/dist/components/CardGrid/index.d.ts.map +1 -0
  38. package/dist/components/CardGrid/index.js +3 -0
  39. package/dist/components/CardGrid/index.js.map +1 -0
  40. package/dist/components/Divider/Divider.contract.d.ts +3 -0
  41. package/dist/components/Divider/Divider.contract.d.ts.map +1 -0
  42. package/dist/components/Divider/Divider.contract.js +77 -0
  43. package/dist/components/Divider/Divider.contract.js.map +1 -0
  44. package/dist/components/Divider/Divider.d.ts +8 -0
  45. package/dist/components/Divider/Divider.d.ts.map +1 -0
  46. package/dist/components/Divider/Divider.js +10 -0
  47. package/dist/components/Divider/Divider.js.map +1 -0
  48. package/dist/components/Divider/index.d.ts +3 -0
  49. package/dist/components/Divider/index.d.ts.map +1 -0
  50. package/dist/components/Divider/index.js +3 -0
  51. package/dist/components/Divider/index.js.map +1 -0
  52. package/dist/components/FAQ/FAQ.contract.d.ts +3 -0
  53. package/dist/components/FAQ/FAQ.contract.d.ts.map +1 -0
  54. package/dist/components/FAQ/FAQ.contract.js +93 -0
  55. package/dist/components/FAQ/FAQ.contract.js.map +1 -0
  56. package/dist/components/FAQ/FAQ.d.ts +14 -0
  57. package/dist/components/FAQ/FAQ.d.ts.map +1 -0
  58. package/dist/components/FAQ/FAQ.js +7 -0
  59. package/dist/components/FAQ/FAQ.js.map +1 -0
  60. package/dist/components/FAQ/index.d.ts +3 -0
  61. package/dist/components/FAQ/index.d.ts.map +1 -0
  62. package/dist/components/FAQ/index.js +3 -0
  63. package/dist/components/FAQ/index.js.map +1 -0
  64. package/dist/components/Hero/Hero.contract.d.ts +3 -0
  65. package/dist/components/Hero/Hero.contract.d.ts.map +1 -0
  66. package/dist/components/Hero/Hero.contract.js +171 -0
  67. package/dist/components/Hero/Hero.contract.js.map +1 -0
  68. package/dist/components/Hero/Hero.d.ts +24 -0
  69. package/dist/components/Hero/Hero.d.ts.map +1 -0
  70. package/dist/components/Hero/Hero.js +7 -0
  71. package/dist/components/Hero/Hero.js.map +1 -0
  72. package/dist/components/Hero/index.d.ts +3 -0
  73. package/dist/components/Hero/index.d.ts.map +1 -0
  74. package/dist/components/Hero/index.js +3 -0
  75. package/dist/components/Hero/index.js.map +1 -0
  76. package/dist/components/MediaBlock/MediaBlock.contract.d.ts +3 -0
  77. package/dist/components/MediaBlock/MediaBlock.contract.d.ts.map +1 -0
  78. package/dist/components/MediaBlock/MediaBlock.contract.js +83 -0
  79. package/dist/components/MediaBlock/MediaBlock.contract.js.map +1 -0
  80. package/dist/components/MediaBlock/MediaBlock.d.ts +23 -0
  81. package/dist/components/MediaBlock/MediaBlock.d.ts.map +1 -0
  82. package/dist/components/MediaBlock/MediaBlock.js +8 -0
  83. package/dist/components/MediaBlock/MediaBlock.js.map +1 -0
  84. package/dist/components/MediaBlock/index.d.ts +3 -0
  85. package/dist/components/MediaBlock/index.d.ts.map +1 -0
  86. package/dist/components/MediaBlock/index.js +3 -0
  87. package/dist/components/MediaBlock/index.js.map +1 -0
  88. package/dist/components/Prose/Prose.contract.d.ts +3 -0
  89. package/dist/components/Prose/Prose.contract.d.ts.map +1 -0
  90. package/dist/components/Prose/Prose.contract.js +95 -0
  91. package/dist/components/Prose/Prose.contract.js.map +1 -0
  92. package/dist/components/Prose/Prose.d.ts +9 -0
  93. package/dist/components/Prose/Prose.d.ts.map +1 -0
  94. package/dist/components/Prose/Prose.js +7 -0
  95. package/dist/components/Prose/Prose.js.map +1 -0
  96. package/dist/components/Prose/index.d.ts +3 -0
  97. package/dist/components/Prose/index.d.ts.map +1 -0
  98. package/dist/components/Prose/index.js +3 -0
  99. package/dist/components/Prose/index.js.map +1 -0
  100. package/dist/components/Quote/Quote.contract.d.ts +3 -0
  101. package/dist/components/Quote/Quote.contract.d.ts.map +1 -0
  102. package/dist/components/Quote/Quote.contract.js +90 -0
  103. package/dist/components/Quote/Quote.contract.js.map +1 -0
  104. package/dist/components/Quote/Quote.d.ts +16 -0
  105. package/dist/components/Quote/Quote.d.ts.map +1 -0
  106. package/dist/components/Quote/Quote.js +7 -0
  107. package/dist/components/Quote/Quote.js.map +1 -0
  108. package/dist/components/Quote/index.d.ts +3 -0
  109. package/dist/components/Quote/index.d.ts.map +1 -0
  110. package/dist/components/Quote/index.js +3 -0
  111. package/dist/components/Quote/index.js.map +1 -0
  112. package/dist/components/SectionIntro/SectionIntro.contract.d.ts +3 -0
  113. package/dist/components/SectionIntro/SectionIntro.contract.d.ts.map +1 -0
  114. package/dist/components/SectionIntro/SectionIntro.contract.js +97 -0
  115. package/dist/components/SectionIntro/SectionIntro.contract.js.map +1 -0
  116. package/dist/components/SectionIntro/SectionIntro.d.ts +10 -0
  117. package/dist/components/SectionIntro/SectionIntro.d.ts.map +1 -0
  118. package/dist/components/SectionIntro/SectionIntro.js +7 -0
  119. package/dist/components/SectionIntro/SectionIntro.js.map +1 -0
  120. package/dist/components/SectionIntro/index.d.ts +3 -0
  121. package/dist/components/SectionIntro/index.d.ts.map +1 -0
  122. package/dist/components/SectionIntro/index.js +3 -0
  123. package/dist/components/SectionIntro/index.js.map +1 -0
  124. package/dist/components/Spacer/Spacer.contract.d.ts +3 -0
  125. package/dist/components/Spacer/Spacer.contract.d.ts.map +1 -0
  126. package/dist/components/Spacer/Spacer.contract.js +66 -0
  127. package/dist/components/Spacer/Spacer.contract.js.map +1 -0
  128. package/dist/components/Spacer/Spacer.d.ts +7 -0
  129. package/dist/components/Spacer/Spacer.d.ts.map +1 -0
  130. package/dist/components/Spacer/Spacer.js +7 -0
  131. package/dist/components/Spacer/Spacer.js.map +1 -0
  132. package/dist/components/Spacer/index.d.ts +3 -0
  133. package/dist/components/Spacer/index.d.ts.map +1 -0
  134. package/dist/components/Spacer/index.js +3 -0
  135. package/dist/components/Spacer/index.js.map +1 -0
  136. package/dist/components/Stats/Stats.contract.d.ts +3 -0
  137. package/dist/components/Stats/Stats.contract.d.ts.map +1 -0
  138. package/dist/components/Stats/Stats.contract.js +92 -0
  139. package/dist/components/Stats/Stats.contract.js.map +1 -0
  140. package/dist/components/Stats/Stats.d.ts +13 -0
  141. package/dist/components/Stats/Stats.d.ts.map +1 -0
  142. package/dist/components/Stats/Stats.js +7 -0
  143. package/dist/components/Stats/Stats.js.map +1 -0
  144. package/dist/components/Stats/index.d.ts +3 -0
  145. package/dist/components/Stats/index.d.ts.map +1 -0
  146. package/dist/components/Stats/index.js +3 -0
  147. package/dist/components/Stats/index.js.map +1 -0
  148. package/dist/foundations/_helpers.d.ts +16 -0
  149. package/dist/foundations/_helpers.d.ts.map +1 -0
  150. package/dist/foundations/_helpers.js +31 -0
  151. package/dist/foundations/_helpers.js.map +1 -0
  152. package/dist/index.d.ts +14 -0
  153. package/dist/index.d.ts.map +1 -0
  154. package/dist/index.js +14 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/manifest.json +2059 -0
  157. package/dist/styles/base.css +55 -0
  158. package/dist/styles/index.css +224 -0
  159. package/dist/styles/reset.css +48 -0
  160. package/dist/styles/tokens.css +116 -0
  161. package/dist/tokens/colors.d.ts +48 -0
  162. package/dist/tokens/colors.d.ts.map +1 -0
  163. package/dist/tokens/colors.js +47 -0
  164. package/dist/tokens/colors.js.map +1 -0
  165. package/dist/tokens/index.d.ts +110 -0
  166. package/dist/tokens/index.d.ts.map +1 -0
  167. package/dist/tokens/index.js +23 -0
  168. package/dist/tokens/index.js.map +1 -0
  169. package/dist/tokens/layout.d.ts +24 -0
  170. package/dist/tokens/layout.d.ts.map +1 -0
  171. package/dist/tokens/layout.js +23 -0
  172. package/dist/tokens/layout.js.map +1 -0
  173. package/dist/tokens/radii.d.ts +11 -0
  174. package/dist/tokens/radii.d.ts.map +1 -0
  175. package/dist/tokens/radii.js +10 -0
  176. package/dist/tokens/radii.js.map +1 -0
  177. package/dist/tokens/spacing.d.ts +26 -0
  178. package/dist/tokens/spacing.d.ts.map +1 -0
  179. package/dist/tokens/spacing.js +25 -0
  180. package/dist/tokens/spacing.js.map +1 -0
  181. package/dist/tokens/typography.d.ts +28 -0
  182. package/dist/tokens/typography.d.ts.map +1 -0
  183. package/dist/tokens/typography.js +27 -0
  184. package/dist/tokens/typography.js.map +1 -0
  185. package/package.json +88 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @forumone/throughline-reference-ds
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e994176: Add layout tokens and a Storybook "Foundations" section.
8
+
9
+ New `layout` tokens (container max-widths, gutter, page margin, breakpoints) emit as `--layout-*` CSS custom properties, filling the previous gap where the DS had no container/breakpoint scale. New Foundations stories — Colors, Typography, Spacing, Radii, Elevation, and **Layout & Containers** — document the tokens visually in Storybook.
10
+
11
+ ## 0.2.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#11](https://github.com/forumone/throughline/pull/11) [`b46635d`](https://github.com/forumone/throughline/commit/b46635d311d7172c0f6d439b9146f809f2ce2339) Thanks [@briangraves](https://github.com/briangraves)! - Initial release. Twelve components (Hero, SectionIntro, Prose, MediaBlock, Card, CardGrid, CTASection, Stats, FAQ, Quote, Divider, Spacer) with full `ComponentContract` metadata, Storybook stories, unit tests, and a generated manifest. Serves as the reference implementation for contract compliance, a test fixture for core packages, and a starting template for client projects without their own design system.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,119 @@
1
1
  # @forumone/throughline-reference-ds
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ A competent, brand-neutral reference design system that demonstrates contract compliance for Throughline. Twelve components, a CSS-variable token layer, a generated AI-ready manifest, and Storybook.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## What this package provides
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ Twelve components covering the common editorial surface:
8
8
 
9
- ## Purpose
9
+ | Component | Purpose |
10
+ | --- | --- |
11
+ | `Hero` | Page opener with headline, body, CTAs, optional media — three variants |
12
+ | `SectionIntro` | h2-level section opener with optional eyebrow and body |
13
+ | `Prose` | Typographic container for long-form rich-text content |
14
+ | `MediaBlock` | Single image or video with optional caption and aspect ratios |
15
+ | `Card` | Linked or static content card with image, title, description, link |
16
+ | `CardGrid` | 2/3/4-column responsive layout for Cards |
17
+ | `CTASection` | Page-bottom call to action with one or two buttons |
18
+ | `Stats` | 2–4 headline metrics as a semantic `<dl>` |
19
+ | `FAQ` | Disclosure-style Q&A using native `<details>`/`<summary>` |
20
+ | `Quote` | Pullquote or testimonial with optional attribution |
21
+ | `Divider` | Decorative or semantic horizontal rule |
22
+ | `Spacer` | Explicit token-sized vertical spacing |
10
23
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-reference-ds`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
24
+ Every component ships a `ComponentContract` that conforms to `@forumone/throughline-design-contract@^0.2.0`. The package exposes the aggregated manifest at the `./manifest` subpath so any MCP server or client app can read it directly.
15
25
 
16
- ## What is OIDC Trusted Publishing?
26
+ ## Three ways to use this package
17
27
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
28
+ **As a starting point.** Copy components into your own design system. Customize freely; keep what works.
19
29
 
20
- ## Setup Instructions
30
+ **As a test fixture.** Core framework packages (Component Server, Publishing Server) consume this DS to verify their behaviour against a realistic, contract-compliant design system.
21
31
 
22
- To properly configure OIDC trusted publishing for this package:
32
+ **As a demo.** Stand up a Throughline site in minutes using only these twelve components — useful for prototypes, internal tools, and pitches.
23
33
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
34
+ ## Installation
28
35
 
29
- ## DO NOT USE THIS PACKAGE
36
+ ```bash
37
+ pnpm add @forumone/throughline-reference-ds
38
+ ```
30
39
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
40
+ Peers: `react` + `react-dom` 18 or 19.
36
41
 
37
- ## More Information
42
+ ## Using components
38
43
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
44
+ ```tsx
45
+ import '@forumone/throughline-reference-ds/styles.css'
46
+ import { Hero, CardGrid, Card } from '@forumone/throughline-reference-ds'
42
47
 
43
- ---
48
+ export function HomePage() {
49
+ return (
50
+ <>
51
+ <Hero
52
+ eyebrow="New program"
53
+ headline="Fellowship for climate researchers"
54
+ body="A one-year fellowship supporting researchers at the intersection of climate and community resilience."
55
+ cta={{ label: 'Apply now', url: '/apply' }}
56
+ />
57
+ <CardGrid columns={3}>
58
+ <Card title="Research" description="…" link={{ label: 'Read', url: '#' }} />
59
+ <Card title="Partnerships" description="…" link={{ label: 'Read', url: '#' }} />
60
+ <Card title="Funding" description="…" link={{ label: 'Read', url: '#' }} />
61
+ </CardGrid>
62
+ </>
63
+ )
64
+ }
65
+ ```
44
66
 
45
- **Maintained for OIDC setup purposes only**
67
+ ## Reading the manifest
68
+
69
+ The aggregated manifest ships at the `./manifest` subpath. Component servers, lint tooling, and documentation sites consume it:
70
+
71
+ ```ts
72
+ import manifest from '@forumone/throughline-reference-ds/manifest'
73
+ import { loadManifest } from '@forumone/throughline-design-contract'
74
+
75
+ const loaded = loadManifest(manifest)
76
+ console.log(loaded.listComponents())
77
+ // ['Card', 'CardGrid', 'CTASection', 'Divider', 'FAQ', 'Hero', 'MediaBlock', 'Prose', 'Quote', 'SectionIntro', 'Spacer', 'Stats']
78
+ ```
79
+
80
+ ## Theming via CSS variables
81
+
82
+ Every token is exposed as a CSS variable on `:root`. Override them to rebrand without touching component code:
83
+
84
+ ```css
85
+ :root {
86
+ --color-brand-primary: #7E33FF;
87
+ --color-brand-primary-hover: #6420CC;
88
+ --font-family-sans: 'DM Sans', system-ui, sans-serif;
89
+ }
90
+ ```
91
+
92
+ The package also ships a `prefers-color-scheme: dark` override that swaps background, text, and border tokens. Clients with more elaborate dark-mode requirements should override the entire token layer.
93
+
94
+ Full token list: `src/styles/tokens.css` (generated from `src/tokens/*.ts` via `pnpm build:tokens-css`).
95
+
96
+ ## Storybook
97
+
98
+ ```bash
99
+ pnpm --filter @forumone/throughline-reference-ds storybook # dev server on :6006
100
+ pnpm --filter @forumone/throughline-reference-ds build-storybook # produces storybook-static/
101
+ ```
102
+
103
+ The static build is used by `pnpm validate` to verify that every `storyId` declared on a contract resolves to a real Storybook story.
104
+
105
+ ## CI validation
106
+
107
+ ```bash
108
+ pnpm --filter @forumone/throughline-reference-ds build
109
+ pnpm --filter @forumone/throughline-reference-ds build-storybook
110
+ pnpm --filter @forumone/throughline-reference-ds validate
111
+ ```
112
+
113
+ `validate` runs `lintManifest` from `@forumone/throughline-design-contract/lint` against the generated manifest. Errors fail the build; warnings print but pass.
114
+
115
+ ## Authoring your own design system
116
+
117
+ Use this package as a reference. Each component has a `.contract.ts` file; every contract satisfies `ComponentContractSchema`. Copy the structure, replace the content, and your design system becomes a valid input to Throughline.
118
+
119
+ The authoring guide lives at `docs/building-plugins.md` (for MCP plugins) and the contract authoring expectations live in `@forumone/throughline-design-contract`'s README.
@@ -0,0 +1,3 @@
1
+ import type { ComponentContract } from '@forumone/throughline-design-contract';
2
+ export declare const contract: ComponentContract;
3
+ //# sourceMappingURL=CTASection.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CTASection.contract.d.ts","sourceRoot":"","sources":["../../../src/components/CTASection/CTASection.contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAE9E,eAAO,MAAM,QAAQ,EAAE,iBA6HtB,CAAA"}
@@ -0,0 +1,117 @@
1
+ export const contract = {
2
+ name: 'CTASection',
3
+ category: 'cta',
4
+ description: 'A page-bottom call to action with a headline, optional supporting copy, and one or two buttons.',
5
+ intent: 'Close a page with a clear next step. Use at the end of program pages, campaigns, and marketing pages where the reader has enough context to act. Do not use at the top of a page (that is Hero\'s job) or as a section opener (that is SectionIntro\'s).',
6
+ composition: {
7
+ placement: ['page', 'section'],
8
+ maxPerPage: 2,
9
+ requiredSiblings: [],
10
+ forbiddenAdjacent: ['CTASection'],
11
+ },
12
+ content: {
13
+ fields: [
14
+ {
15
+ name: 'headline',
16
+ type: 'text',
17
+ required: true,
18
+ maxLength: 100,
19
+ constraints: 'Direct, imperative; reads like an invitation',
20
+ },
21
+ {
22
+ name: 'body',
23
+ type: 'text',
24
+ required: false,
25
+ maxLength: 200,
26
+ constraints: 'One sentence that reduces friction to clicking',
27
+ },
28
+ {
29
+ name: 'cta',
30
+ type: 'group',
31
+ required: true,
32
+ of: [
33
+ { name: 'label', type: 'text', required: true, maxLength: 30 },
34
+ { name: 'url', type: 'link', required: true },
35
+ ],
36
+ },
37
+ {
38
+ name: 'secondaryCta',
39
+ type: 'group',
40
+ required: false,
41
+ of: [
42
+ { name: 'label', type: 'text', required: true, maxLength: 30 },
43
+ { name: 'url', type: 'link', required: true },
44
+ ],
45
+ },
46
+ ],
47
+ variants: [
48
+ { name: 'primary', description: 'Standard background', whenToUse: 'Default' },
49
+ { name: 'secondary', description: 'Subtle grey background', whenToUse: 'Most page closes — less visual weight than Hero' },
50
+ { name: 'inverse', description: 'Dark background', whenToUse: 'Campaign pages or high-stakes calls to action' },
51
+ ],
52
+ },
53
+ tokens: {
54
+ consumes: [
55
+ 'color.bg.primary',
56
+ 'color.bg.secondary',
57
+ 'color.bg.inverse',
58
+ 'color.text.primary',
59
+ 'color.text.secondary',
60
+ 'color.text.inverse',
61
+ 'color.brand.primary',
62
+ 'color.brand.primaryHover',
63
+ 'color.border.strong',
64
+ 'spacing.section',
65
+ 'spacing.container',
66
+ 'spacing.8',
67
+ 'spacing.6',
68
+ 'spacing.4',
69
+ 'spacing.3',
70
+ 'font.size.4xl',
71
+ 'font.size.lg',
72
+ 'font.weight.bold',
73
+ 'font.weight.semibold',
74
+ 'line.height.tight',
75
+ 'line.height.relaxed',
76
+ 'radius.md',
77
+ ],
78
+ configurable: [
79
+ {
80
+ prop: 'background',
81
+ tokenGroup: 'color.bg',
82
+ allowedValues: ['primary', 'secondary', 'inverse'],
83
+ },
84
+ ],
85
+ },
86
+ accessibility: {
87
+ keyboardSupport: ['Tab to primary CTA', 'Tab to secondary CTA'],
88
+ screenReaderBehavior: 'The headline is announced as an h2. CTAs are standard links; their label text is announced as the link name.',
89
+ contentWarnings: [
90
+ 'Avoid verb-only labels ("Click", "Go") — include the destination ("Start building", "Read the docs")',
91
+ ],
92
+ },
93
+ examples: [
94
+ { label: 'Default', intent: 'Two-button page close', storyId: 'cta-section--default' },
95
+ { label: 'Primary only', intent: 'Single-action CTA', storyId: 'cta-section--primary-only' },
96
+ { label: 'Inverse', intent: 'High-contrast campaign CTA', storyId: 'cta-section--inverse' },
97
+ ],
98
+ antiExamples: [
99
+ {
100
+ label: 'Three or more CTAs',
101
+ why: 'Too many options reduces conversion; pick the top one, put the rest on a detail page',
102
+ useInstead: 'One primary + optional secondary',
103
+ },
104
+ {
105
+ label: 'Using CTASection as a page opener',
106
+ why: 'Readers cannot convert without context; CTA belongs at the end',
107
+ useInstead: 'Hero',
108
+ },
109
+ ],
110
+ behavior: {
111
+ fetchesData: false,
112
+ hasClientState: false,
113
+ animates: false,
114
+ requiresAnalytics: true,
115
+ },
116
+ };
117
+ //# sourceMappingURL=CTASection.contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CTASection.contract.js","sourceRoot":"","sources":["../../../src/components/CTASection/CTASection.contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export type CTASectionBackground = 'primary' | 'secondary' | 'inverse';
2
+ export interface CTASectionProps {
3
+ headline: string;
4
+ body?: string;
5
+ cta: {
6
+ label: string;
7
+ url: string;
8
+ };
9
+ secondaryCta?: {
10
+ label: string;
11
+ url: string;
12
+ };
13
+ background?: CTASectionBackground;
14
+ className?: string;
15
+ }
16
+ export declare function CTASection({ headline, body, cta, secondaryCta, background, className, }: CTASectionProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=CTASection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CTASection.d.ts","sourceRoot":"","sources":["../../../src/components/CTASection/CTASection.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAA;AAEtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,UAAU,CAAC,EAAE,oBAAoB,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,IAAI,EACJ,GAAG,EACH,YAAY,EACZ,UAAwB,EACxB,SAAS,GACV,EAAE,eAAe,2CAmBjB"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from 'clsx';
3
+ import styles from './CTASection.module.css';
4
+ export function CTASection({ headline, body, cta, secondaryCta, background = 'secondary', className, }) {
5
+ return (_jsx("section", { className: clsx(styles.cta, styles[`bg-${background}`], className), children: _jsxs("div", { className: styles.container, children: [_jsx("h2", { className: styles.headline, children: headline }), body ? _jsx("p", { className: styles.body, children: body }) : null, _jsxs("div", { className: styles.actions, children: [_jsx("a", { href: cta.url, className: styles.primary, children: cta.label }), secondaryCta ? (_jsx("a", { href: secondaryCta.url, className: styles.secondary, children: secondaryCta.label })) : null] })] }) }));
6
+ }
7
+ //# sourceMappingURL=CTASection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CTASection.js","sourceRoot":"","sources":["../../../src/components/CTASection/CTASection.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,MAAM,MAAM,yBAAyB,CAAA;AAa5C,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,IAAI,EACJ,GAAG,EACH,YAAY,EACZ,UAAU,GAAG,WAAW,EACxB,SAAS,GACO;IAChB,OAAO,CACL,kBAAS,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,UAAU,EAAE,CAAC,EAAE,SAAS,CAAC,YACzE,eAAK,SAAS,EAAE,MAAM,CAAC,SAAS,aAC9B,aAAI,SAAS,EAAE,MAAM,CAAC,QAAQ,YAAG,QAAQ,GAAM,EAC9C,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAE,MAAM,CAAC,IAAI,YAAG,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EACpD,eAAK,SAAS,EAAE,MAAM,CAAC,OAAO,aAC5B,YAAG,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,YACxC,GAAG,CAAC,KAAK,GACR,EACH,YAAY,CAAC,CAAC,CAAC,CACd,YAAG,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,YACnD,YAAY,CAAC,KAAK,GACjB,CACL,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,GACE,CACX,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { CTASection, type CTASectionProps, type CTASectionBackground } from './CTASection.js';
2
+ export { contract as ctaSectionContract } from './CTASection.contract.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/CTASection/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC7F,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { CTASection } from './CTASection.js';
2
+ export { contract as ctaSectionContract } from './CTASection.contract.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/CTASection/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmD,MAAM,iBAAiB,CAAA;AAC7F,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { ComponentContract } from '@forumone/throughline-design-contract';
2
+ export declare const contract: ComponentContract;
3
+ //# sourceMappingURL=Card.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.contract.d.ts","sourceRoot":"","sources":["../../../src/components/Card/Card.contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAE9E,eAAO,MAAM,QAAQ,EAAE,iBAuHtB,CAAA"}
@@ -0,0 +1,111 @@
1
+ export const contract = {
2
+ name: 'Card',
3
+ category: 'card',
4
+ description: 'A single content card with optional image, eyebrow, title, description, and link. Renders as a link when a link target is provided.',
5
+ intent: 'Surface a single piece of content in a scannable, consistent shape — a program page, a blog post, a case study, a staff bio. Cards are typically laid out in a CardGrid, but a single Card can appear alone inside a section when the content warrants singular emphasis.',
6
+ composition: {
7
+ placement: ['section', 'inline'],
8
+ maxPerPage: null,
9
+ requiredSiblings: [],
10
+ forbiddenAdjacent: [],
11
+ },
12
+ content: {
13
+ fields: [
14
+ {
15
+ name: 'eyebrow',
16
+ type: 'text',
17
+ required: false,
18
+ maxLength: 30,
19
+ constraints: 'Short label such as the content type ("Case study", "Program update")',
20
+ },
21
+ {
22
+ name: 'image',
23
+ type: 'image',
24
+ required: false,
25
+ constraints: 'Landscape orientation; framing works best for images that read at small sizes',
26
+ },
27
+ {
28
+ name: 'title',
29
+ type: 'text',
30
+ required: true,
31
+ maxLength: 100,
32
+ constraints: 'Sentence case; reads like a headline',
33
+ },
34
+ {
35
+ name: 'description',
36
+ type: 'text',
37
+ required: false,
38
+ maxLength: 200,
39
+ constraints: 'One to two sentences that give the reader a reason to click',
40
+ },
41
+ {
42
+ name: 'link',
43
+ type: 'group',
44
+ required: false,
45
+ of: [
46
+ { name: 'label', type: 'text', required: true, maxLength: 30 },
47
+ { name: 'url', type: 'link', required: true },
48
+ ],
49
+ },
50
+ ],
51
+ variants: [
52
+ { name: 'linked', description: 'Entire card is clickable', whenToUse: 'Teaser for a single piece of content' },
53
+ { name: 'static', description: 'No link; presentational only', whenToUse: 'Inline informational cards (team bios, program descriptions) that do not link elsewhere' },
54
+ ],
55
+ },
56
+ tokens: {
57
+ consumes: [
58
+ 'color.bg.primary',
59
+ 'color.bg.tertiary',
60
+ 'color.text.primary',
61
+ 'color.text.secondary',
62
+ 'color.border.default',
63
+ 'color.border.strong',
64
+ 'color.brand.primary',
65
+ 'spacing.6',
66
+ 'spacing.2',
67
+ 'font.size.xl',
68
+ 'font.size.base',
69
+ 'font.size.sm',
70
+ 'font.size.xs',
71
+ 'font.weight.semibold',
72
+ 'line.height.snug',
73
+ 'line.height.normal',
74
+ 'letter.spacing.wide',
75
+ 'radius.lg',
76
+ ],
77
+ },
78
+ accessibility: {
79
+ keyboardSupport: ['When linked, the whole card is a single tab stop'],
80
+ screenReaderBehavior: 'Linked cards render as a single <a> element announcing the title + description + link label as one unit. Static cards render as <article> with an h3 title.',
81
+ contentWarnings: [
82
+ 'Avoid nested interactive elements inside a linked card (buttons, additional links)',
83
+ 'Always author alt text for card images or omit the image entirely',
84
+ ],
85
+ },
86
+ examples: [
87
+ { label: 'Default', intent: 'Content teaser with image and link', storyId: 'card--default' },
88
+ { label: 'With eyebrow', intent: 'Labeled teaser highlighting content type', storyId: 'card--with-eyebrow' },
89
+ { label: 'No image', intent: 'Text-only teaser for resources or downloads', storyId: 'card--no-image' },
90
+ { label: 'Static', intent: 'Presentational card without a link target', storyId: 'card--static' },
91
+ ],
92
+ antiExamples: [
93
+ {
94
+ label: 'Nesting interactive controls inside a linked card',
95
+ why: 'A linked card is a single tab stop; adding buttons or additional links creates nested interactive regions that confuse assistive technology',
96
+ useInstead: 'Use a static card with explicit nested controls, or move secondary actions to a detail page',
97
+ },
98
+ {
99
+ label: 'Using Card as a navigation menu item',
100
+ why: 'Cards are for content teasers; site navigation belongs in a Menu or Nav component',
101
+ useInstead: 'A dedicated navigation component',
102
+ },
103
+ ],
104
+ behavior: {
105
+ fetchesData: false,
106
+ hasClientState: false,
107
+ animates: false,
108
+ requiresAnalytics: false,
109
+ },
110
+ };
111
+ //# sourceMappingURL=Card.contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.contract.js","sourceRoot":"","sources":["../../../src/components/Card/Card.contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface CardProps {
2
+ title: string;
3
+ description?: string;
4
+ image?: {
5
+ url: string;
6
+ alt: string;
7
+ };
8
+ link?: {
9
+ label: string;
10
+ url: string;
11
+ };
12
+ eyebrow?: string;
13
+ className?: string;
14
+ }
15
+ export declare function Card({ title, description, image, link, eyebrow, className }: CardProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=Card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../../src/components/Card/Card.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,2CA0BtF"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { clsx } from 'clsx';
3
+ import styles from './Card.module.css';
4
+ export function Card({ title, description, image, link, eyebrow, className }) {
5
+ const body = (_jsxs(_Fragment, { children: [image ? (_jsx("div", { className: styles.imageFrame, children: _jsx("img", { src: image.url, alt: image.alt, className: styles.image }) })) : null, _jsxs("div", { className: styles.content, children: [eyebrow ? _jsx("p", { className: styles.eyebrow, children: eyebrow }) : null, _jsx("h3", { className: styles.title, children: title }), description ? _jsx("p", { className: styles.description, children: description }) : null, link ? _jsx("span", { className: styles.linkLabel, children: link.label }) : null] })] }));
6
+ if (link) {
7
+ return (_jsx("a", { href: link.url, className: clsx(styles.card, styles.linked, className), children: body }));
8
+ }
9
+ return _jsx("article", { className: clsx(styles.card, className), children: body });
10
+ }
11
+ //# sourceMappingURL=Card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Card.js","sourceRoot":"","sources":["../../../src/components/Card/Card.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,MAAM,MAAM,mBAAmB,CAAA;AAWtC,MAAM,UAAU,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAa;IACrF,MAAM,IAAI,GAAG,CACX,8BACG,KAAK,CAAC,CAAC,CAAC,CACP,cAAK,SAAS,EAAE,MAAM,CAAC,UAAU,YAC/B,cAAK,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,GAAI,GAC5D,CACP,CAAC,CAAC,CAAC,IAAI,EACR,eAAK,SAAS,EAAE,MAAM,CAAC,OAAO,aAC3B,OAAO,CAAC,CAAC,CAAC,YAAG,SAAS,EAAE,MAAM,CAAC,OAAO,YAAG,OAAO,GAAK,CAAC,CAAC,CAAC,IAAI,EAC7D,aAAI,SAAS,EAAE,MAAM,CAAC,KAAK,YAAG,KAAK,GAAM,EACxC,WAAW,CAAC,CAAC,CAAC,YAAG,SAAS,EAAE,MAAM,CAAC,WAAW,YAAG,WAAW,GAAK,CAAC,CAAC,CAAC,IAAI,EACxE,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAE,MAAM,CAAC,SAAS,YAAG,IAAI,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,IACjE,IACL,CACJ,CAAA;IAED,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CACL,YAAG,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,YACtE,IAAI,GACH,CACL,CAAA;IACH,CAAC;IAED,OAAO,kBAAS,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,YAAG,IAAI,GAAW,CAAA;AAC3E,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Card, type CardProps } from './Card.js';
2
+ export { contract as cardContract } from './Card.contract.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { Card } from './Card.js';
2
+ export { contract as cardContract } from './Card.contract.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/Card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAkB,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { ComponentContract } from '@forumone/throughline-design-contract';
2
+ export declare const contract: ComponentContract;
3
+ //# sourceMappingURL=CardGrid.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CardGrid.contract.d.ts","sourceRoot":"","sources":["../../../src/components/CardGrid/CardGrid.contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAE9E,eAAO,MAAM,QAAQ,EAAE,iBA2EtB,CAAA"}
@@ -0,0 +1,67 @@
1
+ export const contract = {
2
+ name: 'CardGrid',
3
+ category: 'section',
4
+ description: 'A responsive layout container for two, three, or four Cards per row. Reflows to fewer columns on narrow viewports.',
5
+ intent: 'Lay out multiple Card components inside a section. Use for program lists, blog indexes, staff directories, and any collection of similarly-shaped content. CardGrid is purely layout; it does not fetch data or filter — authors list the Cards they want.',
6
+ composition: {
7
+ placement: ['section'],
8
+ maxPerPage: null,
9
+ requiredSiblings: ['Card'],
10
+ forbiddenAdjacent: [],
11
+ allowedSlots: { children: ['Card'] },
12
+ },
13
+ content: {
14
+ fields: [
15
+ {
16
+ name: 'children',
17
+ type: 'array',
18
+ required: true,
19
+ constraints: 'An array of Card components',
20
+ of: [{ name: 'card', type: 'group', required: true }],
21
+ },
22
+ ],
23
+ variants: [
24
+ { name: '2', description: 'Two columns', whenToUse: 'Lists of 2-4 prominent items' },
25
+ { name: '3', description: 'Three columns', whenToUse: 'Default — works for most collections' },
26
+ { name: '4', description: 'Four columns', whenToUse: 'Denser lists of 8 or more short cards' },
27
+ ],
28
+ },
29
+ tokens: {
30
+ consumes: ['spacing.container', 'spacing.6'],
31
+ configurable: [
32
+ { prop: 'columns', tokenGroup: 'layout', allowedValues: ['2', '3', '4'] },
33
+ ],
34
+ },
35
+ accessibility: {
36
+ keyboardSupport: [],
37
+ screenReaderBehavior: 'Purely visual layout. Screen readers traverse children in source order; CardGrid itself adds no ARIA.',
38
+ contentWarnings: [
39
+ 'Children must be Cards; mixing other components breaks the visual rhythm',
40
+ 'Keep all cards roughly the same content length — dramatically unequal cards look broken',
41
+ ],
42
+ },
43
+ examples: [
44
+ { label: 'Two columns', intent: 'Featured program pair', storyId: 'card-grid--two-column' },
45
+ { label: 'Three columns', intent: 'Standard content index', storyId: 'card-grid--three-column' },
46
+ { label: 'Four columns', intent: 'Dense staff directory', storyId: 'card-grid--four-column' },
47
+ ],
48
+ antiExamples: [
49
+ {
50
+ label: 'Mixing Cards and non-Card children',
51
+ why: 'The grid assumes uniform children; mixing Stats or MediaBlocks produces broken columns',
52
+ useInstead: 'Group by type — one CardGrid, then the other components below',
53
+ },
54
+ {
55
+ label: 'Using CardGrid with a single Card',
56
+ why: 'A CardGrid wrapping one Card is overhead without benefit',
57
+ useInstead: 'Render the Card directly',
58
+ },
59
+ ],
60
+ behavior: {
61
+ fetchesData: false,
62
+ hasClientState: false,
63
+ animates: false,
64
+ requiresAnalytics: false,
65
+ },
66
+ };
67
+ //# sourceMappingURL=CardGrid.contract.js.map