@ugurdemirel/landcraft 0.1.2 → 0.1.4

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 (116) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +41 -22
  4. package/dist/index.js +2261 -1937
  5. package/dist/index.js.map +1 -1
  6. package/dist/styles.css +3 -0
  7. package/package.json +1 -1
  8. package/src/components/Blog/BlogCard.tsx +12 -0
  9. package/src/components/Blog/BlogSection.tsx +95 -0
  10. package/src/components/Blog/index.ts +8 -0
  11. package/src/components/Blog/parts.tsx +38 -0
  12. package/src/components/Blog/types.ts +42 -0
  13. package/src/components/Blog/variants/Card.tsx +62 -0
  14. package/src/components/Blog/variants/Row.tsx +41 -0
  15. package/src/components/CTA/CTA.tsx +12 -0
  16. package/src/components/CTA/index.ts +2 -0
  17. package/src/components/CTA/parts.tsx +26 -0
  18. package/src/components/CTA/types.ts +12 -0
  19. package/src/components/CTA/variants/Inverse.tsx +50 -0
  20. package/src/components/CTA/variants/Panel.tsx +60 -0
  21. package/src/components/CTA/variants/Surface.tsx +34 -0
  22. package/src/components/FAQ/FAQ.tsx +48 -0
  23. package/src/components/FAQ/index.ts +2 -0
  24. package/src/components/FAQ/parts.tsx +74 -0
  25. package/src/components/FAQ/types.ts +18 -0
  26. package/src/components/FAQ/variants/Accordion.tsx +46 -0
  27. package/src/components/FAQ/variants/Cards.tsx +40 -0
  28. package/src/components/FAQ/variants/Split.tsx +44 -0
  29. package/src/components/Features/FeatureCard.tsx +44 -0
  30. package/src/components/Features/FeatureGrid.tsx +14 -0
  31. package/src/components/Features/index.ts +3 -0
  32. package/src/components/Features/parts.ts +15 -0
  33. package/src/components/Features/types.ts +25 -0
  34. package/src/components/Features/variants/Bento.tsx +32 -0
  35. package/src/components/Features/variants/Columns.tsx +21 -0
  36. package/src/components/Features/variants/EditorialRows.tsx +38 -0
  37. package/src/components/Footer/Footer.tsx +11 -0
  38. package/src/components/Footer/index.ts +2 -0
  39. package/src/components/Footer/parts.tsx +101 -0
  40. package/src/components/Footer/types.ts +32 -0
  41. package/src/components/Footer/variants/Classic.tsx +59 -0
  42. package/src/components/Footer/variants/Editorial.tsx +47 -0
  43. package/src/components/Footer/variants/Minimal.tsx +33 -0
  44. package/src/components/Hero/Hero.tsx +12 -0
  45. package/src/components/Hero/index.ts +2 -0
  46. package/src/components/Hero/parts.tsx +29 -0
  47. package/src/components/Hero/types.ts +20 -0
  48. package/src/components/Hero/variants/Centered.tsx +54 -0
  49. package/src/components/Hero/variants/Split.tsx +53 -0
  50. package/src/components/Hero/variants/Statement.tsx +60 -0
  51. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +37 -0
  52. package/src/components/LanguageSwitcher/index.ts +6 -0
  53. package/src/components/LanguageSwitcher/parts.tsx +144 -0
  54. package/src/components/LanguageSwitcher/types.ts +40 -0
  55. package/src/components/LanguageSwitcher/useLanguageSwitcher.ts +23 -0
  56. package/src/components/LanguageSwitcher/variants/Dropdown.tsx +88 -0
  57. package/src/components/LanguageSwitcher/variants/Modal.tsx +56 -0
  58. package/src/components/LogoCloud/LogoCloud.tsx +14 -0
  59. package/src/components/LogoCloud/index.ts +2 -0
  60. package/src/components/LogoCloud/parts.tsx +32 -0
  61. package/src/components/LogoCloud/types.ts +16 -0
  62. package/src/components/LogoCloud/variants/Marquee.tsx +30 -0
  63. package/src/components/LogoCloud/variants/Quiet.tsx +26 -0
  64. package/src/components/LogoCloud/variants/Strip.tsx +25 -0
  65. package/src/components/{MegaMenu.tsx → MegaMenu/MegaMenu.tsx} +108 -126
  66. package/src/components/MegaMenu/index.ts +9 -0
  67. package/src/components/MegaMenu/types.ts +56 -0
  68. package/src/components/Modal.tsx +1 -0
  69. package/src/components/Navbar/Navbar.tsx +185 -0
  70. package/src/components/Navbar/index.ts +2 -0
  71. package/src/components/Navbar/types.ts +30 -0
  72. package/src/components/Newsletter/Newsletter.tsx +12 -0
  73. package/src/components/Newsletter/index.ts +2 -0
  74. package/src/components/Newsletter/parts.tsx +23 -0
  75. package/src/components/Newsletter/types.ts +12 -0
  76. package/src/components/Newsletter/useNewsletterForm.ts +27 -0
  77. package/src/components/Newsletter/variants/Card.tsx +62 -0
  78. package/src/components/Newsletter/variants/Inline.tsx +54 -0
  79. package/src/components/Newsletter/variants/Underline.tsx +52 -0
  80. package/src/components/Pricing/Pricing.tsx +37 -0
  81. package/src/components/Pricing/index.ts +2 -0
  82. package/src/components/Pricing/parts.tsx +132 -0
  83. package/src/components/Pricing/types.ts +28 -0
  84. package/src/components/Pricing/variants/Bento.tsx +72 -0
  85. package/src/components/Pricing/variants/Cards.tsx +79 -0
  86. package/src/components/Pricing/variants/Compact.tsx +69 -0
  87. package/src/components/Stats/Stats.tsx +16 -0
  88. package/src/components/Stats/index.ts +2 -0
  89. package/src/components/Stats/parts.tsx +29 -0
  90. package/src/components/Stats/types.ts +23 -0
  91. package/src/components/Stats/variants/Cells.tsx +34 -0
  92. package/src/components/Stats/variants/Editorial.tsx +41 -0
  93. package/src/components/Stats/variants/Hairline.tsx +31 -0
  94. package/src/components/Stats/variants/Ticker.tsx +36 -0
  95. package/src/components/Testimonials/TestimonialCard.tsx +61 -0
  96. package/src/components/Testimonials/Testimonials.tsx +14 -0
  97. package/src/components/Testimonials/index.ts +3 -0
  98. package/src/components/Testimonials/parts.ts +7 -0
  99. package/src/components/Testimonials/types.ts +24 -0
  100. package/src/components/Testimonials/variants/Carousel.tsx +94 -0
  101. package/src/components/Testimonials/variants/Grid.tsx +19 -0
  102. package/src/components/Testimonials/variants/Marquee.tsx +28 -0
  103. package/src/utils/useTokenForeground.ts +1 -0
  104. package/src/components/Blog.tsx +0 -251
  105. package/src/components/CTA.tsx +0 -155
  106. package/src/components/FAQ.tsx +0 -163
  107. package/src/components/Features.tsx +0 -153
  108. package/src/components/Footer.tsx +0 -185
  109. package/src/components/Hero.tsx +0 -169
  110. package/src/components/LanguageSwitcher.tsx +0 -223
  111. package/src/components/LogoCloud.tsx +0 -100
  112. package/src/components/Navbar.tsx +0 -195
  113. package/src/components/Newsletter.tsx +0 -180
  114. package/src/components/Pricing.tsx +0 -336
  115. package/src/components/Stats.tsx +0 -181
  116. package/src/components/Testimonials.tsx +0 -205
@@ -0,0 +1,16 @@
1
+ import type { HTMLAttributes } from "react";
2
+
3
+ export type LogoCloudOption = "quiet" | "marquee" | "strip";
4
+
5
+ export interface LogoItem {
6
+ name: string;
7
+ src?: string;
8
+ href?: string;
9
+ }
10
+
11
+ export interface LogoCloudProps extends HTMLAttributes<HTMLDivElement> {
12
+ logos: LogoItem[];
13
+ title?: string;
14
+ /** Visual option: quiet list · infinite marquee · hairline strip */
15
+ option?: LogoCloudOption;
16
+ }
@@ -0,0 +1,30 @@
1
+ import { forwardRef, type HTMLAttributes } from "react";
2
+ import { cn } from "../../../utils/cn";
3
+ import { LogoMark, LogoCloudTitle } from "../parts";
4
+ import type { LogoItem } from "../types";
5
+
6
+ interface MarqueeProps extends HTMLAttributes<HTMLDivElement> {
7
+ logos: LogoItem[];
8
+ title?: string;
9
+ }
10
+
11
+ export const LogoCloudMarquee = forwardRef<HTMLDivElement, MarqueeProps>(
12
+ ({ className, logos, title, ...props }, ref) => {
13
+ const doubled = [...logos, ...logos];
14
+ return (
15
+ <div ref={ref} className={cn("w-full overflow-hidden", className)} {...props}>
16
+ <LogoCloudTitle title={title} />
17
+ <div className="mask-fade-x">
18
+ <div className="flex w-max animate-marquee gap-14 pr-14 motion-reduce:animate-none">
19
+ {doubled.map((logo, i) => (
20
+ <div key={`${logo.name}-${i}`} className="group shrink-0">
21
+ <LogoMark logo={logo} />
22
+ </div>
23
+ ))}
24
+ </div>
25
+ </div>
26
+ </div>
27
+ );
28
+ },
29
+ );
30
+ LogoCloudMarquee.displayName = "LogoCloudMarquee";
@@ -0,0 +1,26 @@
1
+ import { forwardRef, type HTMLAttributes } from "react";
2
+ import { cn } from "../../../utils/cn";
3
+ import { LogoMark, LogoCloudTitle } from "../parts";
4
+ import type { LogoItem } from "../types";
5
+
6
+ interface QuietProps extends HTMLAttributes<HTMLDivElement> {
7
+ logos: LogoItem[];
8
+ title?: string;
9
+ }
10
+
11
+ /** quiet — unbounded spread of wordmarks. */
12
+ export const LogoCloudQuiet = forwardRef<HTMLDivElement, QuietProps>(
13
+ ({ className, logos, title, ...props }, ref) => (
14
+ <div ref={ref} className={cn("w-full", className)} {...props}>
15
+ <LogoCloudTitle title={title} />
16
+ <ul className="flex flex-wrap items-center justify-center gap-x-12 gap-y-6">
17
+ {logos.map((logo) => (
18
+ <li key={logo.name} className="group">
19
+ <LogoMark logo={logo} />
20
+ </li>
21
+ ))}
22
+ </ul>
23
+ </div>
24
+ ),
25
+ );
26
+ LogoCloudQuiet.displayName = "LogoCloudQuiet";
@@ -0,0 +1,25 @@
1
+ import { forwardRef, type HTMLAttributes } from "react";
2
+ import { cn } from "../../../utils/cn";
3
+ import { LogoMark, LogoCloudTitle } from "../parts";
4
+ import type { LogoItem } from "../types";
5
+
6
+ interface StripProps extends HTMLAttributes<HTMLDivElement> {
7
+ logos: LogoItem[];
8
+ title?: string;
9
+ }
10
+
11
+ export const LogoCloudStrip = forwardRef<HTMLDivElement, StripProps>(
12
+ ({ className, logos, title, ...props }, ref) => (
13
+ <div ref={ref} className={cn("w-full", className)} {...props}>
14
+ <LogoCloudTitle title={title} />
15
+ <ul className="flex flex-wrap items-center justify-center gap-x-10 gap-y-4 rounded-2xl border border-border bg-surface px-6 py-7 sm:gap-x-14">
16
+ {logos.map((logo) => (
17
+ <li key={logo.name} className="group">
18
+ <LogoMark logo={logo} />
19
+ </li>
20
+ ))}
21
+ </ul>
22
+ </div>
23
+ ),
24
+ );
25
+ LogoCloudStrip.displayName = "LogoCloudStrip";
@@ -1,63 +1,83 @@
1
- import { useId, useState, type ElementType, type HTMLAttributes, type ReactNode } from "react";
2
- import { cn } from "../utils/cn";
3
- import { getContrastText } from "../utils/contrast";
4
- import { ChevronDown, Menu, X } from "../icons";
1
+ "use client";
2
+ import { useId, useState } from "react";
3
+ import { cn } from "../../utils/cn";
4
+ import { getContrastText } from "../../utils/contrast";
5
+ import { ChevronDown, Menu, X } from "../../icons";
6
+ import type { MegaMenuProps, MegaMenuVariant, MegaMenuItem } from "./types";
5
7
 
6
- export interface MegaMenuLink {
7
- label: string;
8
- href: string;
9
- description?: string;
8
+ interface MegaMenuStyle {
9
+ shell: string;
10
+ bar: string;
11
+ header: string;
12
+ trigger: string;
13
+ triggerOpen: string;
14
+ badge: string;
15
+ panel: string;
16
+ columnTitle: string;
17
+ columnLink: string;
18
+ linkDescription: string;
19
+ mobilePanel: string;
20
+ mobileButton: string;
21
+ mobileLink: string;
22
+ brandText: string;
23
+ brandDot: string;
10
24
  }
11
25
 
12
- export interface MegaMenuColumn {
13
- title?: string;
14
- links: MegaMenuLink[];
15
- }
16
-
17
- export interface MegaMenuFeatured {
18
- title: string;
19
- description?: string;
20
- href: string;
21
- cta?: string;
22
- /** Background color for the card; glyph text color is computed for contrast. */
23
- accent?: string;
24
- }
25
-
26
- export interface MegaMenuItem {
27
- label: string;
28
- href?: string;
29
- /** When set, the item becomes a trigger that opens a mega panel. */
30
- columns?: MegaMenuColumn[];
31
- /** Optional highlighted card pinned to the right side of the panel. */
32
- featured?: MegaMenuFeatured;
33
- /** Small pill rendered next to the trigger label. */
34
- badge?: string;
35
- }
36
-
37
- export interface MegaMenuProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
38
- variant?: "classic" | "floating" | "inverse";
39
- /** Wordmark rendered when no logo is supplied. */
40
- brand?: ReactNode;
41
- /** Custom logo node (component, <img>, inline SVG…). Replaces the wordmark. */
42
- logo?: ReactNode;
43
- /** Quick logo image source. */
44
- logoSrc?: string;
45
- logoAlt?: string;
46
- logoClassName?: string;
47
- brandHref?: string;
48
- items: MegaMenuItem[];
49
- /** Rendered at the right side of the bar (desktop) and inside the mobile panel. */
50
- actions?: ReactNode;
51
- cta?: ReactNode;
52
- sticky?: boolean;
53
- /** Component used to render brand, trigger and panel links. Defaults to `<a>`. Pass your router's `<Link>` (Next.js, Remix, React Router…) for framework-aware navigation. */
54
- LinkComponent?: ElementType;
55
- }
26
+ const inverse = "text-on-secondary";
27
+ const light = "text-foreground";
56
28
 
57
- const triggerStyles = {
58
- classic: "text-muted-foreground hover:text-foreground hover:bg-surface",
59
- floating: "text-muted-foreground hover:text-foreground hover:bg-surface",
60
- inverse: "text-on-secondary/60 hover:text-on-secondary hover:bg-white/10",
29
+ const styles: Record<MegaMenuVariant, MegaMenuStyle> = {
30
+ classic: {
31
+ shell: "border-b border-border bg-background/85 backdrop-blur-md supports-[backdrop-filter]:bg-background/75",
32
+ bar: "mx-auto max-w-6xl px-5 sm:px-8",
33
+ header: "px-0",
34
+ trigger: "text-muted-foreground hover:text-foreground hover:bg-surface",
35
+ triggerOpen: "bg-surface text-foreground",
36
+ badge: "bg-primary-soft text-primary",
37
+ panel: "border-border bg-surface shadow-raised",
38
+ columnTitle: "text-muted-foreground/70",
39
+ columnLink: "hover:bg-surface-strong",
40
+ linkDescription: "text-muted-foreground",
41
+ mobilePanel: "border-t border-border bg-background",
42
+ mobileButton: `${light} hover:bg-surface`,
43
+ mobileLink: "text-muted-foreground hover:bg-surface hover:text-foreground",
44
+ brandText: light,
45
+ brandDot: "bg-foreground",
46
+ },
47
+ floating: {
48
+ shell: "px-0",
49
+ bar: "mx-auto mt-3 max-w-5xl rounded-xl border border-border bg-surface/90 px-5 shadow-soft backdrop-blur-md supports-[backdrop-filter]:bg-surface/70",
50
+ header: "px-3 sm:px-6",
51
+ trigger: "text-muted-foreground hover:text-foreground hover:bg-surface",
52
+ triggerOpen: "bg-surface text-foreground",
53
+ badge: "bg-primary-soft text-primary",
54
+ panel: "border-border bg-surface shadow-raised",
55
+ columnTitle: "text-muted-foreground/70",
56
+ columnLink: "hover:bg-surface-strong",
57
+ linkDescription: "text-muted-foreground",
58
+ mobilePanel: "mx-3 mb-3 max-w-5xl overflow-hidden rounded-b-xl border border-border bg-surface",
59
+ mobileButton: `${light} hover:bg-surface`,
60
+ mobileLink: "text-muted-foreground hover:bg-surface hover:text-foreground",
61
+ brandText: light,
62
+ brandDot: "bg-foreground",
63
+ },
64
+ inverse: {
65
+ shell: "border-b border-white/10 bg-[#101010]/85 backdrop-blur-md",
66
+ bar: "mx-auto max-w-6xl px-5 sm:px-8",
67
+ header: "px-0",
68
+ trigger: "text-on-secondary/60 hover:text-on-secondary hover:bg-white/10",
69
+ triggerOpen: "bg-white/10 text-on-secondary",
70
+ badge: "bg-white/15 text-on-secondary",
71
+ panel: "border-white/10 bg-[#161616]",
72
+ columnTitle: "text-on-secondary/45",
73
+ columnLink: "hover:bg-white/10",
74
+ linkDescription: "text-on-secondary/50",
75
+ mobilePanel: "border-t border-white/10 bg-[#101010]",
76
+ mobileButton: `${inverse} hover:bg-white/10`,
77
+ mobileLink: "text-on-secondary/70 hover:bg-white/10 hover:text-on-secondary",
78
+ brandText: inverse,
79
+ brandDot: "bg-on-secondary",
80
+ },
61
81
  };
62
82
 
63
83
  export const MegaMenu = ({
@@ -72,6 +92,7 @@ export const MegaMenu = ({
72
92
  items,
73
93
  actions,
74
94
  cta,
95
+ languageSwitcher,
75
96
  sticky = true,
76
97
  LinkComponent,
77
98
  onKeyDown,
@@ -82,8 +103,7 @@ export const MegaMenu = ({
82
103
  const [openIndex, setOpenIndex] = useState<number | null>(null);
83
104
  const [mobileOpen, setMobileOpen] = useState(false);
84
105
  const [mobileExpanded, setMobileExpanded] = useState<number | null>(null);
85
-
86
- const inverse = variant === "inverse";
106
+ const s = styles[variant];
87
107
 
88
108
  const closePanels = () => setOpenIndex(null);
89
109
  const closeAll = () => {
@@ -92,27 +112,8 @@ export const MegaMenu = ({
92
112
  setMobileExpanded(null);
93
113
  };
94
114
 
95
- const panelBackground = inverse
96
- ? "border-white/10 bg-[#161616]"
97
- : "border-border bg-surface shadow-raised";
98
-
99
115
  const panelId = (i: number) => `${ids}-panel-${i}`;
100
116
 
101
- const shell = cn(
102
- "relative w-full transition-colors duration-200",
103
- sticky && "sticky top-0 z-50",
104
- variant === "classic" &&
105
- "border-b border-border bg-background/85 backdrop-blur-md supports-[backdrop-filter]:bg-background/75",
106
- variant === "inverse" && "border-b border-white/10 bg-[#101010]/85 backdrop-blur-md",
107
- );
108
-
109
- const bar = cn(
110
- "relative flex h-16 w-full items-center justify-between gap-4 transition-colors duration-200",
111
- variant === "floating" &&
112
- "mx-auto mt-3 max-w-5xl rounded-xl border border-border bg-surface/90 px-5 shadow-soft backdrop-blur-md supports-[backdrop-filter]:bg-surface/70",
113
- variant !== "floating" && "mx-auto max-w-6xl px-5 sm:px-8",
114
- );
115
-
116
117
  const logoNode =
117
118
  logo ??
118
119
  (logoSrc ? (
@@ -126,26 +127,15 @@ export const MegaMenu = ({
126
127
  const brandNode = logoNode ? (
127
128
  <span className="flex items-center">{logoNode}</span>
128
129
  ) : (
129
- <span
130
- className={cn(
131
- "flex items-center gap-2 font-display text-[17px] font-bold tracking-tight",
132
- inverse ? "text-on-secondary" : "text-foreground",
133
- )}
134
- >
135
- <span
136
- aria-hidden
137
- className={cn(
138
- "inline-block h-2.5 w-2.5 rounded-sm",
139
- inverse ? "bg-on-secondary" : "bg-foreground",
140
- )}
141
- />
130
+ <span className={cn("flex items-center gap-2 font-display text-[17px] font-bold tracking-tight", s.brandText)}>
131
+ <span aria-hidden className={cn("inline-block h-2.5 w-2.5 rounded-sm", s.brandDot)} />
142
132
  {brand}
143
133
  </span>
144
134
  );
145
135
 
146
136
  const triggerClasses = cn(
147
137
  "flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150",
148
- triggerStyles[variant],
138
+ s.trigger,
149
139
  );
150
140
 
151
141
  const renderColumns = (item: MegaMenuItem) => (
@@ -161,7 +151,7 @@ export const MegaMenu = ({
161
151
  <h4
162
152
  className={cn(
163
153
  "text-xs font-semibold uppercase tracking-[0.14em]",
164
- inverse ? "text-on-secondary/45" : "text-muted-foreground/70",
154
+ s.columnTitle,
165
155
  )}
166
156
  >
167
157
  {column.title}
@@ -175,9 +165,7 @@ export const MegaMenu = ({
175
165
  onClick={closeAll}
176
166
  className={cn(
177
167
  "-mx-2 block rounded-md px-2 py-2 transition-colors duration-150",
178
- inverse
179
- ? "hover:bg-white/10"
180
- : "hover:bg-surface-strong",
168
+ s.columnLink,
181
169
  )}
182
170
  >
183
171
  <span className="block text-sm font-medium text-foreground">
@@ -187,7 +175,7 @@ export const MegaMenu = ({
187
175
  <span
188
176
  className={cn(
189
177
  "mt-0.5 block text-[13px] leading-snug",
190
- inverse ? "text-on-secondary/50" : "text-muted-foreground",
178
+ s.linkDescription,
191
179
  )}
192
180
  >
193
181
  {link.description}
@@ -254,11 +242,17 @@ export const MegaMenu = ({
254
242
 
255
243
  return (
256
244
  <header
257
- className={cn(shell, variant === "floating" && "px-3 sm:px-6", className)}
245
+ className={cn(
246
+ "relative w-full transition-colors duration-200",
247
+ sticky && "sticky top-0 z-50",
248
+ s.shell,
249
+ s.header,
250
+ className,
251
+ )}
258
252
  onKeyDown={handleKeyDown}
259
253
  {...props}
260
254
  >
261
- <nav className={bar} onMouseLeave={closePanels} aria-label="Mega menu">
255
+ <nav className={cn("relative flex h-16 w-full items-center justify-between gap-4 transition-colors duration-200", s.bar)} onMouseLeave={closePanels} aria-label="Mega menu">
262
256
  <Link href={brandHref} className="shrink-0">
263
257
  {brandNode}
264
258
  </Link>
@@ -277,13 +271,7 @@ export const MegaMenu = ({
277
271
  aria-controls={panelId(i)}
278
272
  onClick={() => setOpenIndex(open ? null : i)}
279
273
  onMouseEnter={() => setOpenIndex(i)}
280
- className={cn(
281
- triggerClasses,
282
- open &&
283
- (inverse
284
- ? "bg-white/10 text-on-secondary"
285
- : "bg-surface text-foreground"),
286
- )}
274
+ className={cn(triggerClasses, open && s.triggerOpen)}
287
275
  >
288
276
  <span className="flex items-center gap-1.5">
289
277
  {item.label}
@@ -291,9 +279,7 @@ export const MegaMenu = ({
291
279
  <span
292
280
  className={cn(
293
281
  "rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide",
294
- inverse
295
- ? "bg-white/15 text-on-secondary"
296
- : "bg-primary-soft text-primary",
282
+ s.badge,
297
283
  )}
298
284
  >
299
285
  {item.badge}
@@ -311,7 +297,7 @@ export const MegaMenu = ({
311
297
  <Link href={item.href ?? "#"} className={triggerClasses}>
312
298
  {item.label}
313
299
  {item.badge ? (
314
- <span className="rounded-full bg-primary-soft px-1.5 py-0.5 text-[10px] font-semibold tracking-wide text-primary">
300
+ <span className={cn("rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide", s.badge)}>
315
301
  {item.badge}
316
302
  </span>
317
303
  ) : null}
@@ -323,10 +309,15 @@ export const MegaMenu = ({
323
309
  </ul>
324
310
 
325
311
  <div className="hidden items-center gap-3 lg:flex">
312
+ {languageSwitcher}
326
313
  {actions}
327
314
  {cta}
328
315
  </div>
329
316
 
317
+ {languageSwitcher ? (
318
+ <div className="flex items-center lg:hidden">{languageSwitcher}</div>
319
+ ) : null}
320
+
330
321
  <button
331
322
  type="button"
332
323
  aria-label={mobileOpen ? "Close menu" : "Open menu"}
@@ -335,14 +326,13 @@ export const MegaMenu = ({
335
326
  className={cn(
336
327
  "inline-flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-md lg:hidden",
337
328
  "transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
338
- inverse ? "text-on-secondary hover:bg-white/10" : "text-foreground hover:bg-surface",
329
+ s.mobileButton,
339
330
  )}
340
331
  >
341
332
  {mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
342
333
  </button>
343
334
  </nav>
344
335
 
345
- {/* Desktop mega panels */}
346
336
  {items.map((item, i) => {
347
337
  if (!item.columns?.length && !item.featured) return null;
348
338
  const open = openIndex === i;
@@ -354,7 +344,8 @@ export const MegaMenu = ({
354
344
  aria-label={`${item.label} menu`}
355
345
  onMouseEnter={() => setOpenIndex(i)}
356
346
  className={cn(
357
- "absolute inset-x-0 top-full z-50 pt-2 transition-all duration-150",
347
+ "absolute top-full z-50 pt-2 transition-all duration-150",
348
+ variant === "floating" ? "inset-x-0 mx-auto w-full max-w-5xl" : "inset-x-0",
358
349
  open
359
350
  ? "visible translate-y-0 opacity-100"
360
351
  : "pointer-events-none invisible -translate-y-1 opacity-0",
@@ -363,7 +354,7 @@ export const MegaMenu = ({
363
354
  <div
364
355
  className={cn(
365
356
  "overflow-hidden rounded-xl border p-6 md:p-8",
366
- panelBackground,
357
+ s.panel,
367
358
  item.featured ? "lg:grid lg:grid-cols-[minmax(0,1fr)_auto] lg:gap-12" : "",
368
359
  )}
369
360
  >
@@ -378,16 +369,11 @@ export const MegaMenu = ({
378
369
  );
379
370
  })}
380
371
 
381
- {/* Mobile accordion panel */}
382
372
  <div
383
373
  className={cn(
384
374
  "grid transition-[grid-template-rows,opacity] duration-200 lg:hidden",
385
375
  mobileOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
386
- inverse
387
- ? "border-t border-white/10 bg-[#101010]"
388
- : variant === "floating"
389
- ? "mx-3 mb-3 max-w-5xl overflow-hidden rounded-b-xl border border-border bg-surface"
390
- : "border-t border-border bg-background",
376
+ s.mobilePanel,
391
377
  )}
392
378
  >
393
379
  <div className="min-h-0 overflow-hidden">
@@ -403,15 +389,13 @@ export const MegaMenu = ({
403
389
  }
404
390
  className={cn(
405
391
  "flex w-full items-center justify-between rounded-md px-2 py-2.5 text-sm font-medium transition-colors duration-150",
406
- inverse
407
- ? "text-on-secondary hover:bg-white/10"
408
- : "text-foreground hover:bg-surface",
392
+ s.mobileButton,
409
393
  )}
410
394
  >
411
395
  <span className="flex items-center gap-2">
412
396
  {item.label}
413
397
  {item.badge ? (
414
- <span className="rounded-full bg-primary-soft px-1.5 py-0.5 text-[10px] font-semibold tracking-wide text-primary">
398
+ <span className={cn("rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide", s.badge)}>
415
399
  {item.badge}
416
400
  </span>
417
401
  ) : null}
@@ -448,9 +432,7 @@ export const MegaMenu = ({
448
432
  onClick={() => setMobileOpen(false)}
449
433
  className={cn(
450
434
  "block rounded-md px-2 py-2.5 text-sm font-medium transition-colors duration-150",
451
- inverse
452
- ? "text-on-secondary/70 hover:bg-white/10 hover:text-on-secondary"
453
- : "text-muted-foreground hover:bg-surface hover:text-foreground",
435
+ s.mobileLink,
454
436
  )}
455
437
  >
456
438
  {item.label}
@@ -0,0 +1,9 @@
1
+ export { MegaMenu } from "./MegaMenu";
2
+ export type {
3
+ MegaMenuProps,
4
+ MegaMenuVariant,
5
+ MegaMenuItem,
6
+ MegaMenuColumn,
7
+ MegaMenuLink,
8
+ MegaMenuFeatured,
9
+ } from "./types";
@@ -0,0 +1,56 @@
1
+ import type { ElementType, HTMLAttributes, ReactNode } from "react";
2
+
3
+ export interface MegaMenuLink {
4
+ label: string;
5
+ href: string;
6
+ description?: string;
7
+ }
8
+
9
+ export interface MegaMenuColumn {
10
+ title?: string;
11
+ links: MegaMenuLink[];
12
+ }
13
+
14
+ export interface MegaMenuFeatured {
15
+ title: string;
16
+ description?: string;
17
+ href: string;
18
+ cta?: string;
19
+ /** Background color for the card; glyph text color is computed for contrast. */
20
+ accent?: string;
21
+ }
22
+
23
+ export interface MegaMenuItem {
24
+ label: string;
25
+ href?: string;
26
+ /** When set, the item becomes a trigger that opens a mega panel. */
27
+ columns?: MegaMenuColumn[];
28
+ /** Optional highlighted card pinned to the right side of the panel. */
29
+ featured?: MegaMenuFeatured;
30
+ /** Small pill rendered next to the trigger label. */
31
+ badge?: string;
32
+ }
33
+
34
+ export type MegaMenuVariant = "classic" | "floating" | "inverse";
35
+
36
+ export interface MegaMenuProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
37
+ variant?: MegaMenuVariant;
38
+ /** Wordmark rendered when no logo is supplied. */
39
+ brand?: ReactNode;
40
+ /** Custom logo node (component, <img>, inline SVG…). Replaces the wordmark. */
41
+ logo?: ReactNode;
42
+ /** Quick logo image source. */
43
+ logoSrc?: string;
44
+ logoAlt?: string;
45
+ logoClassName?: string;
46
+ brandHref?: string;
47
+ items: MegaMenuItem[];
48
+ /** Rendered at the right side of the bar (desktop) and inside the mobile panel. */
49
+ actions?: ReactNode;
50
+ cta?: ReactNode;
51
+ /** Optional language switcher rendered on the right (desktop) and beside the menu button (mobile). */
52
+ languageSwitcher?: ReactNode;
53
+ sticky?: boolean;
54
+ /** Component used to render brand, trigger and panel links. Defaults to `<a>`. Pass your router's `<Link>` (Next.js, Remix, React Router…) for framework-aware navigation. */
55
+ LinkComponent?: ElementType;
56
+ }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { forwardRef, useEffect, useId, useRef, type HTMLAttributes, type ReactNode } from "react";
2
3
  import { cn } from "../utils/cn";
3
4
  import { X } from "../icons";