@nationaldesignstudio/react 0.1.0 → 0.2.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 (27) hide show
  1. package/dist/component-registry.md +46 -19
  2. package/dist/components/atoms/accordion/accordion.d.ts +7 -7
  3. package/dist/components/sections/banner/banner.d.ts +9 -9
  4. package/dist/components/sections/faq-section/faq-section.d.ts +1 -1
  5. package/dist/components/sections/hero/hero.d.ts +115 -18
  6. package/dist/components/sections/tout/tout.d.ts +8 -8
  7. package/dist/components/sections/two-column-section/two-column-section.d.ts +7 -21
  8. package/dist/index.js +1212 -1188
  9. package/dist/index.js.map +1 -1
  10. package/dist/tokens.css +16 -16
  11. package/package.json +1 -1
  12. package/src/components/atoms/accordion/accordion.test.tsx +18 -20
  13. package/src/components/atoms/accordion/accordion.tsx +19 -17
  14. package/src/components/atoms/background/background.test.tsx +2 -2
  15. package/src/components/atoms/background/background.tsx +48 -29
  16. package/src/components/atoms/button/button.tsx +1 -1
  17. package/src/components/atoms/button/icon-button.tsx +51 -16
  18. package/src/components/organisms/card/card.test.tsx +4 -2
  19. package/src/components/sections/banner/banner.stories.tsx +5 -1
  20. package/src/components/sections/banner/banner.tsx +10 -10
  21. package/src/components/sections/faq-section/faq-section.stories.tsx +7 -7
  22. package/src/components/sections/faq-section/faq-section.tsx +3 -3
  23. package/src/components/sections/hero/hero.tsx +33 -51
  24. package/src/components/sections/tout/tout.stories.tsx +31 -7
  25. package/src/components/sections/tout/tout.tsx +6 -8
  26. package/src/components/sections/two-column-section/two-column-section.stories.tsx +11 -11
  27. package/src/components/sections/two-column-section/two-column-section.tsx +16 -10
@@ -41,7 +41,7 @@ export interface FaqSectionProps
41
41
  * </FaqSection>
42
42
  *
43
43
  * // With custom title
44
- * <FaqSection title="Common Questions" variant="light">
44
+ * <FaqSection title="Common Questions" colorScheme="light">
45
45
  * ...
46
46
  * </FaqSection>
47
47
  * ```
@@ -50,7 +50,7 @@ const FaqSection = React.forwardRef<HTMLElement, FaqSectionProps>(
50
50
  (
51
51
  {
52
52
  className,
53
- variant = "dark",
53
+ colorScheme = "light",
54
54
  title = "Frequently Asked Questions",
55
55
  children,
56
56
  layout,
@@ -61,7 +61,7 @@ const FaqSection = React.forwardRef<HTMLElement, FaqSectionProps>(
61
61
  return (
62
62
  <TwoColumnSection
63
63
  ref={ref}
64
- variant={variant}
64
+ colorScheme={colorScheme}
65
65
  layout={layout ?? undefined}
66
66
  title={title}
67
67
  className={cn(
@@ -18,56 +18,54 @@ export {
18
18
  type BackgroundVideoProps as HeroBackgroundVideoProps,
19
19
  } from "@/components/atoms/background";
20
20
 
21
- // =============================================================================
22
- // Hero Variants
23
- // =============================================================================
21
+ const DEFAULT_TITLE_TYPOGRAPHY =
22
+ "text-64 leading-64 tracking-64 md:text-128 md:leading-128 md:tracking-128 lg:text-192 lg:leading-192 lg:tracking-192 font-medium";
24
23
 
25
- /**
26
- * Hero variants based on Figma BaseKit / Heros
27
- *
28
- * Slots:
29
- * - root: The outer container
30
- * - top: Full-width slot at top for banners/nav (no padding)
31
- * - content: Padded content area with alignment
32
- *
33
- * Variants:
34
- * - A1: Content aligned at bottom (default)
35
- * - A2: Content aligned at top
36
- * - A3: Content centered
37
- */
38
24
  const heroVariants = tv({
39
25
  slots: {
40
26
  root: "relative flex w-full flex-col overflow-hidden",
41
27
  top: "relative z-10 w-full",
42
28
  content: [
43
29
  "relative z-10 mx-auto flex w-full max-w-screen-xl flex-1 flex-col",
44
- // Mobile padding
45
30
  "p-spacing-20",
46
- // Tablet padding
47
31
  "md:p-spacing-56",
48
32
  ],
33
+ title: DEFAULT_TITLE_TYPOGRAPHY,
49
34
  },
50
35
  variants: {
51
36
  variant: {
52
- // A1: Content at bottom
53
37
  A1: {
54
38
  root: "min-h-[80vh]",
55
39
  content: ["justify-end", "lg:p-spacing-72"],
56
40
  },
57
- // A2: Content at top
58
41
  A2: {
59
42
  root: "min-h-[80vh]",
60
43
  content: ["justify-start", "lg:p-spacing-64"],
61
44
  },
62
- // A3: Content centered
63
45
  A3: {
64
46
  root: "min-h-[80vh]",
65
47
  content: ["items-center justify-center", "lg:p-spacing-64"],
66
48
  },
67
49
  },
50
+ colorScheme: {
51
+ dark: {
52
+ root: "bg-bg-page",
53
+ title: "text-text-primary",
54
+ },
55
+ light: {
56
+ root: "bg-gray-900",
57
+ title: "text-text-inverted",
58
+ },
59
+ },
60
+ hasBackground: {
61
+ true: {
62
+ root: "bg-transparent",
63
+ },
64
+ },
68
65
  },
69
66
  defaultVariants: {
70
67
  variant: "A1",
68
+ colorScheme: "dark",
71
69
  },
72
70
  });
73
71
 
@@ -141,6 +139,12 @@ export interface HeroProps
141
139
  * Default: "text-64 leading-64 tracking-64 md:text-128 md:leading-128 md:tracking-128 lg:text-192 lg:leading-192 lg:tracking-192"
142
140
  */
143
141
  titleClassName?: string;
142
+ /**
143
+ * Color scheme for text content.
144
+ * - dark: Dark text for use on light backgrounds (default)
145
+ * - light: Light text for use on dark backgrounds
146
+ */
147
+ colorScheme?: "dark" | "light";
144
148
  /**
145
149
  * Content for the top slot (full-width, no padding).
146
150
  * Use for USGovBanner, Navigation, etc.
@@ -172,13 +176,6 @@ export interface HeroProps
172
176
  theme?: ComponentTheme;
173
177
  }
174
178
 
175
- /**
176
- * Default responsive typography for hero title using primitive tokens
177
- * Mobile: 64px, Tablet: 128px, Desktop: 192px
178
- */
179
- const DEFAULT_TITLE_TYPOGRAPHY =
180
- "text-64 leading-64 tracking-64 md:text-128 md:leading-128 md:tracking-128 lg:text-192 lg:leading-192 lg:tracking-192 font-medium";
181
-
182
179
  /**
183
180
  * Checks if the background prop is a color string
184
181
  */
@@ -238,6 +235,7 @@ const Hero = React.forwardRef<HTMLElement, HeroProps>(
238
235
  children,
239
236
  title,
240
237
  titleClassName,
238
+ colorScheme = "dark",
241
239
  top,
242
240
  variant,
243
241
  background,
@@ -250,9 +248,13 @@ const Hero = React.forwardRef<HTMLElement, HeroProps>(
250
248
  },
251
249
  ref,
252
250
  ) => {
253
- const styles = heroVariants({ variant });
254
251
  const isColor = isColorString(background);
255
252
  const hasMediaBackground = background && !isColor;
253
+ const styles = heroVariants({
254
+ variant,
255
+ colorScheme,
256
+ hasBackground: !!background,
257
+ });
256
258
  const themeStyles = themeToStyleVars(theme);
257
259
  const combinedStyles = {
258
260
  ...themeStyles,
@@ -264,22 +266,14 @@ const Hero = React.forwardRef<HTMLElement, HeroProps>(
264
266
  return (
265
267
  <section
266
268
  ref={ref}
267
- className={styles.root({
268
- class: [
269
- // Default background color when no background is provided
270
- !background && "bg-bg-overlay",
271
- className,
272
- ],
273
- })}
269
+ className={styles.root({ class: className })}
274
270
  style={
275
271
  Object.keys(combinedStyles).length > 0 ? combinedStyles : undefined
276
272
  }
277
273
  {...props}
278
274
  >
279
- {/* Background slot (image, video, or stream) */}
280
275
  {hasMediaBackground && background}
281
276
 
282
- {/* Overlay */}
283
277
  {hasMediaBackground && overlayOpacity > 0 && (
284
278
  <BackgroundOverlay
285
279
  opacity={overlayOpacity}
@@ -292,23 +286,11 @@ const Hero = React.forwardRef<HTMLElement, HeroProps>(
292
286
  />
293
287
  )}
294
288
 
295
- {/* Top slot - full width, no padding (for banners, nav) */}
296
289
  {top && <div className={styles.top()}>{top}</div>}
297
290
 
298
- {/* Content slot - padded area with alignment */}
299
291
  <div className={styles.content()}>
300
- {/* Title - convenience prop, renders h1 with default typography */}
301
292
  {title && (
302
- <h1
303
- className={[
304
- titleClassName || DEFAULT_TITLE_TYPOGRAPHY,
305
- "text-text-inverted",
306
- ]
307
- .filter(Boolean)
308
- .join(" ")}
309
- >
310
- {title}
311
- </h1>
293
+ <h1 className={styles.title({ class: titleClassName })}>{title}</h1>
312
294
  )}
313
295
 
314
296
  {/* Children - always render if provided */}
@@ -43,7 +43,11 @@ Playground.args = {
43
43
  headline: "Brand-Large/Headline/Small",
44
44
  body: "A river pattern stacks content in a simple vertical flow: one clear heading, a short block of copy, then the next step. It's ideal for guiding citizens through a process or story, keeping focus moving straight down the page with minimal choices and well-timed calls to action.",
45
45
  primaryAction: <Button>Primary</Button>,
46
- secondaryAction: <Button variant="charcoalOutline">Secondary</Button>,
46
+ secondaryAction: (
47
+ <Button variant="outline" colorScheme="light">
48
+ Secondary
49
+ </Button>
50
+ ),
47
51
  backgroundMedia: <PlaceholderBackground />,
48
52
  };
49
53
 
@@ -57,7 +61,11 @@ export const Desktop: Story = {
57
61
  headline="Brand-Large/Headline/Small"
58
62
  body="A river pattern stacks content in a simple vertical flow: one clear heading, a short block of copy, then the next step. It's ideal for guiding citizens through a process or story, keeping focus moving straight down the page with minimal choices and well-timed calls to action."
59
63
  primaryAction={<Button>Primary</Button>}
60
- secondaryAction={<Button variant="charcoalOutline">Secondary</Button>}
64
+ secondaryAction={
65
+ <Button variant="outline" colorScheme="light">
66
+ Secondary
67
+ </Button>
68
+ }
61
69
  backgroundMedia={<PlaceholderBackground />}
62
70
  />
63
71
  ),
@@ -72,7 +80,11 @@ export const Tablet: Story = {
72
80
  headline="Brand-Large/Headline/Small"
73
81
  body="A river pattern stacks content in a simple vertical flow: one clear heading, a short block of copy, then the next step. It's ideal for guiding citizens through a process or story, keeping focus moving straight down the page with minimal choices and well-timed calls to action."
74
82
  primaryAction={<Button>Primary</Button>}
75
- secondaryAction={<Button variant="charcoalOutline">Secondary</Button>}
83
+ secondaryAction={
84
+ <Button variant="outline" colorScheme="light">
85
+ Secondary
86
+ </Button>
87
+ }
76
88
  backgroundMedia={<PlaceholderBackground />}
77
89
  />
78
90
  ),
@@ -88,7 +100,7 @@ export const Mobile: Story = {
88
100
  body="A river pattern stacks content in a simple vertical flow: one clear heading, a short block of copy, then the next step. It's ideal for guiding citizens through a process or story, keeping focus moving straight down the page with minimal choices and well-timed calls to action."
89
101
  primaryAction={<Button size="sm">Primary</Button>}
90
102
  secondaryAction={
91
- <Button size="sm" variant="charcoalOutline">
103
+ <Button size="sm" variant="outline" colorScheme="light">
92
104
  Secondary
93
105
  </Button>
94
106
  }
@@ -113,7 +125,11 @@ export const WithImage: Story = {
113
125
  headline="Work with Purpose"
114
126
  body="Join a team that's building the future of government services. We're looking for passionate individuals who want to make a difference."
115
127
  primaryAction={<Button>View Careers</Button>}
116
- secondaryAction={<Button variant="charcoalOutline">Learn More</Button>}
128
+ secondaryAction={
129
+ <Button variant="outline" colorScheme="light">
130
+ Learn More
131
+ </Button>
132
+ }
117
133
  backgroundMedia={<ImageBackground />}
118
134
  />
119
135
  ),
@@ -142,7 +158,11 @@ export const WithVideoPlaceholder: Story = {
142
158
  headline="Experience Innovation"
143
159
  body="See how modern technology is transforming the way government serves its citizens."
144
160
  primaryAction={<Button>Watch Video</Button>}
145
- secondaryAction={<Button variant="charcoalOutline">Learn More</Button>}
161
+ secondaryAction={
162
+ <Button variant="outline" colorScheme="light">
163
+ Learn More
164
+ </Button>
165
+ }
146
166
  backgroundMedia={
147
167
  <div className="absolute inset-0 bg-gray-800 flex items-center justify-center">
148
168
  <span className="text-gray-400 typography-body-small">
@@ -163,7 +183,11 @@ export const WithNdstudioFooter: Story = {
163
183
  headline="Work with Purpose"
164
184
  body="Join a team that's building the future of government services. We're looking for passionate individuals who want to make a difference."
165
185
  primaryAction={<Button>View Careers</Button>}
166
- secondaryAction={<Button variant="charcoalOutline">Learn More</Button>}
186
+ secondaryAction={
187
+ <Button variant="outline" colorScheme="light">
188
+ Learn More
189
+ </Button>
190
+ }
167
191
  backgroundMedia={<ImageBackground />}
168
192
  footer={<NdstudioFooter />}
169
193
  />
@@ -16,10 +16,8 @@ const toutVariants = tv({
16
16
  "h-[600px] md:h-[750px] lg:h-[900px]",
17
17
  ],
18
18
  variants: {
19
- variant: {
20
- // Default light content styling
19
+ colorScheme: {
21
20
  light: "",
22
- // Dark content styling
23
21
  dark: "",
24
22
  },
25
23
  align: {
@@ -28,7 +26,7 @@ const toutVariants = tv({
28
26
  },
29
27
  },
30
28
  defaultVariants: {
31
- variant: "light",
29
+ colorScheme: "light",
32
30
  align: "left",
33
31
  },
34
32
  });
@@ -39,7 +37,7 @@ const toutVariants = tv({
39
37
  * A full-bleed section with a background image and overlaid content.
40
38
  * Content can be positioned on the left side or centered.
41
39
  *
42
- * Variants:
40
+ * Color schemes:
43
41
  * - light: Light text styling (default)
44
42
  * - dark: Dark text styling
45
43
  *
@@ -121,7 +119,7 @@ const Tout = React.forwardRef<HTMLElement, ToutProps>(
121
119
  (
122
120
  {
123
121
  className,
124
- variant = "light",
122
+ colorScheme = "light",
125
123
  align = "left",
126
124
  headline,
127
125
  body,
@@ -136,13 +134,13 @@ const Tout = React.forwardRef<HTMLElement, ToutProps>(
136
134
  ref,
137
135
  ) => {
138
136
  const isCentered = align === "center";
139
- const isDark = variant === "dark";
137
+ const isDark = colorScheme === "dark";
140
138
  const themeStyles = themeToStyleVars(theme);
141
139
 
142
140
  return (
143
141
  <section
144
142
  ref={ref}
145
- className={toutVariants({ variant, align, class: className })}
143
+ className={toutVariants({ colorScheme, align, class: className })}
146
144
  style={{ ...themeStyles, ...style }}
147
145
  {...props}
148
146
  >
@@ -8,10 +8,10 @@ const meta: Meta<typeof TwoColumnSection> = {
8
8
  layout: "fullscreen",
9
9
  },
10
10
  argTypes: {
11
- variant: {
11
+ colorScheme: {
12
12
  control: "select",
13
13
  options: ["dark", "light"],
14
- description: "Color variant",
14
+ description: "Color scheme",
15
15
  },
16
16
  title: {
17
17
  control: "text",
@@ -35,7 +35,7 @@ export const Playground: Story = {
35
35
  render: (args) => <TwoColumnSection {...args} />,
36
36
  };
37
37
  Playground.args = {
38
- variant: "dark",
38
+ colorScheme: "dark",
39
39
  title: "US Tech Force",
40
40
  lead: "The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology. Backed by the White House, Tech Force will tackle the most complex and large-scale civic and defense challenges of our era – from administering critical financial infrastructure at the Treasury Department to advancing cutting-edge programs at the Department of War – and everything in between.",
41
41
  children: (
@@ -73,7 +73,7 @@ Playground.args = {
73
73
  export const DarkVariant: Story = {
74
74
  render: () => (
75
75
  <TwoColumnSection
76
- variant="dark"
76
+ colorScheme="dark"
77
77
  title="US Tech Force"
78
78
  lead="The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology."
79
79
  >
@@ -95,7 +95,7 @@ export const DarkVariant: Story = {
95
95
  export const LightVariant: Story = {
96
96
  render: () => (
97
97
  <TwoColumnSection
98
- variant="light"
98
+ colorScheme="light"
99
99
  title="US Tech Force"
100
100
  lead="The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology."
101
101
  >
@@ -118,7 +118,7 @@ export const LightVariant: Story = {
118
118
  export const Desktop: Story = {
119
119
  render: () => (
120
120
  <TwoColumnSection
121
- variant="dark"
121
+ colorScheme="dark"
122
122
  title="US Tech Force"
123
123
  lead="The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology. Backed by the White House, Tech Force will tackle the most complex and large-scale civic and defense challenges of our era."
124
124
  >
@@ -151,7 +151,7 @@ export const Desktop: Story = {
151
151
  export const Tablet: Story = {
152
152
  render: () => (
153
153
  <TwoColumnSection
154
- variant="dark"
154
+ colorScheme="dark"
155
155
  title="US Tech Force"
156
156
  lead="The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology. Backed by the White House, Tech Force will tackle the most complex and large-scale civic and defense challenges of our era."
157
157
  >
@@ -184,7 +184,7 @@ export const Tablet: Story = {
184
184
  export const Mobile: Story = {
185
185
  render: () => (
186
186
  <TwoColumnSection
187
- variant="dark"
187
+ colorScheme="dark"
188
188
  title="US Tech Force"
189
189
  lead="The US Tech Force is recruiting an elite corps of engineers to build the next generation of government technology. Backed by the White House, Tech Force will tackle the most complex and large-scale civic and defense challenges of our era."
190
190
  >
@@ -223,7 +223,7 @@ export const Mobile: Story = {
223
223
  */
224
224
  export const WithoutLead: Story = {
225
225
  render: () => (
226
- <TwoColumnSection variant="dark" title="About the Program">
226
+ <TwoColumnSection colorScheme="dark" title="About the Program">
227
227
  <p>
228
228
  Through a two-year program, participants will work in teams reporting
229
229
  directly to agency leadership. In collaboration with leading technology
@@ -245,7 +245,7 @@ export const WithoutLead: Story = {
245
245
  export const RichLeadContent: Story = {
246
246
  render: () => (
247
247
  <TwoColumnSection
248
- variant="dark"
248
+ colorScheme="dark"
249
249
  title="Our Mission"
250
250
  lead={
251
251
  <>
@@ -275,7 +275,7 @@ export const RichLeadContent: Story = {
275
275
  export const ShortContent: Story = {
276
276
  render: () => (
277
277
  <TwoColumnSection
278
- variant="dark"
278
+ colorScheme="dark"
279
279
  title="Join Us"
280
280
  lead="Build the future of American government technology."
281
281
  >
@@ -23,19 +23,17 @@ const twoColumnSectionVariants = tv({
23
23
  "lg:px-spacing-72 lg:pt-spacing-72 lg:pb-spacing-112",
24
24
  ],
25
25
  variants: {
26
- variant: {
26
+ colorScheme: {
27
27
  dark: "bg-gray-1200",
28
28
  light: "bg-white",
29
29
  },
30
30
  layout: {
31
- /** Default 24-column grid with asymmetric split (title: 9, content: 15) */
32
31
  asymmetric: "",
33
- /** Equal 2-column layout at md+ breakpoints */
34
32
  equal: "",
35
33
  },
36
34
  },
37
35
  defaultVariants: {
38
- variant: "dark",
36
+ colorScheme: "dark",
39
37
  layout: "asymmetric",
40
38
  },
41
39
  });
@@ -86,14 +84,22 @@ export interface TwoColumnSectionProps
86
84
  */
87
85
  const TwoColumnSection = React.forwardRef<HTMLElement, TwoColumnSectionProps>(
88
86
  (
89
- { className, variant = "dark", layout, title, lead, children, ...props },
87
+ {
88
+ className,
89
+ colorScheme = "dark",
90
+ layout,
91
+ title,
92
+ lead,
93
+ children,
94
+ ...props
95
+ },
90
96
  ref,
91
97
  ) => {
92
98
  return (
93
99
  <section
94
100
  ref={ref}
95
101
  className={twoColumnSectionVariants({
96
- variant,
102
+ colorScheme,
97
103
  layout,
98
104
  class: className,
99
105
  })}
@@ -103,7 +109,7 @@ const TwoColumnSection = React.forwardRef<HTMLElement, TwoColumnSectionProps>(
103
109
  <div
104
110
  className={cn(
105
111
  "border-t pt-spacing-36",
106
- variant === "dark" ? "border-gray-700" : "border-gray-300",
112
+ colorScheme === "dark" ? "border-gray-700" : "border-gray-300",
107
113
  // Grid layout - uses primitive spacing tokens
108
114
  "grid grid-cols-1 gap-spacing-56",
109
115
  layout === "equal"
@@ -115,7 +121,7 @@ const TwoColumnSection = React.forwardRef<HTMLElement, TwoColumnSectionProps>(
115
121
  <h2
116
122
  className={cn(
117
123
  "typography-subheading-medium",
118
- variant === "dark" ? "text-gray-100" : "text-gray-900",
124
+ colorScheme === "dark" ? "text-gray-100" : "text-gray-900",
119
125
  // Column span based on layout
120
126
  layout !== "equal" && "lg:col-span-9",
121
127
  )}
@@ -135,7 +141,7 @@ const TwoColumnSection = React.forwardRef<HTMLElement, TwoColumnSectionProps>(
135
141
  <div
136
142
  className={cn(
137
143
  "typography-body-large",
138
- variant === "dark" ? "text-gray-100" : "text-gray-900",
144
+ colorScheme === "dark" ? "text-gray-100" : "text-gray-900",
139
145
  )}
140
146
  >
141
147
  {typeof lead === "string" ? <p>{lead}</p> : lead}
@@ -146,7 +152,7 @@ const TwoColumnSection = React.forwardRef<HTMLElement, TwoColumnSectionProps>(
146
152
  <div
147
153
  className={cn(
148
154
  "typography-body-medium flex flex-col gap-[1em]",
149
- variant === "dark" ? "text-gray-400" : "text-gray-600",
155
+ colorScheme === "dark" ? "text-gray-400" : "text-gray-600",
150
156
  )}
151
157
  >
152
158
  {children}