@nationaldesignstudio/react 0.0.10 → 0.0.12

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 (169) hide show
  1. package/dist/component-registry.md +2405 -0
  2. package/dist/components/atoms/accordion/accordion.d.ts +44 -3
  3. package/dist/components/atoms/button/button.d.ts +155 -11
  4. package/dist/components/atoms/button/icon-button.d.ts +114 -5
  5. package/dist/components/atoms/ndstudio-footer/ndstudio-footer.d.ts +30 -0
  6. package/dist/components/atoms/pager-control/pager-control.d.ts +116 -9
  7. package/dist/components/dev-tools/dev-toolbar/dev-toolbar.d.ts +4 -0
  8. package/dist/components/dev-tools/grid-overlay/grid-overlay.d.ts +6 -0
  9. package/dist/components/organisms/card/card.d.ts +40 -4
  10. package/dist/components/sections/banner/banner.d.ts +39 -6
  11. package/dist/components/sections/card-grid/card-grid.d.ts +37 -4
  12. package/dist/components/sections/faq-section/faq-section.d.ts +2 -2
  13. package/dist/components/sections/hero/hero.d.ts +167 -16
  14. package/dist/components/sections/river/river.d.ts +37 -4
  15. package/dist/components/sections/tout/tout.d.ts +86 -6
  16. package/dist/components/sections/two-column-section/two-column-section.d.ts +80 -6
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/use-event-listener.d.ts +24 -0
  19. package/dist/index.d.ts +9 -2
  20. package/dist/index.js +12034 -5934
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/theme.d.ts +330 -0
  23. package/dist/tokens.css +13930 -6368
  24. package/package.json +14 -24
  25. package/src/App.css +0 -0
  26. package/src/App.tsx +0 -7
  27. package/src/assets/fonts/PPNeueMontreal-Variable.woff2 +0 -0
  28. package/src/assets/react.svg +0 -1
  29. package/src/components/atoms/accordion/accordion.stories.tsx +0 -228
  30. package/src/components/atoms/accordion/accordion.tsx +0 -137
  31. package/src/components/atoms/accordion/index.ts +0 -6
  32. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-darwin.png +0 -0
  33. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-linux.png +0 -0
  34. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-darwin.png +0 -0
  35. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-linux.png +0 -0
  36. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-darwin.png +0 -0
  37. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-linux.png +0 -0
  38. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-darwin.png +0 -0
  39. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-linux.png +0 -0
  40. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-darwin.png +0 -0
  41. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-linux.png +0 -0
  42. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-darwin.png +0 -0
  43. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-linux.png +0 -0
  44. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-darwin.png +0 -0
  45. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-linux.png +0 -0
  46. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-darwin.png +0 -0
  47. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-linux.png +0 -0
  48. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-darwin.png +0 -0
  49. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-linux.png +0 -0
  50. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-darwin.png +0 -0
  51. package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-linux.png +0 -0
  52. package/src/components/atoms/button/button.stories.tsx +0 -84
  53. package/src/components/atoms/button/button.test.tsx +0 -141
  54. package/src/components/atoms/button/button.tsx +0 -95
  55. package/src/components/atoms/button/button.visual.test.tsx +0 -102
  56. package/src/components/atoms/button/icon-button.stories.tsx +0 -166
  57. package/src/components/atoms/button/icon-button.tsx +0 -125
  58. package/src/components/atoms/button/index.ts +0 -6
  59. package/src/components/atoms/pager-control/index.ts +0 -5
  60. package/src/components/atoms/pager-control/pager-control.stories.tsx +0 -209
  61. package/src/components/atoms/pager-control/pager-control.test.tsx +0 -149
  62. package/src/components/atoms/pager-control/pager-control.tsx +0 -328
  63. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-darwin.png +0 -0
  64. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-linux.png +0 -0
  65. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-darwin.png +0 -0
  66. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-linux.png +0 -0
  67. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-darwin.png +0 -0
  68. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-linux.png +0 -0
  69. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-darwin.png +0 -0
  70. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-linux.png +0 -0
  71. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-darwin.png +0 -0
  72. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-linux.png +0 -0
  73. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-darwin.png +0 -0
  74. package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-linux.png +0 -0
  75. package/src/components/organisms/card/card.stories.tsx +0 -293
  76. package/src/components/organisms/card/card.test.tsx +0 -245
  77. package/src/components/organisms/card/card.tsx +0 -227
  78. package/src/components/organisms/card/card.visual.test.tsx +0 -197
  79. package/src/components/organisms/card/index.ts +0 -19
  80. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-darwin.png +0 -0
  81. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-linux.png +0 -0
  82. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-darwin.png +0 -0
  83. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-linux.png +0 -0
  84. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-darwin.png +0 -0
  85. package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-linux.png +0 -0
  86. package/src/components/organisms/navbar/index.ts +0 -18
  87. package/src/components/organisms/navbar/navbar.stories.tsx +0 -313
  88. package/src/components/organisms/navbar/navbar.test.tsx +0 -190
  89. package/src/components/organisms/navbar/navbar.tsx +0 -317
  90. package/src/components/organisms/navbar/navbar.visual.test.tsx +0 -85
  91. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-darwin.png +0 -0
  92. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-linux.png +0 -0
  93. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-darwin.png +0 -0
  94. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-linux.png +0 -0
  95. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-darwin.png +0 -0
  96. package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-linux.png +0 -0
  97. package/src/components/organisms/us-gov-banner/index.ts +0 -1
  98. package/src/components/organisms/us-gov-banner/us-gov-banner.stories.tsx +0 -35
  99. package/src/components/organisms/us-gov-banner/us-gov-banner.test.tsx +0 -107
  100. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +0 -73
  101. package/src/components/organisms/us-gov-banner/us-gov-banner.visual.test.tsx +0 -46
  102. package/src/components/sections/banner/banner.stories.tsx +0 -150
  103. package/src/components/sections/banner/banner.test.tsx +0 -185
  104. package/src/components/sections/banner/banner.tsx +0 -130
  105. package/src/components/sections/banner/index.ts +0 -2
  106. package/src/components/sections/card-grid/card-grid.stories.tsx +0 -351
  107. package/src/components/sections/card-grid/card-grid.tsx +0 -118
  108. package/src/components/sections/card-grid/index.ts +0 -1
  109. package/src/components/sections/faq-section/faq-section.tsx +0 -77
  110. package/src/components/sections/faq-section/index.ts +0 -2
  111. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-darwin.png +0 -0
  112. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-linux.png +0 -0
  113. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-darwin.png +0 -0
  114. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-linux.png +0 -0
  115. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-darwin.png +0 -0
  116. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-linux.png +0 -0
  117. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-darwin.png +0 -0
  118. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-linux.png +0 -0
  119. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-darwin.png +0 -0
  120. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-linux.png +0 -0
  121. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-darwin.png +0 -0
  122. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-linux.png +0 -0
  123. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-darwin.png +0 -0
  124. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-linux.png +0 -0
  125. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-darwin.png +0 -0
  126. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-linux.png +0 -0
  127. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-darwin.png +0 -0
  128. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-linux.png +0 -0
  129. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-darwin.png +0 -0
  130. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-linux.png +0 -0
  131. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-default-chromium-linux.png +0 -0
  132. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-darwin.png +0 -0
  133. package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-linux.png +0 -0
  134. package/src/components/sections/hero/hero.stories.tsx +0 -145
  135. package/src/components/sections/hero/hero.test.tsx +0 -135
  136. package/src/components/sections/hero/hero.tsx +0 -191
  137. package/src/components/sections/hero/hero.visual.test.tsx +0 -140
  138. package/src/components/sections/hero/index.ts +0 -1
  139. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-darwin.png +0 -0
  140. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-linux.png +0 -0
  141. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-darwin.png +0 -0
  142. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-linux.png +0 -0
  143. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-darwin.png +0 -0
  144. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-linux.png +0 -0
  145. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-darwin.png +0 -0
  146. package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-linux.png +0 -0
  147. package/src/components/sections/prose/index.ts +0 -6
  148. package/src/components/sections/prose/prose.stories.tsx +0 -144
  149. package/src/components/sections/prose/prose.test.tsx +0 -178
  150. package/src/components/sections/prose/prose.tsx +0 -88
  151. package/src/components/sections/prose/prose.visual.test.tsx +0 -105
  152. package/src/components/sections/river/index.ts +0 -1
  153. package/src/components/sections/river/river.stories.tsx +0 -237
  154. package/src/components/sections/river/river.test.tsx +0 -268
  155. package/src/components/sections/river/river.tsx +0 -175
  156. package/src/components/sections/tout/index.ts +0 -1
  157. package/src/components/sections/tout/tout.stories.tsx +0 -154
  158. package/src/components/sections/tout/tout.test.tsx +0 -242
  159. package/src/components/sections/tout/tout.tsx +0 -206
  160. package/src/components/sections/two-column-section/index.ts +0 -5
  161. package/src/components/sections/two-column-section/two-column-section.stories.tsx +0 -285
  162. package/src/components/sections/two-column-section/two-column-section.tsx +0 -152
  163. package/src/index.ts +0 -98
  164. package/src/lib/utils.ts +0 -6
  165. package/src/main.tsx +0 -13
  166. package/src/stories/Introduction.mdx +0 -114
  167. package/src/stories/TokenShowcase.stories.tsx +0 -92
  168. package/src/stories/TokenShowcase.tsx +0 -1352
  169. package/src/styles.css +0 -11
@@ -1,317 +0,0 @@
1
- import { Dialog } from "@base-ui-components/react/dialog";
2
- import { Slot } from "@radix-ui/react-slot";
3
- import * as React from "react";
4
- import { cn } from "@/lib/utils";
5
-
6
- // Context for mobile menu state
7
- const NavbarContext = React.createContext<{
8
- isMobileMenuOpen: boolean;
9
- setIsMobileMenuOpen: (open: boolean) => void;
10
- }>({
11
- isMobileMenuOpen: false,
12
- setIsMobileMenuOpen: () => {},
13
- });
14
-
15
- export interface NavbarProps extends React.HTMLAttributes<HTMLElement> {}
16
-
17
- /**
18
- * Main navigation bar container.
19
- * Provides responsive layout for brand, links, and actions.
20
- */
21
- const Navbar = React.forwardRef<HTMLElement, NavbarProps>(
22
- ({ className, children, ...props }, ref) => {
23
- const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
24
- const navRef = React.useRef<HTMLElement>(null);
25
-
26
- // Set navbar height CSS variable for mobile menu positioning
27
- React.useEffect(() => {
28
- const updateHeight = () => {
29
- if (navRef.current) {
30
- const height = navRef.current.offsetHeight;
31
- document.documentElement.style.setProperty(
32
- "--navbar-height",
33
- `${height}px`,
34
- );
35
- }
36
- };
37
-
38
- updateHeight();
39
- window.addEventListener("resize", updateHeight);
40
- return () => window.removeEventListener("resize", updateHeight);
41
- }, []);
42
-
43
- // Merge refs
44
- const mergedRef = React.useCallback(
45
- (node: HTMLElement | null) => {
46
- (navRef as React.MutableRefObject<HTMLElement | null>).current = node;
47
- if (typeof ref === "function") {
48
- ref(node);
49
- } else if (ref) {
50
- ref.current = node;
51
- }
52
- },
53
- [ref],
54
- );
55
-
56
- return (
57
- <NavbarContext.Provider value={{ isMobileMenuOpen, setIsMobileMenuOpen }}>
58
- <nav
59
- ref={mergedRef}
60
- className={cn("relative z-50 w-full bg-gray-50", className)}
61
- {...props}
62
- >
63
- <div
64
- className={cn(
65
- "flex w-full items-center justify-between",
66
- // Mobile
67
- "px-spacing-20 py-spacing-8",
68
- // Tablet
69
- "md:px-spacing-32 md:py-spacing-16",
70
- // Desktop
71
- "xl:px-spacing-32 xl:py-spacing-24",
72
- )}
73
- >
74
- {children}
75
- </div>
76
- </nav>
77
- </NavbarContext.Provider>
78
- );
79
- },
80
- );
81
- Navbar.displayName = "Navbar";
82
-
83
- export interface NavbarBrandProps extends React.HTMLAttributes<HTMLDivElement> {
84
- asChild?: boolean;
85
- }
86
-
87
- /**
88
- * Brand/logo area of the navbar.
89
- * Use asChild to render as a link.
90
- */
91
- const NavbarBrand = React.forwardRef<HTMLDivElement, NavbarBrandProps>(
92
- ({ className, asChild = false, ...props }, ref) => {
93
- const Comp = asChild ? Slot : "div";
94
- return <Comp ref={ref} className={cn("shrink-0", className)} {...props} />;
95
- },
96
- );
97
- NavbarBrand.displayName = "NavbarBrand";
98
-
99
- export interface NavbarLinksProps
100
- extends React.HTMLAttributes<HTMLDivElement> {}
101
-
102
- /**
103
- * Container for navigation links.
104
- * Centers links on desktop, hidden on mobile (use NavbarMobileMenu instead).
105
- */
106
- const NavbarLinks = React.forwardRef<HTMLDivElement, NavbarLinksProps>(
107
- ({ className, ...props }, ref) => {
108
- return (
109
- <div
110
- ref={ref}
111
- className={cn("hidden items-center gap-spacing-40 md:flex", className)}
112
- {...props}
113
- />
114
- );
115
- },
116
- );
117
- NavbarLinks.displayName = "NavbarLinks";
118
-
119
- export interface NavbarLinkProps
120
- extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
121
- asChild?: boolean;
122
- active?: boolean;
123
- }
124
-
125
- /**
126
- * Individual navigation link.
127
- * Use asChild to render with a router Link component.
128
- */
129
- const NavbarLink = React.forwardRef<HTMLAnchorElement, NavbarLinkProps>(
130
- ({ className, asChild = false, active, ...props }, ref) => {
131
- const Comp = asChild ? Slot : "a";
132
- return (
133
- <Comp
134
- ref={ref}
135
- className={cn(
136
- "typography-brand-medium-link-small text-gray-900 transition-colors hover:text-gray-700",
137
- active && "text-gray-1100",
138
- className,
139
- )}
140
- {...props}
141
- />
142
- );
143
- },
144
- );
145
- NavbarLink.displayName = "NavbarLink";
146
-
147
- export interface NavbarActionsProps
148
- extends React.HTMLAttributes<HTMLDivElement> {}
149
-
150
- /**
151
- * Container for navbar action items (search, menu button, etc).
152
- */
153
- const NavbarActions = React.forwardRef<HTMLDivElement, NavbarActionsProps>(
154
- ({ className, ...props }, ref) => {
155
- return (
156
- <div
157
- ref={ref}
158
- className={cn("flex items-center gap-spacing-8", className)}
159
- {...props}
160
- />
161
- );
162
- },
163
- );
164
- NavbarActions.displayName = "NavbarActions";
165
-
166
- export interface NavbarMobileMenuProps
167
- extends React.HTMLAttributes<HTMLDivElement> {}
168
-
169
- /**
170
- * Mobile menu container that displays navigation links on mobile devices.
171
- * Hidden on desktop (md and above). Should be used with NavbarMobileMenuButton.
172
- * Built on Base UI Dialog for accessibility (focus trap, escape key, click-outside).
173
- */
174
- const NavbarMobileMenu = React.forwardRef<
175
- HTMLDivElement,
176
- NavbarMobileMenuProps
177
- >(({ className, children, ...props }, ref) => {
178
- const { isMobileMenuOpen, setIsMobileMenuOpen } =
179
- React.useContext(NavbarContext);
180
-
181
- return (
182
- <Dialog.Root open={isMobileMenuOpen} onOpenChange={setIsMobileMenuOpen}>
183
- <Dialog.Portal>
184
- <Dialog.Popup
185
- ref={ref}
186
- className={cn(
187
- "fixed inset-0 z-50 bg-gray-50 md:hidden",
188
- // Position below navbar
189
- "pt-[calc(var(--navbar-height,60px)+1px)]",
190
- // Smooth transition
191
- "transition-opacity duration-200",
192
- className,
193
- )}
194
- {...props}
195
- >
196
- <Dialog.Title className="sr-only">Navigation menu</Dialog.Title>
197
- <div
198
- className={cn(
199
- "flex flex-col",
200
- // Padding matching navbar
201
- "px-spacing-20 py-spacing-16",
202
- // Gap between links
203
- "gap-spacing-8",
204
- )}
205
- >
206
- {children}
207
- </div>
208
- </Dialog.Popup>
209
- </Dialog.Portal>
210
- </Dialog.Root>
211
- );
212
- });
213
- NavbarMobileMenu.displayName = "NavbarMobileMenu";
214
-
215
- export interface NavbarMobileMenuButtonProps
216
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
217
- asChild?: boolean;
218
- }
219
-
220
- /**
221
- * Button to toggle the mobile menu.
222
- * Should be placed in NavbarActions on mobile.
223
- * Use asChild to render as a custom button component (e.g., IconButton).
224
- */
225
- const NavbarMobileMenuButton = React.forwardRef<
226
- HTMLButtonElement,
227
- NavbarMobileMenuButtonProps
228
- >(({ className, asChild = false, children, ...props }, ref) => {
229
- const { isMobileMenuOpen, setIsMobileMenuOpen } =
230
- React.useContext(NavbarContext);
231
-
232
- const handleClick = () => {
233
- setIsMobileMenuOpen(!isMobileMenuOpen);
234
- };
235
-
236
- if (asChild) {
237
- return (
238
- <Slot
239
- ref={ref}
240
- aria-label="Toggle navigation menu"
241
- aria-expanded={isMobileMenuOpen}
242
- aria-controls="navbar-mobile-menu"
243
- onClick={handleClick}
244
- className={className}
245
- {...props}
246
- >
247
- {children}
248
- </Slot>
249
- );
250
- }
251
-
252
- return (
253
- <button
254
- ref={ref}
255
- type="button"
256
- aria-label="Toggle navigation menu"
257
- aria-expanded={isMobileMenuOpen}
258
- aria-controls="navbar-mobile-menu"
259
- onClick={handleClick}
260
- className={cn("transition-colors", className)}
261
- {...props}
262
- >
263
- {children}
264
- </button>
265
- );
266
- });
267
- NavbarMobileMenuButton.displayName = "NavbarMobileMenuButton";
268
-
269
- export interface NavbarMobileMenuLinkProps
270
- extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
271
- asChild?: boolean;
272
- active?: boolean;
273
- }
274
-
275
- /**
276
- * Navigation link for the mobile menu.
277
- * Automatically closes the mobile menu when clicked.
278
- * Use asChild to render with a router Link component.
279
- */
280
- const NavbarMobileMenuLink = React.forwardRef<
281
- HTMLAnchorElement,
282
- NavbarMobileMenuLinkProps
283
- >(({ className, asChild = false, active, onClick, ...props }, ref) => {
284
- const { setIsMobileMenuOpen } = React.useContext(NavbarContext);
285
- const Comp = asChild ? Slot : "a";
286
-
287
- const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
288
- setIsMobileMenuOpen(false);
289
- onClick?.(event);
290
- };
291
-
292
- return (
293
- <Comp
294
- ref={ref}
295
- className={cn(
296
- "typography-brand-medium-link-medium text-gray-900 transition-colors hover:text-gray-700",
297
- "py-spacing-8",
298
- active && "text-gray-1100",
299
- className,
300
- )}
301
- onClick={handleClick}
302
- {...props}
303
- />
304
- );
305
- });
306
- NavbarMobileMenuLink.displayName = "NavbarMobileMenuLink";
307
-
308
- export {
309
- Navbar,
310
- NavbarBrand,
311
- NavbarLinks,
312
- NavbarLink,
313
- NavbarActions,
314
- NavbarMobileMenu,
315
- NavbarMobileMenuButton,
316
- NavbarMobileMenuLink,
317
- };
@@ -1,85 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { page } from "vitest/browser";
3
- import { render } from "vitest-browser-react";
4
- import { Button } from "../../atoms/button";
5
- import {
6
- Navbar,
7
- NavbarActions,
8
- NavbarBrand,
9
- NavbarLink,
10
- NavbarLinks,
11
- } from "./navbar";
12
-
13
- describe("Navbar Visual Regression", () => {
14
- test("default navbar renders correctly", async () => {
15
- await page.viewport(1280, 800);
16
-
17
- render(
18
- <Navbar data-testid="navbar">
19
- <NavbarBrand>
20
- <span className="text-xl font-bold">Logo</span>
21
- </NavbarBrand>
22
- <NavbarLinks>
23
- <NavbarLink href="#">Home</NavbarLink>
24
- <NavbarLink href="#">About</NavbarLink>
25
- <NavbarLink href="#">Services</NavbarLink>
26
- <NavbarLink href="#">Contact</NavbarLink>
27
- </NavbarLinks>
28
- <NavbarActions>
29
- <Button size="sm" variant="charcoal">
30
- Sign In
31
- </Button>
32
- </NavbarActions>
33
- </Navbar>,
34
- );
35
-
36
- await expect(page.getByTestId("navbar")).toMatchScreenshot(
37
- "navbar-default",
38
- );
39
- });
40
-
41
- test("navbar with active link renders correctly", async () => {
42
- await page.viewport(1280, 800);
43
-
44
- render(
45
- <Navbar data-testid="navbar">
46
- <NavbarBrand>
47
- <span className="text-xl font-bold">Logo</span>
48
- </NavbarBrand>
49
- <NavbarLinks>
50
- <NavbarLink href="#" active>
51
- Home
52
- </NavbarLink>
53
- <NavbarLink href="#">About</NavbarLink>
54
- <NavbarLink href="#">Services</NavbarLink>
55
- <NavbarLink href="#">Contact</NavbarLink>
56
- </NavbarLinks>
57
- <NavbarActions>
58
- <Button size="sm" variant="charcoal">
59
- Sign In
60
- </Button>
61
- </NavbarActions>
62
- </Navbar>,
63
- );
64
-
65
- await expect(page.getByTestId("navbar")).toMatchScreenshot(
66
- "navbar-active-link",
67
- );
68
- });
69
-
70
- test("navbar brand only renders correctly", async () => {
71
- await page.viewport(1280, 800);
72
-
73
- render(
74
- <Navbar data-testid="navbar">
75
- <NavbarBrand>
76
- <span className="text-xl font-bold">Brand Only</span>
77
- </NavbarBrand>
78
- </Navbar>,
79
- );
80
-
81
- await expect(page.getByTestId("navbar")).toMatchScreenshot(
82
- "navbar-brand-only",
83
- );
84
- });
85
- });
@@ -1 +0,0 @@
1
- export { USGovBanner, type USGovBannerProps } from "./us-gov-banner";
@@ -1,35 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { USGovBanner } from ".";
3
-
4
- const meta: Meta<typeof USGovBanner> = {
5
- title: "Organisms/USGovBanner",
6
- component: USGovBanner,
7
- parameters: {
8
- layout: "fullscreen",
9
- },
10
- } as Meta<typeof USGovBanner>;
11
-
12
- export default meta;
13
- type Story = StoryObj<typeof USGovBanner>;
14
-
15
- export const Default: Story = {
16
- render: () => <USGovBanner />,
17
- };
18
-
19
- export const CustomText: Story = {
20
- render: () => (
21
- <USGovBanner text="An official website of the U.S. Department of State" />
22
- ),
23
- };
24
-
25
- export const Playground: Story = {
26
- render: (args) => <USGovBanner {...args} />,
27
- };
28
- Playground.argTypes = {
29
- text: {
30
- control: { type: "text" },
31
- },
32
- };
33
- Playground.args = {
34
- text: "An official website of the United States government",
35
- };
@@ -1,107 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { page } from "vitest/browser";
3
- import { render } from "vitest-browser-react";
4
- import { USGovBanner } from "./us-gov-banner";
5
-
6
- describe("USGovBanner", () => {
7
- describe("Accessibility", () => {
8
- test("banner text is readable", async () => {
9
- render(<USGovBanner />);
10
-
11
- await expect
12
- .element(
13
- page.getByText("An official website of the United States government"),
14
- )
15
- .toBeInTheDocument();
16
- });
17
-
18
- test("flag icon is hidden from screen readers", async () => {
19
- render(<USGovBanner data-testid="banner" />);
20
-
21
- // The default flag SVG has aria-hidden="true"
22
- const banner = page.getByTestId("banner");
23
- await expect.element(banner).toBeInTheDocument();
24
-
25
- // Verify the SVG is present but decorative
26
- const svg = banner.element().querySelector("svg");
27
- expect(svg?.getAttribute("aria-hidden")).toBe("true");
28
- });
29
-
30
- test("custom text is readable", async () => {
31
- render(<USGovBanner text="Custom government message" />);
32
-
33
- await expect
34
- .element(page.getByText("Custom government message"))
35
- .toBeInTheDocument();
36
- });
37
- });
38
-
39
- describe("Props", () => {
40
- test("renders default text when no text prop provided", async () => {
41
- render(<USGovBanner />);
42
-
43
- await expect
44
- .element(
45
- page.getByText("An official website of the United States government"),
46
- )
47
- .toBeInTheDocument();
48
- });
49
-
50
- test("renders custom text when provided", async () => {
51
- render(<USGovBanner text="An official California website" />);
52
-
53
- await expect
54
- .element(page.getByText("An official California website"))
55
- .toBeInTheDocument();
56
- });
57
-
58
- test("renders custom flag icon when provided", async () => {
59
- render(
60
- <USGovBanner flagIcon={<span data-testid="custom-icon">🏛️</span>} />,
61
- );
62
-
63
- await expect.element(page.getByTestId("custom-icon")).toBeInTheDocument();
64
- });
65
-
66
- test("supports custom className", async () => {
67
- render(<USGovBanner className="custom-banner" data-testid="banner" />);
68
-
69
- const banner = page.getByTestId("banner");
70
- await expect.element(banner).toHaveClass(/custom-banner/);
71
- });
72
-
73
- test("spreads additional props", async () => {
74
- render(
75
- <USGovBanner data-testid="banner" aria-label="Government banner" />,
76
- );
77
-
78
- const banner = page.getByTestId("banner");
79
- await expect
80
- .element(banner)
81
- .toHaveAttribute("aria-label", "Government banner");
82
- });
83
- });
84
-
85
- describe("Styling", () => {
86
- test("has light background by default", async () => {
87
- render(<USGovBanner data-testid="banner" />);
88
-
89
- const banner = page.getByTestId("banner");
90
- await expect.element(banner).toHaveClass(/bg-gray-50/);
91
- });
92
-
93
- test("is centered", async () => {
94
- render(<USGovBanner data-testid="banner" />);
95
-
96
- const banner = page.getByTestId("banner");
97
- await expect.element(banner).toHaveClass(/justify-center/);
98
- });
99
-
100
- test("spans full width", async () => {
101
- render(<USGovBanner data-testid="banner" />);
102
-
103
- const banner = page.getByTestId("banner");
104
- await expect.element(banner).toHaveClass(/w-full/);
105
- });
106
- });
107
- });
@@ -1,73 +0,0 @@
1
- import * as React from "react";
2
- import { cn } from "@/lib/utils";
3
-
4
- export interface USGovBannerProps extends React.HTMLAttributes<HTMLDivElement> {
5
- /**
6
- * Custom flag icon element. Defaults to a US flag SVG.
7
- */
8
- flagIcon?: React.ReactNode;
9
- /**
10
- * Banner text content
11
- * @default "An official website of the United States government"
12
- */
13
- text?: string;
14
- }
15
-
16
- /**
17
- * US Government official website banner.
18
- * Displays the official government website notice with flag icon.
19
- * Commonly placed at the very top of government websites.
20
- */
21
- const USGovBanner = React.forwardRef<HTMLDivElement, USGovBannerProps>(
22
- (
23
- {
24
- className,
25
- flagIcon,
26
- text = "An official website of the United States government",
27
- ...props
28
- },
29
- ref,
30
- ) => {
31
- return (
32
- <div
33
- ref={ref}
34
- className={cn(
35
- "flex w-full items-center justify-center bg-gray-50 py-spacing-12",
36
- className,
37
- )}
38
- {...props}
39
- >
40
- <div className="flex items-center gap-spacing-8 opacity-70">
41
- {flagIcon ?? <DefaultUSFlag />}
42
- <p className="text-[11px] leading-[13px] tracking-[0.17px] text-gray-900">
43
- {text}
44
- </p>
45
- </div>
46
- </div>
47
- );
48
- },
49
- );
50
- USGovBanner.displayName = "USGovBanner";
51
-
52
- function DefaultUSFlag() {
53
- return (
54
- <svg
55
- width="16"
56
- height="12"
57
- viewBox="0 0 16 12"
58
- fill="none"
59
- xmlns="http://www.w3.org/2000/svg"
60
- aria-hidden="true"
61
- >
62
- <path d="M0 0H16V12H0V0Z" fill="#212121" fillOpacity="0.1" />
63
- <path
64
- d="M0 0H16V0.923077H0V0ZM0 1.84615H16V2.76923H0V1.84615ZM0 3.69231H16V4.61538H0V3.69231ZM0 5.53846H16V6.46154H0V5.53846ZM0 7.38462H16V8.30769H0V7.38462ZM0 9.23077H16V10.1538H0V9.23077ZM0 11.0769H16V12H0V11.0769Z"
65
- fill="#BF0A30"
66
- fillOpacity="0.8"
67
- />
68
- <path d="M0 0H8V6.46154H0V0Z" fill="#002868" fillOpacity="0.8" />
69
- </svg>
70
- );
71
- }
72
-
73
- export { USGovBanner };
@@ -1,46 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { page } from "vitest/browser";
3
- import { render } from "vitest-browser-react";
4
- import { USGovBanner } from "./us-gov-banner";
5
-
6
- describe("USGovBanner Visual Regression", () => {
7
- test("default banner renders correctly", async () => {
8
- await page.viewport(1280, 800);
9
-
10
- render(<USGovBanner data-testid="banner" />);
11
-
12
- await expect(page.getByTestId("banner")).toMatchScreenshot(
13
- "us-gov-banner-default",
14
- );
15
- });
16
-
17
- test("banner with custom text renders correctly", async () => {
18
- await page.viewport(1280, 800);
19
-
20
- render(
21
- <USGovBanner
22
- data-testid="banner"
23
- text="An official website of the State of California"
24
- />,
25
- );
26
-
27
- await expect(page.getByTestId("banner")).toMatchScreenshot(
28
- "us-gov-banner-custom-text",
29
- );
30
- });
31
-
32
- test("banner with custom flag icon renders correctly", async () => {
33
- await page.viewport(1280, 800);
34
-
35
- render(
36
- <USGovBanner
37
- data-testid="banner"
38
- flagIcon={<span aria-hidden="true">🏛️</span>}
39
- />,
40
- );
41
-
42
- await expect(page.getByTestId("banner")).toMatchScreenshot(
43
- "us-gov-banner-custom-icon",
44
- );
45
- });
46
- });