nextworks 0.2.0-alpha.2 → 0.2.0-alpha.21

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 (145) hide show
  1. package/README.md +280 -228
  2. package/dist/cli_manifests/blocks_manifest.json +193 -194
  3. package/dist/commands/blocks.d.ts +3 -0
  4. package/dist/commands/blocks.d.ts.map +1 -1
  5. package/dist/commands/blocks.js +128 -19
  6. package/dist/commands/blocks.js.map +1 -1
  7. package/dist/commands/doctor.d.ts +136 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +696 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/remove-blocks.d.ts +4 -1
  12. package/dist/commands/remove-blocks.d.ts.map +1 -1
  13. package/dist/commands/remove-blocks.js +24 -6
  14. package/dist/commands/remove-blocks.js.map +1 -1
  15. package/dist/index.js +125 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +104 -194
  18. package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +115 -75
  19. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +269 -251
  20. package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
  21. package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -36
  22. package/dist/kits/blocks/app/templates/digitalagency/components/About.tsx +71 -99
  23. package/dist/kits/blocks/app/templates/digitalagency/components/CTA.tsx +40 -74
  24. package/dist/kits/blocks/app/templates/digitalagency/components/Contact.tsx +123 -227
  25. package/dist/kits/blocks/app/templates/digitalagency/components/Footer.tsx +89 -89
  26. package/dist/kits/blocks/app/templates/digitalagency/components/Hero.tsx +83 -90
  27. package/dist/kits/blocks/app/templates/digitalagency/components/Navbar.tsx +121 -168
  28. package/dist/kits/blocks/app/templates/digitalagency/components/NetworkPattern.tsx +288 -297
  29. package/dist/kits/blocks/app/templates/digitalagency/components/Portfolio.tsx +157 -157
  30. package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
  31. package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
  32. package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
  33. package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
  34. package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
  35. package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
  36. package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +130 -85
  37. package/dist/kits/blocks/app/templates/gallery/page.tsx +357 -303
  38. package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -74
  39. package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -55
  40. package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -178
  41. package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -93
  42. package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
  43. package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -93
  44. package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -84
  45. package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
  46. package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -89
  47. package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -162
  48. package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
  49. package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
  50. package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
  51. package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
  52. package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -89
  53. package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
  54. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
  55. package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
  56. package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -38
  57. package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -176
  58. package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
  59. package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
  60. package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -91
  61. package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
  62. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -105
  63. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -127
  64. package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -159
  65. package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
  66. package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -97
  67. package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
  68. package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
  69. package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
  70. package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -183
  71. package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
  72. package/dist/kits/blocks/components/sections/About.tsx +291 -291
  73. package/dist/kits/blocks/components/sections/CTA.tsx +257 -258
  74. package/dist/kits/blocks/components/sections/CommandShowcase.tsx +517 -0
  75. package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
  76. package/dist/kits/blocks/components/sections/FAQ.tsx +214 -225
  77. package/dist/kits/blocks/components/sections/FeaturedProjectShowcase.tsx +687 -0
  78. package/dist/kits/blocks/components/sections/Features.tsx +268 -270
  79. package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
  80. package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
  81. package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
  82. package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +246 -0
  83. package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
  84. package/dist/kits/blocks/components/sections/HeroWithVideo.tsx +495 -0
  85. package/dist/kits/blocks/components/sections/Navbar.tsx +352 -353
  86. package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -550
  87. package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
  88. package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
  89. package/dist/kits/blocks/components/sections/ProjectDeepDive.tsx +805 -0
  90. package/dist/kits/blocks/components/sections/SelectedWorkRail.tsx +485 -0
  91. package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
  92. package/dist/kits/blocks/components/sections/Team.tsx +309 -309
  93. package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
  94. package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
  95. package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +128 -0
  96. package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +519 -0
  97. package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +140 -0
  98. package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +130 -0
  99. package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +430 -0
  100. package/dist/kits/blocks/components/sections/product-demo/TaskListPanel.tsx +302 -0
  101. package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +744 -0
  102. package/dist/kits/blocks/components/sections/product-demo/types.ts +262 -0
  103. package/dist/kits/blocks/components/theme-provider.tsx +1 -34
  104. package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
  105. package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
  106. package/dist/kits/blocks/components/ui/button.tsx +122 -122
  107. package/dist/kits/blocks/components/ui/card.tsx +95 -95
  108. package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
  109. package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
  110. package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
  111. package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
  112. package/dist/kits/blocks/components/ui/input.tsx +27 -27
  113. package/dist/kits/blocks/components/ui/label.tsx +29 -29
  114. package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
  115. package/dist/kits/blocks/components/ui/select.tsx +25 -25
  116. package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
  117. package/dist/kits/blocks/components/ui/table.tsx +98 -98
  118. package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
  119. package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
  120. package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -247
  121. package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
  122. package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
  123. package/dist/kits/blocks/lib/themes.ts +400 -400
  124. package/dist/kits/blocks/lib/utils.ts +6 -9
  125. package/dist/kits/blocks/package-deps.json +40 -28
  126. package/dist/kits/blocks/tsconfig.json +11 -0
  127. package/dist/kits/blocks/tsconfig.tsbuildinfo +1 -0
  128. package/dist/utils/file-operations.d.ts +3 -1
  129. package/dist/utils/file-operations.d.ts.map +1 -1
  130. package/dist/utils/file-operations.js +84 -12
  131. package/dist/utils/file-operations.js.map +1 -1
  132. package/dist/utils/installation-tracker.d.ts +2 -2
  133. package/dist/utils/installation-tracker.d.ts.map +1 -1
  134. package/dist/utils/installation-tracker.js +11 -11
  135. package/dist/utils/installation-tracker.js.map +1 -1
  136. package/dist/utils/package-manager.d.ts +6 -0
  137. package/dist/utils/package-manager.d.ts.map +1 -0
  138. package/dist/utils/package-manager.js +58 -0
  139. package/dist/utils/package-manager.js.map +1 -0
  140. package/dist/utils/yarn-pnp.d.ts +6 -0
  141. package/dist/utils/yarn-pnp.d.ts.map +1 -0
  142. package/dist/utils/yarn-pnp.js +39 -0
  143. package/dist/utils/yarn-pnp.js.map +1 -0
  144. package/package.json +5 -2
  145. package/dist/kits/blocks/notes/THEME_GUIDE.md +0 -29
@@ -1,353 +1,352 @@
1
- // components/sections/Navbar.tsx
2
-
3
- "use client";
4
-
5
- import React, { useEffect, useRef, useState } from "react";
6
- import Link from "next/link";
7
- import { Button } from "@/components/ui/button";
8
- import {
9
- ThemeToggle,
10
- type ThemeToggleProps,
11
- } from "@/components/ui/theme-toggle";
12
- import { cn } from "@/lib/utils";
13
- import { Menu, X } from "lucide-react";
14
-
15
- /**
16
- * Props for the Navbar component.
17
- *
18
- * @remarks
19
- * Slot-style className overrides are available for structural parts. The mobile
20
- * menu locks body scroll when open and restores it on close/unmount.
21
- *
22
- * @public
23
- */
24
- export interface NavbarProps {
25
- /** Optional id to attach to the root nav element */
26
- id?: string;
27
-
28
- /** The height of the navbar as a Tailwind class. Example: "h-16" or "h-[13vh]" */
29
- navHeight?: string;
30
-
31
- /**
32
- * The text displayed as the brand/logo.
33
- * @defaultValue "Brand Name"
34
- */
35
- brand?: string;
36
- /** Optional custom brand node (e.g., logo). Rendered left of brand text */
37
- brandNode?: React.ReactNode;
38
-
39
- /** Navigation links array. Each item has a label and href */
40
- menuItems?: { label: string; href: string }[];
41
-
42
- /**
43
- * Call to action button config or null to hide it.
44
- * Example: { label: "Get Started", href: "#contact" }
45
- */
46
- ctaButton?: { label: string; href: string } | null;
47
-
48
- /**
49
- * Whether to display the color mode toggle.
50
- * @defaultValue true
51
- */
52
- showColorModeToggle?: boolean;
53
-
54
- /**
55
- * Whether navbar should stick to top when scrolling.
56
- * @defaultValue true
57
- */
58
- sticky?: boolean;
59
-
60
- /** Optional top-level class to override the nav root */
61
- className?: string;
62
-
63
- /** Styling configuration objects */
64
- nav?: {
65
- className?: string;
66
- };
67
-
68
- brandText?: {
69
- className?: string;
70
- };
71
- links?: {
72
- className?: string;
73
- };
74
- ctaButtonStyle?: {
75
- unstyled?: boolean;
76
- style?: React.CSSProperties;
77
- variant?:
78
- | "default"
79
- | "destructive"
80
- | "outline"
81
- | "secondary"
82
- | "ghost"
83
- | "link";
84
- size?: "default" | "sm" | "lg" | "icon";
85
- className?: string;
86
- };
87
- mobileMenu?: {
88
- className?: string;
89
- };
90
-
91
- /* Additional style slots for targeted overrides */
92
- container?: { className?: string };
93
- brandWrapper?: { className?: string };
94
- desktopMenu?: { className?: string };
95
- toggleButton?: { className?: string };
96
- colorModeWrapper?: { className?: string };
97
- ctaButtonWrapper?: { className?: string };
98
- mobileMenuInner?: { className?: string };
99
- /** Additional class overrides for mobile menu link hover/background, etc. */
100
- mobileLinks?: { className?: string };
101
-
102
- /** Props forwarded to ThemeToggle so templates can fully style it */
103
- themeToggle?: ThemeToggleProps;
104
-
105
- /** ARIA label for the navbar. */
106
- ariaLabel?: string;
107
- }
108
-
109
- const defaultMenuItems = [
110
- { label: "Home", href: "#home" },
111
- { label: "Features", href: "#features" },
112
- { label: "Pricing", href: "#pricing" },
113
- { label: "FAQ", href: "#faq" },
114
- { label: "Contact", href: "#contact" },
115
- ];
116
-
117
- /**
118
- * Responsive navbar with brand, menu links, color mode toggle, and optional CTA.
119
- *
120
- * @remarks
121
- * - Sticky behavior is enabled by default (sticky top-0 z-50).
122
- * - Mobile menu toggles with a button and traps body scroll while open.
123
- * - Accessibility: Focus rings are applied to interactive elements; aria
124
- * attributes are set on the toggle button for state.
125
- *
126
- * @example
127
- * <Navbar brand="Acme" />
128
- */
129
- export function Navbar({
130
- id,
131
- navHeight = "h-16",
132
- brand = "Brand Name",
133
- brandNode,
134
- menuItems = defaultMenuItems,
135
- ctaButton = { label: "Get Started", href: "#contact" },
136
- showColorModeToggle = true,
137
- sticky = true,
138
- className,
139
- nav = {
140
- className: "bg-background text-foreground border-b border-border",
141
- },
142
- brandText = {
143
- className: "text-2xl font-bold text-primary",
144
- },
145
- links = {
146
- className:
147
- "text-base font-normal text-foreground hover:text-gray-500 dark:hover-text-gray-400 transition-colors",
148
- },
149
- ctaButtonStyle = {
150
- variant: "default",
151
- size: "default",
152
- className:
153
- "shadow-lg hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5",
154
- },
155
- mobileMenu = {
156
- className: "border-t border-border",
157
- },
158
-
159
- /* new slot defaults */
160
- container = { className: "" },
161
- brandWrapper = { className: "" },
162
- desktopMenu = {
163
- className: "hidden items-center space-x-1 lg:flex lg:space-x-6",
164
- },
165
- toggleButton = {
166
- className:
167
- "hover:bg-accent flex items-center justify-center rounded-md p-2 transition-colors",
168
- },
169
- colorModeWrapper = { className: "ml-2" },
170
- ctaButtonWrapper = { className: "ml-2" },
171
- mobileMenuInner = { className: "space-y-2 px-4 pt-2 pb-4" },
172
- mobileLinks = { className: "hover:bg-accent" },
173
-
174
- themeToggle = {},
175
-
176
- ariaLabel = "Main navigation",
177
- }: NavbarProps) {
178
- const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
179
- const mobileMenuId = "navbar-mobile-menu";
180
-
181
- // Measure header height for reliable max height on mobile menu
182
- const headerRef = useRef<HTMLDivElement | null>(null);
183
- const [headerHeightPx, setHeaderHeightPx] = useState(0);
184
-
185
- useEffect(() => {
186
- const update = () =>
187
- setHeaderHeightPx(headerRef.current?.offsetHeight ?? 0);
188
- update();
189
- window.addEventListener("resize", update);
190
- return () => window.removeEventListener("resize", update);
191
- }, []);
192
-
193
- // Lock body scroll when mobile menu is open
194
- useEffect(() => {
195
- const originalOverflow = document.body.style.overflow;
196
- if (isMobileMenuOpen) {
197
- document.body.style.overflow = "hidden";
198
- } else {
199
- document.body.style.overflow = originalOverflow || "";
200
- }
201
- return () => {
202
- document.body.style.overflow = originalOverflow || "";
203
- };
204
- }, [isMobileMenuOpen]);
205
-
206
- return (
207
- <nav
208
- id={id}
209
- className={cn(
210
- "w-full",
211
- sticky && "sticky top-0 z-50",
212
- nav.className,
213
- className,
214
- )}
215
- aria-label={ariaLabel}
216
- >
217
- <div
218
- ref={headerRef}
219
- className={cn(
220
- "flex items-center justify-between px-4 py-2",
221
- navHeight,
222
- container.className,
223
- )}
224
- >
225
- <div
226
- className={cn(
227
- "mr-2 flex items-center gap-2 pl-2",
228
- brandWrapper.className,
229
- )}
230
- >
231
- {brandNode}
232
- {brand && <h1 className={cn(brandText.className)}>{brand}</h1>}
233
- </div>
234
-
235
- <button
236
- aria-label="Toggle mobile menu"
237
- aria-expanded={isMobileMenuOpen}
238
- aria-controls={mobileMenuId}
239
- className={cn("lg:hidden", toggleButton.className)}
240
- // className={cn("lg:hidden", toggleButton.className)}
241
- onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
242
- >
243
- {isMobileMenuOpen ? (
244
- <X className="h-6 w-6" />
245
- ) : (
246
- <Menu className="h-6 w-6" />
247
- )}
248
- </button>
249
-
250
- <div className={cn("hidden md:flex", desktopMenu.className)}>
251
- {/* <div className={cn("hidden lg:flex", desktopMenu.className)}> */}
252
- {menuItems.map((item) => (
253
- <Link
254
- key={item.label}
255
- href={item.href}
256
- className={cn(
257
- "rounded-md px-2 py-2 transition-colors duration-200 lg:px-4",
258
- "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
259
- "focus-visible:ring-[var(--navbar-ring)]",
260
- links.className,
261
- )}
262
- >
263
- {item.label}
264
- </Link>
265
- ))}
266
-
267
- {showColorModeToggle && (
268
- <div className={cn(colorModeWrapper.className)}>
269
- <ThemeToggle {...themeToggle} />
270
- </div>
271
- )}
272
-
273
- {ctaButton && (
274
- <Button
275
- asChild
276
- unstyled={ctaButtonStyle.unstyled}
277
- variant={ctaButtonStyle.variant}
278
- size={ctaButtonStyle.size}
279
- className={cn(
280
- ctaButtonWrapper.className,
281
- ctaButtonStyle.className,
282
- )}
283
- style={ctaButtonStyle.style}
284
- >
285
- <Link href={ctaButton.href} aria-label={ctaButton.label}>
286
- {ctaButton.label}
287
- </Link>
288
- </Button>
289
- )}
290
- </div>
291
- </div>
292
-
293
- {isMobileMenuOpen && (
294
- <div
295
- id={mobileMenuId}
296
- className={cn(
297
- "md:hidden",
298
- // "lg:hidden",
299
- "overflow-y-auto overscroll-y-contain",
300
- nav.className,
301
- mobileMenu.className,
302
- )}
303
- style={{
304
- maxHeight: `calc(100dvh - ${headerHeightPx}px)`,
305
- WebkitOverflowScrolling: "touch",
306
- }}
307
- >
308
- <div className={cn(mobileMenuInner.className)}>
309
- {menuItems.map((item) => (
310
- <Link
311
- key={item.label}
312
- href={item.href}
313
- className={cn(
314
- "block w-full rounded-md px-2 py-4 text-center transition-colors duration-200",
315
- mobileLinks.className,
316
- "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
317
- "focus-visible:ring-[var(--navbar-ring)]",
318
- links.className,
319
- )}
320
- onClick={() => setIsMobileMenuOpen(false)}
321
- >
322
- {item.label}
323
- </Link>
324
- ))}
325
-
326
- {showColorModeToggle && (
327
- <div className="flex w-full justify-center pt-4">
328
- <ThemeToggle {...themeToggle} />
329
- </div>
330
- )}
331
-
332
- {ctaButton && (
333
- <div className="flex w-full justify-center pt-6">
334
- <Button
335
- asChild
336
- unstyled={ctaButtonStyle.unstyled}
337
- variant={ctaButtonStyle.variant}
338
- size={ctaButtonStyle.size}
339
- className={cn("w-full max-w-xs", ctaButtonStyle.className)}
340
- style={ctaButtonStyle.style}
341
- >
342
- <Link href={ctaButton.href} aria-label={ctaButton.label}>
343
- {ctaButton.label}
344
- </Link>
345
- </Button>
346
- </div>
347
- )}
348
- </div>
349
- </div>
350
- )}
351
- </nav>
352
- );
353
- }
1
+ // components/sections/Navbar.tsx
2
+
3
+ "use client";
4
+
5
+ import React, { useEffect, useRef, useState } from "react";
6
+ import Link from "next/link";
7
+ import { Button } from "@/components/ui/button";
8
+ import {
9
+ ThemeToggle,
10
+ type ThemeToggleProps,
11
+ } from "@/components/ui/theme-toggle";
12
+ import { cn } from "@/lib/utils";
13
+ import { Menu, X } from "lucide-react";
14
+
15
+ /**
16
+ * Props for the Navbar component.
17
+ *
18
+ * @remarks
19
+ * Slot-style className overrides are available for structural parts. The mobile
20
+ * menu locks body scroll when open and restores it on close/unmount.
21
+ *
22
+ * @public
23
+ */
24
+ export interface NavbarProps {
25
+ /** Optional id to attach to the root nav element */
26
+ id?: string;
27
+
28
+ /** The height of the navbar as a Tailwind class. Example: "h-16" or "h-[13vh]" */
29
+ navHeight?: string;
30
+
31
+ /**
32
+ * The text displayed as the brand/logo.
33
+ * @defaultValue "Brand Name"
34
+ */
35
+ brand?: string;
36
+ /** Optional custom brand node (e.g., logo). Rendered left of brand text */
37
+ brandNode?: React.ReactNode;
38
+
39
+ /** Navigation links array. Each item has a label and href */
40
+ menuItems?: { label: string; href: string }[];
41
+
42
+ /**
43
+ * Call to action button config or null to hide it.
44
+ * Example: { label: "Get Started", href: "#contact" }
45
+ */
46
+ ctaButton?: { label: string; href: string } | null;
47
+
48
+ /**
49
+ * Whether to display the color mode toggle.
50
+ * @defaultValue true
51
+ */
52
+ showColorModeToggle?: boolean;
53
+
54
+ /**
55
+ * Whether navbar should stick to top when scrolling.
56
+ * @defaultValue true
57
+ */
58
+ sticky?: boolean;
59
+
60
+ /** Optional top-level class to override the nav root */
61
+ className?: string;
62
+
63
+ /** Styling configuration objects */
64
+ nav?: {
65
+ className?: string;
66
+ };
67
+
68
+ brandText?: {
69
+ className?: string;
70
+ };
71
+ links?: {
72
+ className?: string;
73
+ };
74
+ ctaButtonStyle?: {
75
+ unstyled?: boolean;
76
+ style?: React.CSSProperties;
77
+ variant?:
78
+ | "default"
79
+ | "destructive"
80
+ | "outline"
81
+ | "secondary"
82
+ | "ghost"
83
+ | "link";
84
+ size?: "default" | "sm" | "lg" | "icon";
85
+ className?: string;
86
+ };
87
+ mobileMenu?: {
88
+ className?: string;
89
+ };
90
+
91
+ /* Additional style slots for targeted overrides */
92
+ container?: { className?: string };
93
+ brandWrapper?: { className?: string };
94
+ desktopMenu?: { className?: string };
95
+ toggleButton?: { className?: string };
96
+ colorModeWrapper?: { className?: string };
97
+ ctaButtonWrapper?: { className?: string };
98
+ mobileMenuInner?: { className?: string };
99
+ /** Additional class overrides for mobile menu link hover/background, etc. */
100
+ mobileLinks?: { className?: string };
101
+
102
+ /** Props forwarded to ThemeToggle so templates can fully style it */
103
+ themeToggle?: ThemeToggleProps;
104
+
105
+ /** ARIA label for the navbar. */
106
+ ariaLabel?: string;
107
+ }
108
+
109
+ const defaultMenuItems = [
110
+ { label: "Home", href: "#home" },
111
+ { label: "Features", href: "#features" },
112
+ { label: "Pricing", href: "#pricing" },
113
+ { label: "FAQ", href: "#faq" },
114
+ { label: "Contact", href: "#contact" },
115
+ ];
116
+
117
+ /**
118
+ * Responsive navbar with brand, menu links, color mode toggle, and optional CTA.
119
+ *
120
+ * @remarks
121
+ * - Sticky behavior is enabled by default (sticky top-0 z-50).
122
+ * - Mobile menu toggles with a button and traps body scroll while open.
123
+ * - Accessibility: Focus rings are applied to interactive elements; aria
124
+ * attributes are set on the toggle button for state.
125
+ *
126
+ * @example
127
+ * <Navbar brand="Acme" />
128
+ */
129
+ export function Navbar({
130
+ id,
131
+ navHeight = "h-16",
132
+ brand = "Brand Name",
133
+ brandNode,
134
+ menuItems = defaultMenuItems,
135
+ ctaButton = { label: "Get Started", href: "#contact" },
136
+ showColorModeToggle = true,
137
+ sticky = true,
138
+ className,
139
+ nav = {
140
+ className: "bg-background text-foreground border-b border-border",
141
+ },
142
+ brandText = {
143
+ className: "text-2xl font-bold text-primary",
144
+ },
145
+ links = {
146
+ className:
147
+ "text-base font-normal text-foreground hover:text-gray-500 dark:hover-text-gray-400 transition-colors",
148
+ },
149
+ ctaButtonStyle = {
150
+ variant: "default",
151
+ size: "default",
152
+ className:
153
+ "shadow-lg hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5",
154
+ },
155
+ mobileMenu = {
156
+ className: "border-t border-border",
157
+ },
158
+
159
+ /* Slot defaults */
160
+ container = { className: "" },
161
+ brandWrapper = { className: "" },
162
+ desktopMenu = {
163
+ className: "hidden items-center space-x-1 lg:flex lg:space-x-6",
164
+ },
165
+ toggleButton = {
166
+ className:
167
+ "hover:bg-accent flex items-center justify-center rounded-md p-2 transition-colors",
168
+ },
169
+ colorModeWrapper = { className: "ml-2" },
170
+ ctaButtonWrapper = { className: "ml-2" },
171
+ mobileMenuInner = { className: "space-y-2 px-4 pt-2 pb-4" },
172
+ mobileLinks = { className: "hover:bg-accent" },
173
+
174
+ themeToggle = {},
175
+
176
+ ariaLabel = "Main navigation",
177
+ }: NavbarProps) {
178
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
179
+ const mobileMenuId = "navbar-mobile-menu";
180
+
181
+ // Measure header height for reliable max height on mobile menu
182
+ const headerRef = useRef<HTMLDivElement | null>(null);
183
+ const [headerHeightPx, setHeaderHeightPx] = useState(0);
184
+
185
+ useEffect(() => {
186
+ const update = () =>
187
+ setHeaderHeightPx(headerRef.current?.offsetHeight ?? 0);
188
+ update();
189
+ window.addEventListener("resize", update);
190
+ return () => window.removeEventListener("resize", update);
191
+ }, []);
192
+
193
+ // Lock body scroll when mobile menu is open
194
+ useEffect(() => {
195
+ const originalOverflow = document.body.style.overflow;
196
+ if (isMobileMenuOpen) {
197
+ document.body.style.overflow = "hidden";
198
+ } else {
199
+ document.body.style.overflow = originalOverflow || "";
200
+ }
201
+ return () => {
202
+ document.body.style.overflow = originalOverflow || "";
203
+ };
204
+ }, [isMobileMenuOpen]);
205
+
206
+ return (
207
+ <nav
208
+ id={id}
209
+ className={cn(
210
+ "w-full",
211
+ sticky && "sticky top-0 z-50",
212
+ nav.className,
213
+ className,
214
+ )}
215
+ aria-label={ariaLabel}
216
+ >
217
+ <div
218
+ ref={headerRef}
219
+ className={cn(
220
+ "flex items-center justify-between px-4 py-2",
221
+ navHeight,
222
+ container.className,
223
+ )}
224
+ >
225
+ <div
226
+ className={cn(
227
+ "mr-2 flex items-center gap-2 pl-2",
228
+ brandWrapper.className,
229
+ )}
230
+ >
231
+ {brandNode}
232
+ {brand && <h1 className={cn(brandText.className)}>{brand}</h1>}
233
+ </div>
234
+
235
+ <button
236
+ aria-label="Toggle mobile menu"
237
+ aria-expanded={isMobileMenuOpen}
238
+ aria-controls={mobileMenuId}
239
+ className={cn("lg:hidden", toggleButton.className)}
240
+ onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
241
+ >
242
+ {isMobileMenuOpen ? (
243
+ <X className="h-6 w-6" />
244
+ ) : (
245
+ <Menu className="h-6 w-6" />
246
+ )}
247
+ </button>
248
+
249
+ <div className={cn("hidden md:flex", desktopMenu.className)}>
250
+ {menuItems.map((item) => (
251
+ <Link
252
+ key={item.label}
253
+ href={item.href}
254
+ className={cn(
255
+ "rounded-md px-2 py-2 transition-colors duration-200 lg:px-4",
256
+ "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
257
+ "focus-visible:ring-[var(--navbar-ring)]",
258
+
259
+ links.className,
260
+ )}
261
+ >
262
+ {item.label}
263
+ </Link>
264
+ ))}
265
+
266
+ {showColorModeToggle && (
267
+ <div className={cn(colorModeWrapper.className)}>
268
+ <ThemeToggle {...themeToggle} />
269
+ </div>
270
+ )}
271
+
272
+ {ctaButton && (
273
+ <Button
274
+ asChild
275
+ unstyled={ctaButtonStyle.unstyled}
276
+ variant={ctaButtonStyle.variant}
277
+ size={ctaButtonStyle.size}
278
+ className={cn(
279
+ ctaButtonWrapper.className,
280
+ ctaButtonStyle.className,
281
+ )}
282
+ style={ctaButtonStyle.style}
283
+ >
284
+ <Link href={ctaButton.href} aria-label={ctaButton.label}>
285
+ {ctaButton.label}
286
+ </Link>
287
+ </Button>
288
+ )}
289
+ </div>
290
+ </div>
291
+
292
+ {isMobileMenuOpen && (
293
+ <div
294
+ id={mobileMenuId}
295
+ className={cn(
296
+ "md:hidden",
297
+ "overflow-y-auto overscroll-y-contain",
298
+ nav.className,
299
+ mobileMenu.className,
300
+ )}
301
+ style={{
302
+ maxHeight: `calc(100dvh - ${headerHeightPx}px)`,
303
+ WebkitOverflowScrolling: "touch",
304
+ }}
305
+ >
306
+ <div className={cn(mobileMenuInner.className)}>
307
+ {menuItems.map((item) => (
308
+ <Link
309
+ key={item.label}
310
+ href={item.href}
311
+ className={cn(
312
+ "block w-full rounded-md px-2 py-4 text-center transition-colors duration-200",
313
+ mobileLinks.className,
314
+ "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
315
+ "focus-visible:ring-[var(--navbar-ring)]",
316
+
317
+ links.className,
318
+ )}
319
+ onClick={() => setIsMobileMenuOpen(false)}
320
+ >
321
+ {item.label}
322
+ </Link>
323
+ ))}
324
+
325
+ {showColorModeToggle && (
326
+ <div className="flex w-full justify-center pt-4">
327
+ <ThemeToggle {...themeToggle} />
328
+ </div>
329
+ )}
330
+
331
+ {ctaButton && (
332
+ <div className="flex w-full justify-center pt-6">
333
+ <Button
334
+ asChild
335
+ unstyled={ctaButtonStyle.unstyled}
336
+ variant={ctaButtonStyle.variant}
337
+ size={ctaButtonStyle.size}
338
+ className={cn("w-full max-w-xs", ctaButtonStyle.className)}
339
+ style={ctaButtonStyle.style}
340
+ >
341
+ <Link href={ctaButton.href} aria-label={ctaButton.label}>
342
+ {ctaButton.label}
343
+ </Link>
344
+ </Button>
345
+ </div>
346
+ )}
347
+ </div>
348
+ </div>
349
+ )}
350
+ </nav>
351
+ );
352
+ }