nextworks 0.2.0-alpha.11 → 0.2.0-alpha.13

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 (111) hide show
  1. package/README.md +283 -282
  2. package/dist/cli_manifests/blocks_manifest.json +198 -175
  3. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +101 -100
  4. package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +105 -104
  5. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +1 -1
  6. package/dist/kits/blocks/app/templates/aiworkflow/PresetThemeVars.tsx +58 -0
  7. package/dist/kits/blocks/app/templates/aiworkflow/README.md +46 -0
  8. package/dist/kits/blocks/app/templates/aiworkflow/components/CTA.tsx +44 -0
  9. package/dist/kits/blocks/app/templates/aiworkflow/components/Contact.tsx +105 -0
  10. package/dist/kits/blocks/app/templates/aiworkflow/components/FAQ.tsx +63 -0
  11. package/dist/kits/blocks/app/templates/aiworkflow/components/Features.tsx +65 -0
  12. package/dist/kits/blocks/app/templates/aiworkflow/components/Footer.tsx +109 -0
  13. package/dist/kits/blocks/app/templates/aiworkflow/components/Hero.tsx +636 -0
  14. package/dist/kits/blocks/app/templates/aiworkflow/components/Navbar.tsx +90 -0
  15. package/dist/kits/blocks/app/templates/aiworkflow/components/Pricing.tsx +86 -0
  16. package/dist/kits/blocks/app/templates/aiworkflow/components/ProcessTimeline.tsx +103 -0
  17. package/dist/kits/blocks/app/templates/aiworkflow/components/Testimonials.tsx +56 -0
  18. package/dist/kits/blocks/app/templates/aiworkflow/components/TrustBadges.tsx +59 -0
  19. package/dist/kits/blocks/app/templates/aiworkflow/page.tsx +43 -0
  20. package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
  21. package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -42
  22. package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
  23. package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
  24. package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
  25. package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
  26. package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
  27. package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
  28. package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +84 -84
  29. package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -75
  30. package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -62
  31. package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -84
  32. package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -50
  33. package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
  34. package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -86
  35. package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -83
  36. package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
  37. package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -88
  38. package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -116
  39. package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
  40. package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
  41. package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
  42. package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
  43. package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -90
  44. package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
  45. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
  46. package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
  47. package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -44
  48. package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -129
  49. package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
  50. package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
  51. package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -90
  52. package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
  53. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -104
  54. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -126
  55. package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -117
  56. package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
  57. package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -96
  58. package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
  59. package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
  60. package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
  61. package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -195
  62. package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
  63. package/dist/kits/blocks/components/sections/About.tsx +291 -291
  64. package/dist/kits/blocks/components/sections/CTA.tsx +257 -257
  65. package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
  66. package/dist/kits/blocks/components/sections/FAQ.tsx +214 -214
  67. package/dist/kits/blocks/components/sections/Features.tsx +268 -268
  68. package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
  69. package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
  70. package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
  71. package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +236 -0
  72. package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
  73. package/dist/kits/blocks/components/sections/Navbar.tsx +350 -350
  74. package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -549
  75. package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
  76. package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
  77. package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
  78. package/dist/kits/blocks/components/sections/Team.tsx +309 -309
  79. package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
  80. package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
  81. package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +125 -0
  82. package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +397 -0
  83. package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +128 -0
  84. package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +127 -0
  85. package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +150 -0
  86. package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +191 -0
  87. package/dist/kits/blocks/components/sections/product-demo/types.ts +193 -0
  88. package/dist/kits/blocks/components/theme-provider.tsx +1 -1
  89. package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
  90. package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
  91. package/dist/kits/blocks/components/ui/button.tsx +122 -122
  92. package/dist/kits/blocks/components/ui/card.tsx +95 -95
  93. package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
  94. package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
  95. package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
  96. package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
  97. package/dist/kits/blocks/components/ui/input.tsx +27 -27
  98. package/dist/kits/blocks/components/ui/label.tsx +29 -29
  99. package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
  100. package/dist/kits/blocks/components/ui/select.tsx +25 -25
  101. package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
  102. package/dist/kits/blocks/components/ui/table.tsx +98 -98
  103. package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
  104. package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
  105. package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -243
  106. package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
  107. package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
  108. package/dist/kits/blocks/lib/themes.ts +400 -400
  109. package/dist/kits/blocks/lib/utils.ts +6 -6
  110. package/dist/kits/blocks/package-deps.json +3 -3
  111. package/package.json +1 -1
@@ -1,350 +1,350 @@
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
- links.className,
259
- )}
260
- >
261
- {item.label}
262
- </Link>
263
- ))}
264
-
265
- {showColorModeToggle && (
266
- <div className={cn(colorModeWrapper.className)}>
267
- <ThemeToggle {...themeToggle} />
268
- </div>
269
- )}
270
-
271
- {ctaButton && (
272
- <Button
273
- asChild
274
- unstyled={ctaButtonStyle.unstyled}
275
- variant={ctaButtonStyle.variant}
276
- size={ctaButtonStyle.size}
277
- className={cn(
278
- ctaButtonWrapper.className,
279
- ctaButtonStyle.className,
280
- )}
281
- style={ctaButtonStyle.style}
282
- >
283
- <Link href={ctaButton.href} aria-label={ctaButton.label}>
284
- {ctaButton.label}
285
- </Link>
286
- </Button>
287
- )}
288
- </div>
289
- </div>
290
-
291
- {isMobileMenuOpen && (
292
- <div
293
- id={mobileMenuId}
294
- className={cn(
295
- "md:hidden",
296
- "overflow-y-auto overscroll-y-contain",
297
- nav.className,
298
- mobileMenu.className,
299
- )}
300
- style={{
301
- maxHeight: `calc(100dvh - ${headerHeightPx}px)`,
302
- WebkitOverflowScrolling: "touch",
303
- }}
304
- >
305
- <div className={cn(mobileMenuInner.className)}>
306
- {menuItems.map((item) => (
307
- <Link
308
- key={item.label}
309
- href={item.href}
310
- className={cn(
311
- "block w-full rounded-md px-2 py-4 text-center transition-colors duration-200",
312
- mobileLinks.className,
313
- "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
314
- "focus-visible:ring-[var(--navbar-ring)]",
315
- links.className,
316
- )}
317
- onClick={() => setIsMobileMenuOpen(false)}
318
- >
319
- {item.label}
320
- </Link>
321
- ))}
322
-
323
- {showColorModeToggle && (
324
- <div className="flex w-full justify-center pt-4">
325
- <ThemeToggle {...themeToggle} />
326
- </div>
327
- )}
328
-
329
- {ctaButton && (
330
- <div className="flex w-full justify-center pt-6">
331
- <Button
332
- asChild
333
- unstyled={ctaButtonStyle.unstyled}
334
- variant={ctaButtonStyle.variant}
335
- size={ctaButtonStyle.size}
336
- className={cn("w-full max-w-xs", ctaButtonStyle.className)}
337
- style={ctaButtonStyle.style}
338
- >
339
- <Link href={ctaButton.href} aria-label={ctaButton.label}>
340
- {ctaButton.label}
341
- </Link>
342
- </Button>
343
- </div>
344
- )}
345
- </div>
346
- </div>
347
- )}
348
- </nav>
349
- );
350
- }
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
+ links.className,
259
+ )}
260
+ >
261
+ {item.label}
262
+ </Link>
263
+ ))}
264
+
265
+ {showColorModeToggle && (
266
+ <div className={cn(colorModeWrapper.className)}>
267
+ <ThemeToggle {...themeToggle} />
268
+ </div>
269
+ )}
270
+
271
+ {ctaButton && (
272
+ <Button
273
+ asChild
274
+ unstyled={ctaButtonStyle.unstyled}
275
+ variant={ctaButtonStyle.variant}
276
+ size={ctaButtonStyle.size}
277
+ className={cn(
278
+ ctaButtonWrapper.className,
279
+ ctaButtonStyle.className,
280
+ )}
281
+ style={ctaButtonStyle.style}
282
+ >
283
+ <Link href={ctaButton.href} aria-label={ctaButton.label}>
284
+ {ctaButton.label}
285
+ </Link>
286
+ </Button>
287
+ )}
288
+ </div>
289
+ </div>
290
+
291
+ {isMobileMenuOpen && (
292
+ <div
293
+ id={mobileMenuId}
294
+ className={cn(
295
+ "md:hidden",
296
+ "overflow-y-auto overscroll-y-contain",
297
+ nav.className,
298
+ mobileMenu.className,
299
+ )}
300
+ style={{
301
+ maxHeight: `calc(100dvh - ${headerHeightPx}px)`,
302
+ WebkitOverflowScrolling: "touch",
303
+ }}
304
+ >
305
+ <div className={cn(mobileMenuInner.className)}>
306
+ {menuItems.map((item) => (
307
+ <Link
308
+ key={item.label}
309
+ href={item.href}
310
+ className={cn(
311
+ "block w-full rounded-md px-2 py-4 text-center transition-colors duration-200",
312
+ mobileLinks.className,
313
+ "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none",
314
+ "focus-visible:ring-[var(--navbar-ring)]",
315
+ links.className,
316
+ )}
317
+ onClick={() => setIsMobileMenuOpen(false)}
318
+ >
319
+ {item.label}
320
+ </Link>
321
+ ))}
322
+
323
+ {showColorModeToggle && (
324
+ <div className="flex w-full justify-center pt-4">
325
+ <ThemeToggle {...themeToggle} />
326
+ </div>
327
+ )}
328
+
329
+ {ctaButton && (
330
+ <div className="flex w-full justify-center pt-6">
331
+ <Button
332
+ asChild
333
+ unstyled={ctaButtonStyle.unstyled}
334
+ variant={ctaButtonStyle.variant}
335
+ size={ctaButtonStyle.size}
336
+ className={cn("w-full max-w-xs", ctaButtonStyle.className)}
337
+ style={ctaButtonStyle.style}
338
+ >
339
+ <Link href={ctaButton.href} aria-label={ctaButton.label}>
340
+ {ctaButton.label}
341
+ </Link>
342
+ </Button>
343
+ </div>
344
+ )}
345
+ </div>
346
+ </div>
347
+ )}
348
+ </nav>
349
+ );
350
+ }