@nationaldesignstudio/react 0.0.14 → 0.0.16

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 (166) hide show
  1. package/dist/tailwind.css +15 -1
  2. package/dist/tokens.css +45 -60
  3. package/package.json +5 -10
  4. package/src/App.css +0 -0
  5. package/src/App.tsx +7 -0
  6. package/src/assets/fonts/PPNeueMontreal-Variable.woff2 +0 -0
  7. package/src/assets/react.svg +1 -0
  8. package/src/components/atoms/accordion/accordion.stories.tsx +228 -0
  9. package/src/components/atoms/accordion/accordion.tsx +219 -0
  10. package/src/components/atoms/accordion/index.ts +6 -0
  11. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-darwin.png +0 -0
  12. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-linux.png +0 -0
  13. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-darwin.png +0 -0
  14. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-linux.png +0 -0
  15. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-darwin.png +0 -0
  16. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-linux.png +0 -0
  17. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-darwin.png +0 -0
  18. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-linux.png +0 -0
  19. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-darwin.png +0 -0
  20. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-linux.png +0 -0
  21. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-darwin.png +0 -0
  22. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-linux.png +0 -0
  23. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-darwin.png +0 -0
  24. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-linux.png +0 -0
  25. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-darwin.png +0 -0
  26. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-linux.png +0 -0
  27. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-darwin.png +0 -0
  28. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-linux.png +0 -0
  29. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-darwin.png +0 -0
  30. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-linux.png +0 -0
  31. package/src/components/atoms/button/button.stories.tsx +102 -0
  32. package/src/components/atoms/button/button.test.tsx +135 -0
  33. package/src/components/atoms/button/button.tsx +139 -0
  34. package/src/components/atoms/button/button.visual.test.tsx +102 -0
  35. package/src/components/atoms/button/icon-button.stories.tsx +166 -0
  36. package/src/components/atoms/button/icon-button.tsx +120 -0
  37. package/src/components/atoms/button/index.ts +6 -0
  38. package/src/components/atoms/ndstudio-footer/index.ts +1 -0
  39. package/src/components/atoms/ndstudio-footer/ndstudio-footer.tsx +55 -0
  40. package/src/components/atoms/pager-control/index.ts +5 -0
  41. package/src/components/atoms/pager-control/pager-control.stories.tsx +209 -0
  42. package/src/components/atoms/pager-control/pager-control.test.tsx +130 -0
  43. package/src/components/atoms/pager-control/pager-control.tsx +329 -0
  44. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +82 -0
  45. package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +196 -0
  46. package/src/components/dev-tools/dev-toolbar/index.ts +1 -0
  47. package/src/components/dev-tools/grid-overlay/grid-overlay.tsx +41 -0
  48. package/src/components/dev-tools/grid-overlay/index.ts +1 -0
  49. package/src/components/dev-tools/index.ts +2 -0
  50. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-darwin.png +0 -0
  51. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-linux.png +0 -0
  52. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-darwin.png +0 -0
  53. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-linux.png +0 -0
  54. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-darwin.png +0 -0
  55. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-linux.png +0 -0
  56. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-darwin.png +0 -0
  57. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-linux.png +0 -0
  58. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-darwin.png +0 -0
  59. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-linux.png +0 -0
  60. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-darwin.png +0 -0
  61. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-linux.png +0 -0
  62. package/src/components/organisms/card/card.stories.tsx +293 -0
  63. package/src/components/organisms/card/card.test.tsx +245 -0
  64. package/src/components/organisms/card/card.tsx +225 -0
  65. package/src/components/organisms/card/card.visual.test.tsx +197 -0
  66. package/src/components/organisms/card/index.ts +19 -0
  67. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-darwin.png +0 -0
  68. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-linux.png +0 -0
  69. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-darwin.png +0 -0
  70. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-linux.png +0 -0
  71. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-darwin.png +0 -0
  72. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-linux.png +0 -0
  73. package/src/components/organisms/navbar/index.ts +18 -0
  74. package/src/components/organisms/navbar/navbar.stories.tsx +313 -0
  75. package/src/components/organisms/navbar/navbar.test.tsx +190 -0
  76. package/src/components/organisms/navbar/navbar.tsx +323 -0
  77. package/src/components/organisms/navbar/navbar.visual.test.tsx +85 -0
  78. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-darwin.png +0 -0
  79. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-linux.png +0 -0
  80. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-darwin.png +0 -0
  81. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-linux.png +0 -0
  82. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-darwin.png +0 -0
  83. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-linux.png +0 -0
  84. package/src/components/organisms/us-gov-banner/index.ts +1 -0
  85. package/src/components/organisms/us-gov-banner/us-gov-banner.stories.tsx +35 -0
  86. package/src/components/organisms/us-gov-banner/us-gov-banner.test.tsx +107 -0
  87. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +73 -0
  88. package/src/components/organisms/us-gov-banner/us-gov-banner.visual.test.tsx +46 -0
  89. package/src/components/sections/banner/banner.stories.tsx +150 -0
  90. package/src/components/sections/banner/banner.test.tsx +185 -0
  91. package/src/components/sections/banner/banner.tsx +130 -0
  92. package/src/components/sections/banner/index.ts +2 -0
  93. package/src/components/sections/card-grid/card-grid.stories.tsx +351 -0
  94. package/src/components/sections/card-grid/card-grid.tsx +116 -0
  95. package/src/components/sections/card-grid/index.ts +1 -0
  96. package/src/components/sections/faq-section/faq-section.stories.tsx +453 -0
  97. package/src/components/sections/faq-section/faq-section.tsx +84 -0
  98. package/src/components/sections/faq-section/index.ts +2 -0
  99. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-darwin.png +0 -0
  100. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-linux.png +0 -0
  101. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-darwin.png +0 -0
  102. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-linux.png +0 -0
  103. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-darwin.png +0 -0
  104. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-linux.png +0 -0
  105. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-darwin.png +0 -0
  106. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-linux.png +0 -0
  107. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-darwin.png +0 -0
  108. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-linux.png +0 -0
  109. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-darwin.png +0 -0
  110. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-linux.png +0 -0
  111. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-darwin.png +0 -0
  112. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-linux.png +0 -0
  113. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-darwin.png +0 -0
  114. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-linux.png +0 -0
  115. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-darwin.png +0 -0
  116. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-linux.png +0 -0
  117. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-darwin.png +0 -0
  118. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-linux.png +0 -0
  119. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-default-chromium-linux.png +0 -0
  120. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-darwin.png +0 -0
  121. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-linux.png +0 -0
  122. package/src/components/sections/hero/hero.stories.tsx +274 -0
  123. package/src/components/sections/hero/hero.test.tsx +135 -0
  124. package/src/components/sections/hero/hero.tsx +453 -0
  125. package/src/components/sections/hero/hero.visual.test.tsx +140 -0
  126. package/src/components/sections/hero/index.ts +10 -0
  127. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-darwin.png +0 -0
  128. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-linux.png +0 -0
  129. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-darwin.png +0 -0
  130. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-linux.png +0 -0
  131. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-darwin.png +0 -0
  132. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-linux.png +0 -0
  133. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-darwin.png +0 -0
  134. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-linux.png +0 -0
  135. package/src/components/sections/prose/index.ts +6 -0
  136. package/src/components/sections/prose/prose.stories.tsx +144 -0
  137. package/src/components/sections/prose/prose.test.tsx +178 -0
  138. package/src/components/sections/prose/prose.tsx +88 -0
  139. package/src/components/sections/prose/prose.visual.test.tsx +105 -0
  140. package/src/components/sections/river/index.ts +1 -0
  141. package/src/components/sections/river/river.stories.tsx +237 -0
  142. package/src/components/sections/river/river.test.tsx +268 -0
  143. package/src/components/sections/river/river.tsx +173 -0
  144. package/src/components/sections/tout/index.ts +1 -0
  145. package/src/components/sections/tout/tout.stories.tsx +171 -0
  146. package/src/components/sections/tout/tout.test.tsx +242 -0
  147. package/src/components/sections/tout/tout.tsx +270 -0
  148. package/src/components/sections/two-column-section/index.ts +5 -0
  149. package/src/components/sections/two-column-section/two-column-section.stories.tsx +285 -0
  150. package/src/components/sections/two-column-section/two-column-section.tsx +162 -0
  151. package/src/hooks/index.ts +1 -0
  152. package/src/hooks/use-event-listener.ts +73 -0
  153. package/src/index.ts +155 -0
  154. package/src/lib/theme.ts +1000 -0
  155. package/src/lib/utils.ts +6 -0
  156. package/src/main.tsx +13 -0
  157. package/src/stories/GridSystem.stories.tsx +84 -0
  158. package/src/stories/Introduction.mdx +114 -0
  159. package/src/stories/ThemeProvider.stories.tsx +357 -0
  160. package/src/stories/TokenShowcase.stories.tsx +92 -0
  161. package/src/stories/TokenShowcase.tsx +1429 -0
  162. package/src/styles.css +11 -0
  163. package/src/theme/ThemeProvider.tsx +297 -0
  164. package/src/theme/hooks.ts +40 -0
  165. package/src/theme/index.ts +43 -0
  166. package/src/theme/utils.ts +104 -0
@@ -0,0 +1,196 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { GridOverlay } from "../grid-overlay";
3
+
4
+ function GridIcon({ active }: { active?: boolean }) {
5
+ return (
6
+ <svg
7
+ width="20"
8
+ height="20"
9
+ viewBox="0 0 20 20"
10
+ fill="none"
11
+ stroke="currentColor"
12
+ strokeWidth={active ? "2" : "1.5"}
13
+ aria-hidden="true"
14
+ >
15
+ <rect x="2" y="2" width="16" height="16" rx="2" />
16
+ <line x1="7" y1="2" x2="7" y2="18" />
17
+ <line x1="13" y1="2" x2="13" y2="18" />
18
+ </svg>
19
+ );
20
+ }
21
+
22
+ export interface DevToolbarProps {
23
+ defaultExpanded?: boolean;
24
+ }
25
+
26
+ const DRAG_THRESHOLD = 3;
27
+
28
+ export function DevToolbar({ defaultExpanded = false }: DevToolbarProps) {
29
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
30
+ const [showGrid, setShowGrid] = useState(false);
31
+ const [position, setPosition] = useState({ x: 0, y: 0 });
32
+ const [isDragging, setIsDragging] = useState(false);
33
+ const hasDraggedRef = useRef(false);
34
+ const dragRef = useRef<{
35
+ startX: number;
36
+ startY: number;
37
+ startPosX: number;
38
+ startPosY: number;
39
+ } | null>(null);
40
+ const toolbarRef = useRef<HTMLDivElement>(null);
41
+
42
+ const toggleGrid = useCallback(() => setShowGrid((prev) => !prev), []);
43
+ const toggleExpanded = useCallback(() => setIsExpanded((prev) => !prev), []);
44
+
45
+ useEffect(() => {
46
+ const handleKeyDown = (e: KeyboardEvent) => {
47
+ if ((e.ctrlKey || e.metaKey) && e.key === "g") {
48
+ e.preventDefault();
49
+ toggleGrid();
50
+ }
51
+ };
52
+
53
+ window.addEventListener("keydown", handleKeyDown);
54
+ return () => window.removeEventListener("keydown", handleKeyDown);
55
+ }, [toggleGrid]);
56
+
57
+ const handleDragStart = useCallback(
58
+ (clientX: number, clientY: number) => {
59
+ setIsDragging(true);
60
+ hasDraggedRef.current = false;
61
+ dragRef.current = {
62
+ startX: clientX,
63
+ startY: clientY,
64
+ startPosX: position.x,
65
+ startPosY: position.y,
66
+ };
67
+ },
68
+ [position],
69
+ );
70
+
71
+ const handleDragMove = useCallback(
72
+ (clientX: number, clientY: number) => {
73
+ if (!isDragging || !dragRef.current) return;
74
+
75
+ const deltaX = clientX - dragRef.current.startX;
76
+ const deltaY = clientY - dragRef.current.startY;
77
+
78
+ if (
79
+ Math.abs(deltaX) > DRAG_THRESHOLD ||
80
+ Math.abs(deltaY) > DRAG_THRESHOLD
81
+ ) {
82
+ hasDraggedRef.current = true;
83
+ }
84
+
85
+ setPosition({
86
+ x: dragRef.current.startPosX + deltaX,
87
+ y: dragRef.current.startPosY - deltaY,
88
+ });
89
+ },
90
+ [isDragging],
91
+ );
92
+
93
+ const handleDragEnd = useCallback(() => {
94
+ setIsDragging(false);
95
+ dragRef.current = null;
96
+ }, []);
97
+
98
+ useEffect(() => {
99
+ if (!isDragging) return;
100
+
101
+ const handleMouseMove = (e: MouseEvent) =>
102
+ handleDragMove(e.clientX, e.clientY);
103
+ const handleTouchMove = (e: TouchEvent) => {
104
+ if (e.touches[0])
105
+ handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
106
+ };
107
+ const handleEnd = () => handleDragEnd();
108
+
109
+ window.addEventListener("mousemove", handleMouseMove);
110
+ window.addEventListener("mouseup", handleEnd);
111
+ window.addEventListener("touchmove", handleTouchMove);
112
+ window.addEventListener("touchend", handleEnd);
113
+
114
+ return () => {
115
+ window.removeEventListener("mousemove", handleMouseMove);
116
+ window.removeEventListener("mouseup", handleEnd);
117
+ window.removeEventListener("touchmove", handleTouchMove);
118
+ window.removeEventListener("touchend", handleEnd);
119
+ };
120
+ }, [isDragging, handleDragMove, handleDragEnd]);
121
+
122
+ const handleBarMouseDown = (e: React.MouseEvent) => {
123
+ e.preventDefault();
124
+ handleDragStart(e.clientX, e.clientY);
125
+ };
126
+
127
+ const handleBarTouchStart = (e: React.TouchEvent) => {
128
+ if (e.touches[0]) {
129
+ handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
130
+ }
131
+ };
132
+
133
+ const handleBarClick = () => {
134
+ if (!hasDraggedRef.current) {
135
+ toggleExpanded();
136
+ }
137
+ hasDraggedRef.current = false;
138
+ };
139
+
140
+ return (
141
+ <>
142
+ {showGrid && <GridOverlay />}
143
+
144
+ <div
145
+ ref={toolbarRef}
146
+ className="fixed bottom-4 left-1/2 z-[9999]"
147
+ style={{
148
+ transform: `translate(calc(-50% + ${position.x}px), ${-position.y}px)`,
149
+ }}
150
+ data-testid="dev-toolbar"
151
+ >
152
+ <div
153
+ className={`bg-gray-1100 rounded-radius-16 shadow-lg flex flex-col items-center overflow-hidden px-spacing-12 py-spacing-8 ${isExpanded ? "gap-spacing-4" : ""}`}
154
+ >
155
+ <div
156
+ className={`
157
+ grid transition-all duration-300 ease-out
158
+ ${isExpanded ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"}
159
+ `}
160
+ >
161
+ <div className="overflow-hidden">
162
+ <button
163
+ type="button"
164
+ onClick={toggleGrid}
165
+ className={`
166
+ size-spacing-40 rounded-full flex items-center justify-center transition-colors
167
+ ${
168
+ showGrid
169
+ ? "text-gray-50"
170
+ : "text-gray-400 hover:text-gray-50 hover:bg-alpha-white-10"
171
+ }
172
+ `}
173
+ title="Toggle Grid (⌘G)"
174
+ aria-label="Toggle grid overlay"
175
+ >
176
+ <GridIcon active={showGrid} />
177
+ </button>
178
+ </div>
179
+ </div>
180
+
181
+ <button
182
+ type="button"
183
+ onMouseDown={handleBarMouseDown}
184
+ onTouchStart={handleBarTouchStart}
185
+ onClick={handleBarClick}
186
+ className={`
187
+ w-spacing-32 h-spacing-4 bg-gray-50 rounded-full transition-opacity
188
+ ${isDragging ? "opacity-100 cursor-grabbing" : "opacity-60 hover:opacity-100 cursor-grab"}
189
+ `}
190
+ aria-label={isExpanded ? "Close dev tools" : "Open dev tools"}
191
+ />
192
+ </div>
193
+ </div>
194
+ </>
195
+ );
196
+ }
@@ -0,0 +1 @@
1
+ export { DevToolbar, type DevToolbarProps } from "./dev-toolbar";
@@ -0,0 +1,41 @@
1
+ export interface GridOverlayProps {
2
+ columnOpacity?: number;
3
+ borderOpacity?: number;
4
+ visible?: boolean;
5
+ }
6
+
7
+ export function GridOverlay({
8
+ columnOpacity = 0.15,
9
+ borderOpacity = 0.3,
10
+ visible = true,
11
+ }: GridOverlayProps) {
12
+ const columns = Array.from({ length: 24 }, (_, i) => i);
13
+
14
+ return (
15
+ <div
16
+ className={`
17
+ fixed inset-0 z-[9998] pointer-events-none overflow-hidden
18
+ transition-opacity duration-300 ease-out
19
+ ${visible ? "opacity-100" : "opacity-0"}
20
+ `}
21
+ aria-hidden="true"
22
+ data-testid="grid-overlay"
23
+ >
24
+ <div className="h-full w-full max-w-[90rem] mx-auto px-[var(--spatial-grid-small-margin)] md:px-[var(--spatial-grid-medium-margin)] lg:px-[var(--spatial-grid-large-margin)]">
25
+ <div className="h-full grid grid-cols-4 md:grid-cols-12 lg:grid-cols-24 gap-[var(--spatial-grid-small-gutter)] md:gap-[var(--spatial-grid-medium-gutter)] lg:gap-[var(--spatial-grid-large-gutter)]">
26
+ {columns.map((index) => (
27
+ <div
28
+ key={index}
29
+ className="h-full border border-red-500"
30
+ style={{
31
+ backgroundColor: `rgb(239 68 68 / ${columnOpacity})`,
32
+ borderColor: `rgb(239 68 68 / ${borderOpacity})`,
33
+ }}
34
+ data-column={index + 1}
35
+ />
36
+ ))}
37
+ </div>
38
+ </div>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1 @@
1
+ export { GridOverlay, type GridOverlayProps } from "./grid-overlay";
@@ -0,0 +1,2 @@
1
+ export { DevToolbar, type DevToolbarProps } from "./dev-toolbar";
2
+ export { GridOverlay, type GridOverlayProps } from "./grid-overlay";
@@ -0,0 +1,293 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Button } from "../../atoms/button";
3
+ import {
4
+ Card,
5
+ CardActions,
6
+ CardBody,
7
+ CardContent,
8
+ CardDescription,
9
+ CardEyebrow,
10
+ CardImage,
11
+ CardTitle,
12
+ } from ".";
13
+
14
+ const meta: Meta<typeof Card> = {
15
+ title: "Organisms/Card",
16
+ component: Card,
17
+ parameters: {
18
+ layout: "centered",
19
+ },
20
+ } as Meta<typeof Card>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof Card>;
24
+
25
+ // =============================================================================
26
+ // Default
27
+ // =============================================================================
28
+
29
+ export const Default: Story = {
30
+ render: () => (
31
+ <div className="w-[418px]">
32
+ <Card>
33
+ <CardImage />
34
+ <CardContent>
35
+ <CardBody>
36
+ <CardEyebrow>Optional Eyebrow</CardEyebrow>
37
+ <div className="flex flex-col gap-[6px]">
38
+ <CardTitle>Card Title</CardTitle>
39
+ <CardDescription>
40
+ Use cards when citizens need to scan items at a glance, either
41
+ to jump to a more detailed page or absorb short information.
42
+ </CardDescription>
43
+ </div>
44
+ </CardBody>
45
+ <CardActions>
46
+ <Button size="default" variant="charcoal">
47
+ Primary
48
+ </Button>
49
+ <Button size="default" variant="charcoalOutline">
50
+ Secondary
51
+ </Button>
52
+ </CardActions>
53
+ </CardContent>
54
+ </Card>
55
+ </div>
56
+ ),
57
+ };
58
+
59
+ // =============================================================================
60
+ // Layouts
61
+ // =============================================================================
62
+
63
+ export const Vertical: Story = {
64
+ render: () => (
65
+ <div className="w-[418px]">
66
+ <Card layout="vertical">
67
+ <CardImage />
68
+ <CardContent>
69
+ <CardBody>
70
+ <CardEyebrow>Optional Eyebrow</CardEyebrow>
71
+ <div className="flex flex-col gap-[6px]">
72
+ <CardTitle>Card Title</CardTitle>
73
+ <CardDescription>
74
+ Use cards when citizens need to scan items at a glance, either
75
+ to jump to a more detailed page or absorb short information.
76
+ </CardDescription>
77
+ </div>
78
+ </CardBody>
79
+ <CardActions>
80
+ <Button size="default" variant="charcoal">
81
+ Primary
82
+ </Button>
83
+ <Button size="default" variant="charcoalOutline">
84
+ Secondary
85
+ </Button>
86
+ </CardActions>
87
+ </CardContent>
88
+ </Card>
89
+ </div>
90
+ ),
91
+ };
92
+
93
+ export const Horizontal: Story = {
94
+ render: () => (
95
+ <div className="w-[700px]">
96
+ <Card layout="horizontal">
97
+ <CardImage />
98
+ <CardContent>
99
+ <CardBody>
100
+ <CardEyebrow>Eyebrow</CardEyebrow>
101
+ <div className="flex flex-col gap-[9px]">
102
+ <CardTitle>
103
+ Cards can support multi line headings easily.
104
+ </CardTitle>
105
+ <CardDescription>
106
+ Use cards when citizens need to scan items at a glance, either
107
+ to jump to a more detailed page or absorb short information.
108
+ </CardDescription>
109
+ </div>
110
+ </CardBody>
111
+ <CardActions>
112
+ <Button size="sm" variant="charcoal">
113
+ Primary
114
+ </Button>
115
+ <Button size="sm" variant="charcoalOutline">
116
+ Secondary
117
+ </Button>
118
+ </CardActions>
119
+ </CardContent>
120
+ </Card>
121
+ </div>
122
+ ),
123
+ };
124
+
125
+ // =============================================================================
126
+ // Variations
127
+ // =============================================================================
128
+
129
+ export const WithoutImage: Story = {
130
+ render: () => (
131
+ <div className="w-[418px]">
132
+ <Card>
133
+ <CardContent>
134
+ <CardBody>
135
+ <CardEyebrow>Category</CardEyebrow>
136
+ <div className="flex flex-col gap-[6px]">
137
+ <CardTitle>Card Without Image</CardTitle>
138
+ <CardDescription>
139
+ Cards can be used without images for text-focused content.
140
+ </CardDescription>
141
+ </div>
142
+ </CardBody>
143
+ <CardActions>
144
+ <Button size="default" variant="charcoal">
145
+ Learn More
146
+ </Button>
147
+ </CardActions>
148
+ </CardContent>
149
+ </Card>
150
+ </div>
151
+ ),
152
+ };
153
+
154
+ export const WithoutEyebrow: Story = {
155
+ render: () => (
156
+ <div className="w-[418px]">
157
+ <Card>
158
+ <CardImage />
159
+ <CardContent>
160
+ <CardBody>
161
+ <div className="flex flex-col gap-[6px]">
162
+ <CardTitle>Card Title</CardTitle>
163
+ <CardDescription>
164
+ The eyebrow is optional and can be omitted when not needed.
165
+ </CardDescription>
166
+ </div>
167
+ </CardBody>
168
+ <CardActions>
169
+ <Button size="default" variant="charcoal">
170
+ Primary
171
+ </Button>
172
+ </CardActions>
173
+ </CardContent>
174
+ </Card>
175
+ </div>
176
+ ),
177
+ };
178
+
179
+ export const WithoutActions: Story = {
180
+ render: () => (
181
+ <div className="w-[418px]">
182
+ <Card>
183
+ <CardImage />
184
+ <CardContent>
185
+ <CardBody>
186
+ <CardEyebrow>Information</CardEyebrow>
187
+ <div className="flex flex-col gap-[6px]">
188
+ <CardTitle>Informational Card</CardTitle>
189
+ <CardDescription>
190
+ Cards without actions can be used for purely informational
191
+ content that doesn't require user interaction.
192
+ </CardDescription>
193
+ </div>
194
+ </CardBody>
195
+ </CardContent>
196
+ </Card>
197
+ </div>
198
+ ),
199
+ };
200
+
201
+ export const Minimal: Story = {
202
+ render: () => (
203
+ <div className="w-[418px]">
204
+ <Card>
205
+ <CardContent>
206
+ <CardBody>
207
+ <CardTitle>Minimal Card</CardTitle>
208
+ <CardDescription>
209
+ A minimal card with just title and description.
210
+ </CardDescription>
211
+ </CardBody>
212
+ </CardContent>
213
+ </Card>
214
+ </div>
215
+ ),
216
+ };
217
+
218
+ // =============================================================================
219
+ // Grid Example
220
+ // =============================================================================
221
+
222
+ export const CardGrid: Story = {
223
+ render: () => (
224
+ <div className="grid max-w-[900px] grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
225
+ {[1, 2, 3].map((i) => (
226
+ <Card key={i}>
227
+ <CardImage />
228
+ <CardContent>
229
+ <CardBody>
230
+ <CardEyebrow>Category {i}</CardEyebrow>
231
+ <div className="flex flex-col gap-[6px]">
232
+ <CardTitle>Card Title {i}</CardTitle>
233
+ <CardDescription>
234
+ Brief description of the card content goes here.
235
+ </CardDescription>
236
+ </div>
237
+ </CardBody>
238
+ <CardActions>
239
+ <Button size="sm" variant="charcoal">
240
+ Action
241
+ </Button>
242
+ </CardActions>
243
+ </CardContent>
244
+ </Card>
245
+ ))}
246
+ </div>
247
+ ),
248
+ parameters: {
249
+ layout: "padded",
250
+ },
251
+ };
252
+
253
+ // =============================================================================
254
+ // Playground
255
+ // =============================================================================
256
+
257
+ export const Playground: Story = {
258
+ render: (args) => (
259
+ <div className="w-[418px]">
260
+ <Card {...args}>
261
+ <CardImage />
262
+ <CardContent>
263
+ <CardBody>
264
+ <CardEyebrow>Eyebrow</CardEyebrow>
265
+ <div className="flex flex-col gap-[6px]">
266
+ <CardTitle>Card Title</CardTitle>
267
+ <CardDescription>
268
+ Use cards when citizens need to scan items at a glance.
269
+ </CardDescription>
270
+ </div>
271
+ </CardBody>
272
+ <CardActions>
273
+ <Button size="default" variant="charcoal">
274
+ Primary
275
+ </Button>
276
+ <Button size="default" variant="charcoalOutline">
277
+ Secondary
278
+ </Button>
279
+ </CardActions>
280
+ </CardContent>
281
+ </Card>
282
+ </div>
283
+ ),
284
+ };
285
+ Playground.argTypes = {
286
+ layout: {
287
+ control: { type: "radio" },
288
+ options: ["vertical", "horizontal"],
289
+ },
290
+ };
291
+ Playground.args = {
292
+ layout: "vertical",
293
+ };