@nationaldesignstudio/react 0.2.0 → 0.5.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 (97) hide show
  1. package/dist/component-registry.md +1310 -127
  2. package/dist/components/atoms/background/background.d.ts +13 -27
  3. package/dist/components/atoms/button/button.d.ts +64 -72
  4. package/dist/components/atoms/button/button.figma.d.ts +1 -0
  5. package/dist/components/atoms/button/icon-button.d.ts +62 -110
  6. package/dist/components/atoms/input/input-group.d.ts +278 -0
  7. package/dist/components/atoms/input/input.d.ts +121 -0
  8. package/dist/components/atoms/popover/popover.d.ts +195 -0
  9. package/dist/components/atoms/select/select.d.ts +131 -0
  10. package/dist/components/atoms/tooltip/tooltip.d.ts +161 -0
  11. package/dist/components/organisms/card/card.d.ts +3 -3
  12. package/dist/components/sections/hero/hero.d.ts +2 -2
  13. package/dist/components/sections/prose/prose.d.ts +3 -3
  14. package/dist/components/sections/river/river.d.ts +1 -1
  15. package/dist/components/sections/tout/tout.d.ts +4 -4
  16. package/dist/components/shared/floating-arrow.d.ts +34 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +13935 -7622
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/form-control.d.ts +106 -0
  21. package/dist/tokens.css +4725 -19065
  22. package/package.json +2 -1
  23. package/src/components/atoms/accordion/accordion.stories.tsx +1 -1
  24. package/src/components/atoms/accordion/accordion.tsx +2 -2
  25. package/src/components/atoms/background/background.tsx +71 -109
  26. package/src/components/atoms/button/button.figma.tsx +37 -0
  27. package/src/components/atoms/button/button.stories.tsx +253 -115
  28. package/src/components/atoms/button/button.test.tsx +289 -5
  29. package/src/components/atoms/button/button.tsx +40 -101
  30. package/src/components/atoms/button/button.visual.test.tsx +28 -32
  31. package/src/components/atoms/button/icon-button.stories.tsx +44 -101
  32. package/src/components/atoms/button/icon-button.test.tsx +26 -94
  33. package/src/components/atoms/button/icon-button.tsx +81 -224
  34. package/src/components/atoms/input/index.ts +17 -0
  35. package/src/components/atoms/input/input-group.stories.tsx +646 -0
  36. package/src/components/atoms/input/input-group.test.tsx +362 -0
  37. package/src/components/atoms/input/input-group.tsx +409 -0
  38. package/src/components/atoms/input/input.stories.tsx +228 -0
  39. package/src/components/atoms/input/input.test.tsx +167 -0
  40. package/src/components/atoms/input/input.tsx +104 -0
  41. package/src/components/atoms/pager-control/pager-control.stories.tsx +6 -8
  42. package/src/components/atoms/pager-control/pager-control.tsx +12 -12
  43. package/src/components/atoms/popover/index.ts +30 -0
  44. package/src/components/atoms/popover/popover.stories.tsx +531 -0
  45. package/src/components/atoms/popover/popover.test.tsx +486 -0
  46. package/src/components/atoms/popover/popover.tsx +488 -0
  47. package/src/components/atoms/select/index.ts +18 -0
  48. package/src/components/atoms/select/select.stories.tsx +455 -0
  49. package/src/components/atoms/select/select.tsx +324 -0
  50. package/src/components/atoms/tooltip/index.ts +24 -0
  51. package/src/components/atoms/tooltip/tooltip.stories.tsx +348 -0
  52. package/src/components/atoms/tooltip/tooltip.test.tsx +363 -0
  53. package/src/components/atoms/tooltip/tooltip.tsx +347 -0
  54. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +8 -17
  55. package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +3 -3
  56. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  57. package/src/components/organisms/card/card.stories.tsx +19 -19
  58. package/src/components/organisms/card/card.test.tsx +1 -1
  59. package/src/components/organisms/card/card.tsx +3 -3
  60. package/src/components/organisms/card/card.visual.test.tsx +11 -11
  61. package/src/components/organisms/navbar/navbar.tsx +2 -2
  62. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  63. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +2 -2
  64. package/src/components/sections/banner/banner.stories.tsx +1 -5
  65. package/src/components/sections/banner/banner.test.tsx +2 -2
  66. package/src/components/sections/banner/banner.tsx +6 -6
  67. package/src/components/sections/card-grid/card-grid.tsx +5 -5
  68. package/src/components/sections/faq-section/faq-section.tsx +2 -2
  69. package/src/components/sections/hero/hero.stories.tsx +7 -7
  70. package/src/components/sections/hero/hero.test.tsx +5 -5
  71. package/src/components/sections/hero/hero.tsx +10 -11
  72. package/src/components/sections/prose/prose.test.tsx +2 -2
  73. package/src/components/sections/prose/prose.tsx +6 -7
  74. package/src/components/sections/river/river.stories.tsx +8 -8
  75. package/src/components/sections/river/river.test.tsx +4 -4
  76. package/src/components/sections/river/river.tsx +8 -16
  77. package/src/components/sections/tout/tout.stories.tsx +7 -31
  78. package/src/components/sections/tout/tout.test.tsx +1 -1
  79. package/src/components/sections/tout/tout.tsx +11 -11
  80. package/src/components/sections/two-column-section/two-column-section.tsx +7 -9
  81. package/src/components/shared/floating-arrow.tsx +78 -0
  82. package/src/components/shared/index.ts +5 -0
  83. package/src/index.ts +98 -0
  84. package/src/lib/form-control.ts +71 -0
  85. package/src/stories/grid-system.stories.tsx +309 -0
  86. package/src/stories/{Introduction.mdx → introduction.mdx} +29 -15
  87. package/src/stories/{ThemeProvider.stories.tsx → theme-provider.stories.tsx} +8 -22
  88. package/src/stories/{TokenShowcase.stories.tsx → token-showcase.stories.tsx} +1 -20
  89. package/src/stories/token-showcase.tsx +777 -0
  90. package/src/styles.css +3 -0
  91. package/src/tests/token-resolution.test.tsx +298 -0
  92. package/src/theme/hooks.ts +1 -1
  93. package/src/theme/index.ts +1 -1
  94. package/src/theme/theme-provider.test.tsx +270 -0
  95. package/src/theme/{ThemeProvider.tsx → theme-provider.tsx} +18 -2
  96. package/src/stories/GridSystem.stories.tsx +0 -84
  97. package/src/stories/TokenShowcase.tsx +0 -1429
@@ -1,7 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { IconButton } from ".";
3
3
 
4
- // Simple placeholder icons for stories
5
4
  const SearchIcon = () => (
6
5
  <svg
7
6
  width="16"
@@ -66,7 +65,7 @@ Playground.argTypes = {
66
65
  control: {
67
66
  type: "radio",
68
67
  },
69
- options: ["sm", "default", "lg"],
68
+ options: ["sm", "md", "lg"],
70
69
  },
71
70
  disabled: {
72
71
  control: {
@@ -77,42 +76,38 @@ Playground.argTypes = {
77
76
  control: {
78
77
  type: "radio",
79
78
  },
80
- options: ["solid", "outline", "ghost", "subtle"],
81
- },
82
- colorScheme: {
83
- control: {
84
- type: "radio",
85
- },
86
- options: ["dark", "light"],
79
+ options: [
80
+ "primary",
81
+ "primary-outline",
82
+ "secondary",
83
+ "secondary-outline",
84
+ "ghost",
85
+ "ghost-inverse",
86
+ ],
87
87
  },
88
88
  rounded: {
89
89
  control: {
90
90
  type: "radio",
91
91
  },
92
- options: ["default", "sm", "full"],
92
+ options: ["default", "full"],
93
93
  },
94
94
  };
95
95
  Playground.args = {
96
- size: "default",
96
+ size: "md",
97
97
  disabled: false,
98
- variant: "solid",
99
- colorScheme: "dark",
98
+ variant: "primary",
100
99
  rounded: "default",
101
100
  "aria-label": "Search",
102
101
  };
103
102
 
104
- // =============================================================================
105
- // Variants (Dark Color Scheme - for light backgrounds)
106
- // =============================================================================
107
-
108
- export const Solid = () => (
109
- <IconButton variant="solid" aria-label="Search">
103
+ export const Primary = () => (
104
+ <IconButton variant="primary" aria-label="Search">
110
105
  <SearchIcon />
111
106
  </IconButton>
112
107
  );
113
108
 
114
- export const Outline = () => (
115
- <IconButton variant="outline" aria-label="Search">
109
+ export const PrimaryOutline = () => (
110
+ <IconButton variant="primary-outline" aria-label="Search">
116
111
  <SearchIcon />
117
112
  </IconButton>
118
113
  );
@@ -123,92 +118,64 @@ export const Ghost = () => (
123
118
  </IconButton>
124
119
  );
125
120
 
126
- export const Subtle = () => (
127
- <IconButton variant="subtle" aria-label="Search">
128
- <SearchIcon />
129
- </IconButton>
130
- );
131
-
132
- // =============================================================================
133
- // Variants (Light Color Scheme - for dark backgrounds)
134
- // =============================================================================
135
-
136
121
  const DarkBackground = ({ children }: { children: React.ReactNode }) => (
137
- <div className="rounded-radius-12 bg-gray-1200 p-spacing-32">{children}</div>
138
- );
139
-
140
- export const SolidLight = () => (
141
- <DarkBackground>
142
- <IconButton variant="solid" colorScheme="light" aria-label="Navigate">
143
- <ArrowRightIcon />
144
- </IconButton>
145
- </DarkBackground>
122
+ <div className="rounded-radius-12 bg-gray-1200 p-32">{children}</div>
146
123
  );
147
124
 
148
- export const OutlineLight = () => (
125
+ export const Secondary = () => (
149
126
  <DarkBackground>
150
- <IconButton variant="outline" colorScheme="light" aria-label="Navigate">
127
+ <IconButton variant="secondary" aria-label="Navigate">
151
128
  <ArrowRightIcon />
152
129
  </IconButton>
153
130
  </DarkBackground>
154
131
  );
155
132
 
156
- export const GhostLight = () => (
133
+ export const SecondaryOutline = () => (
157
134
  <DarkBackground>
158
- <IconButton variant="ghost" colorScheme="light" aria-label="Navigate">
135
+ <IconButton variant="secondary-outline" aria-label="Navigate">
159
136
  <ArrowRightIcon />
160
137
  </IconButton>
161
138
  </DarkBackground>
162
139
  );
163
140
 
164
- export const SubtleLight = () => (
141
+ export const GhostInverse = () => (
165
142
  <DarkBackground>
166
- <IconButton variant="subtle" colorScheme="light" aria-label="Navigate">
143
+ <IconButton variant="ghost-inverse" aria-label="Navigate">
167
144
  <ArrowRightIcon />
168
145
  </IconButton>
169
146
  </DarkBackground>
170
147
  );
171
148
 
172
- // =============================================================================
173
- // All Variants Comparison
174
- // =============================================================================
175
-
176
149
  export const AllVariants = () => (
177
- <div className="flex flex-col gap-spacing-32">
150
+ <div className="flex flex-col gap-32">
178
151
  <div>
179
- <h3 className="mb-spacing-16 text-14 font-medium text-text-secondary">
180
- Dark Color Scheme (for light backgrounds)
152
+ <h3 className="mb-16 text-14 font-medium text-text-secondary">
153
+ For Light Backgrounds
181
154
  </h3>
182
- <div className="flex gap-spacing-16">
183
- <IconButton variant="solid" aria-label="Solid">
155
+ <div className="flex gap-16">
156
+ <IconButton variant="primary" aria-label="Primary">
184
157
  <SearchIcon />
185
158
  </IconButton>
186
- <IconButton variant="outline" aria-label="Outline">
159
+ <IconButton variant="primary-outline" aria-label="Primary Outline">
187
160
  <SearchIcon />
188
161
  </IconButton>
189
162
  <IconButton variant="ghost" aria-label="Ghost">
190
163
  <SearchIcon />
191
164
  </IconButton>
192
- <IconButton variant="subtle" aria-label="Subtle">
193
- <SearchIcon />
194
- </IconButton>
195
165
  </div>
196
166
  </div>
197
167
  <DarkBackground>
198
- <h3 className="mb-spacing-16 text-14 font-medium text-gray-400">
199
- Light Color Scheme (for dark backgrounds)
168
+ <h3 className="mb-16 text-14 font-medium text-gray-400">
169
+ For Dark Backgrounds
200
170
  </h3>
201
- <div className="flex gap-spacing-16">
202
- <IconButton variant="solid" colorScheme="light" aria-label="Solid">
203
- <ArrowRightIcon />
204
- </IconButton>
205
- <IconButton variant="outline" colorScheme="light" aria-label="Outline">
171
+ <div className="flex gap-16">
172
+ <IconButton variant="secondary" aria-label="Secondary">
206
173
  <ArrowRightIcon />
207
174
  </IconButton>
208
- <IconButton variant="ghost" colorScheme="light" aria-label="Ghost">
175
+ <IconButton variant="secondary-outline" aria-label="Secondary Outline">
209
176
  <ArrowRightIcon />
210
177
  </IconButton>
211
- <IconButton variant="subtle" colorScheme="light" aria-label="Subtle">
178
+ <IconButton variant="ghost-inverse" aria-label="Ghost Inverse">
212
179
  <ArrowRightIcon />
213
180
  </IconButton>
214
181
  </div>
@@ -216,22 +183,12 @@ export const AllVariants = () => (
216
183
  </div>
217
184
  );
218
185
 
219
- // =============================================================================
220
- // Rounded Variants
221
- // =============================================================================
222
-
223
186
  export const RoundedDefault = () => (
224
187
  <IconButton rounded="default" aria-label="Search">
225
188
  <SearchIcon />
226
189
  </IconButton>
227
190
  );
228
191
 
229
- export const RoundedSm = () => (
230
- <IconButton rounded="sm" aria-label="Search">
231
- <SearchIcon />
232
- </IconButton>
233
- );
234
-
235
192
  export const RoundedFull = () => (
236
193
  <IconButton rounded="full" aria-label="Search">
237
194
  <SearchIcon />
@@ -239,32 +196,22 @@ export const RoundedFull = () => (
239
196
  );
240
197
 
241
198
  export const AllRounded = () => (
242
- <div className="flex gap-spacing-16">
199
+ <div className="flex gap-16">
243
200
  <div className="text-center">
244
201
  <IconButton rounded="default" aria-label="Default rounded">
245
202
  <SearchIcon />
246
203
  </IconButton>
247
- <p className="mt-spacing-8 text-12 text-text-muted">default</p>
248
- </div>
249
- <div className="text-center">
250
- <IconButton rounded="sm" aria-label="Small rounded">
251
- <SearchIcon />
252
- </IconButton>
253
- <p className="mt-spacing-8 text-12 text-text-muted">sm</p>
204
+ <p className="mt-8 text-12 text-text-muted">default</p>
254
205
  </div>
255
206
  <div className="text-center">
256
207
  <IconButton rounded="full" aria-label="Full rounded">
257
208
  <SearchIcon />
258
209
  </IconButton>
259
- <p className="mt-spacing-8 text-12 text-text-muted">full</p>
210
+ <p className="mt-8 text-12 text-text-muted">full</p>
260
211
  </div>
261
212
  </div>
262
213
  );
263
214
 
264
- // =============================================================================
265
- // Sizes
266
- // =============================================================================
267
-
268
215
  export const Small = () => (
269
216
  <IconButton size="sm" aria-label="Search">
270
217
  <ArrowRightIcon />
@@ -272,7 +219,7 @@ export const Small = () => (
272
219
  );
273
220
 
274
221
  export const Medium = () => (
275
- <IconButton size="default" aria-label="Search">
222
+ <IconButton size="md" aria-label="Search">
276
223
  <SearchIcon />
277
224
  </IconButton>
278
225
  );
@@ -284,32 +231,28 @@ export const Large = () => (
284
231
  );
285
232
 
286
233
  export const AllSizes = () => (
287
- <div className="flex items-center gap-spacing-16">
234
+ <div className="flex items-center gap-16">
288
235
  <div className="text-center">
289
236
  <IconButton size="sm" aria-label="Small">
290
237
  <ArrowRightIcon />
291
238
  </IconButton>
292
- <p className="mt-spacing-8 text-12 text-text-muted">sm (32px)</p>
239
+ <p className="mt-8 text-12 text-text-muted">sm (28px)</p>
293
240
  </div>
294
241
  <div className="text-center">
295
- <IconButton size="default" aria-label="Default">
242
+ <IconButton size="md" aria-label="Default">
296
243
  <SearchIcon />
297
244
  </IconButton>
298
- <p className="mt-spacing-8 text-12 text-text-muted">default (40px)</p>
245
+ <p className="mt-8 text-12 text-text-muted">md (40px)</p>
299
246
  </div>
300
247
  <div className="text-center">
301
248
  <IconButton size="lg" aria-label="Large">
302
249
  <SearchIcon />
303
250
  </IconButton>
304
- <p className="mt-spacing-8 text-12 text-text-muted">lg (48px)</p>
251
+ <p className="mt-8 text-12 text-text-muted">lg (56px)</p>
305
252
  </div>
306
253
  </div>
307
254
  );
308
255
 
309
- // =============================================================================
310
- // States
311
- // =============================================================================
312
-
313
256
  export const Disabled = () => (
314
257
  <IconButton disabled aria-label="Search">
315
258
  <SearchIcon />
@@ -3,7 +3,6 @@ import { page, userEvent } from "vitest/browser";
3
3
  import { render } from "vitest-browser-react";
4
4
  import { IconButton } from "./icon-button";
5
5
 
6
- // Simple icon for testing
7
6
  const TestIcon = () => (
8
7
  // biome-ignore lint/a11y/noSvgWithoutTitle: Test component doesn't need accessibility title
9
8
  <svg data-testid="test-icon" width="24" height="24" viewBox="0 0 24 24">
@@ -139,116 +138,49 @@ describe("IconButton", () => {
139
138
  });
140
139
  });
141
140
 
142
- describe("Data attributes", () => {
143
- test("includes data-variant attribute", async () => {
144
- render(
145
- <IconButton variant="ghost" aria-label="Ghost variant">
146
- <TestIcon />
147
- </IconButton>,
148
- );
149
- await expect
150
- .element(page.getByRole("button", { name: "Ghost variant" }))
151
- .toHaveAttribute("data-variant", "ghost");
152
- });
153
-
154
- test("includes data-size attribute", async () => {
155
- render(
156
- <IconButton size="lg" aria-label="Large size">
157
- <TestIcon />
158
- </IconButton>,
159
- );
160
- await expect
161
- .element(page.getByRole("button", { name: "Large size" }))
162
- .toHaveAttribute("data-size", "lg");
163
- });
164
-
165
- test("includes data-color-scheme attribute", async () => {
166
- render(
167
- <IconButton colorScheme="light" aria-label="Light scheme">
168
- <TestIcon />
169
- </IconButton>,
170
- );
171
- await expect
172
- .element(page.getByRole("button", { name: "Light scheme" }))
173
- .toHaveAttribute("data-color-scheme", "light");
174
- });
175
-
176
- test("includes data-rounded attribute", async () => {
177
- render(
178
- <IconButton rounded="full" aria-label="Full rounded">
179
- <TestIcon />
180
- </IconButton>,
181
- );
182
- await expect
183
- .element(page.getByRole("button", { name: "Full rounded" }))
184
- .toHaveAttribute("data-rounded", "full");
185
- });
186
-
187
- test("has default data attributes when not specified", async () => {
188
- render(
189
- <IconButton aria-label="Defaults">
190
- <TestIcon />
191
- </IconButton>,
192
- );
193
- const button = page.getByRole("button", { name: "Defaults" });
194
- await expect.element(button).toHaveAttribute("data-variant", "solid");
195
- await expect.element(button).toHaveAttribute("data-size", "default");
196
- await expect.element(button).toHaveAttribute("data-color-scheme", "dark");
197
- await expect.element(button).toHaveAttribute("data-rounded", "default");
198
- });
199
- });
200
-
201
141
  describe("Variants", () => {
202
- test("applies solid variant classes by default", async () => {
142
+ test("primary variant has visible background color", async () => {
203
143
  render(
204
144
  <IconButton aria-label="Default">
205
145
  <TestIcon />
206
146
  </IconButton>,
207
147
  );
208
148
  const button = page.getByRole("button", { name: "Default" });
209
- await expect.element(button).toHaveClass(/bg-gray-1200/);
210
- });
149
+ await expect.element(button).toBeInTheDocument();
211
150
 
212
- test("applies ghost variant classes", async () => {
213
- render(
214
- <IconButton variant="ghost" aria-label="Ghost">
215
- <TestIcon />
216
- </IconButton>,
217
- );
218
- const button = page.getByRole("button", { name: "Ghost" });
219
- await expect.element(button).toHaveClass(/text-gray-700/);
151
+ const element = button.element();
152
+ const bgColor = window.getComputedStyle(element).backgroundColor;
153
+ expect(bgColor).not.toBe("rgba(0, 0, 0, 0)");
154
+ expect(bgColor).not.toBe("transparent");
220
155
  });
221
156
  });
222
157
 
223
158
  describe("Sizes", () => {
224
- test("applies small size classes", async () => {
159
+ test("different sizes have different dimensions", async () => {
225
160
  render(
226
- <IconButton size="sm" aria-label="Small">
227
- <TestIcon />
228
- </IconButton>,
161
+ <>
162
+ <IconButton size="sm" aria-label="Small">
163
+ <TestIcon />
164
+ </IconButton>
165
+ <IconButton size="lg" aria-label="Large">
166
+ <TestIcon />
167
+ </IconButton>
168
+ </>,
229
169
  );
230
- const button = page.getByRole("button", { name: "Small" });
231
- await expect.element(button).toHaveClass(/size-32/);
232
- });
233
170
 
234
- test("applies default size classes", async () => {
235
- render(
236
- <IconButton size="default" aria-label="Default">
237
- <TestIcon />
238
- </IconButton>,
239
- );
240
- const button = page.getByRole("button", { name: "Default" });
241
- await expect.element(button).toHaveClass(/size-40/);
242
- });
171
+ const smallBtn = page.getByRole("button", { name: "Small" });
172
+ const largeBtn = page.getByRole("button", { name: "Large" });
173
+ await expect.element(smallBtn).toBeInTheDocument();
174
+ await expect.element(largeBtn).toBeInTheDocument();
243
175
 
244
- test("applies large size classes", async () => {
245
- render(
246
- <IconButton size="lg" aria-label="Large">
247
- <TestIcon />
248
- </IconButton>,
176
+ const smallWidth = Number.parseFloat(
177
+ window.getComputedStyle(smallBtn.element()).width,
178
+ );
179
+ const largeWidth = Number.parseFloat(
180
+ window.getComputedStyle(largeBtn.element()).width,
249
181
  );
250
- const button = page.getByRole("button", { name: "Large" });
251
- await expect.element(button).toHaveClass(/size-48/);
182
+
183
+ expect(largeWidth).toBeGreaterThan(smallWidth);
252
184
  });
253
185
  });
254
186
  });