@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
@@ -1,223 +0,0 @@
1
- import { useEffect, useId, useRef, useState, type CSSProperties, type HTMLAttributes, type ReactNode } from "react";
2
- import { cn } from "../utils/cn";
3
- import { iso2ToFlagEmoji } from "../utils/iso2Flag";
4
- import { Modal } from "./Modal";
5
- import { Check, ChevronDown, Globe } from "../icons";
6
-
7
- export interface LanguageOption {
8
- /** Language code (ISO 3166-1 alpha-2, e.g. `"tr"`) — this is the value passed to `onChange` and the flag source. */
9
- code: string;
10
- /** Native display name, e.g. `"Türkçe"`. */
11
- label: string;
12
- }
13
-
14
- export type LanguageSwitcherOption = "dropdown" | "modal";
15
-
16
- export interface LanguageSwitcherProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
17
- /** `dropdown` opens the anchored menu; `modal` opens a full dialog via the Modal component. */
18
- option?: LanguageSwitcherOption;
19
- /** Languages to switch between. Required — no built-in default language list. */
20
- languages: LanguageOption[];
21
- /** Controlled current language code. */
22
- value?: string;
23
- /** Uncontrolled initial language code. Defaults to the first language. */
24
- defaultValue?: string;
25
- /** Called with the selected language code. */
26
- onChange?: (code: string) => void;
27
- /** Dropdown menu alignment relative to its trigger. Defaults to `end`. */
28
- align?: "start" | "end";
29
- /** Heading rendered inside the `modal` option. Optional — pass it to show a title. */
30
- modalTitle?: ReactNode;
31
- /** Replace the globe icon with the selected language's flag emoji (and show flags in options). */
32
- showFlag?: boolean;
33
- /** Adapts colors for dark surfaces (inverse Navbar, classic Footer). */
34
- inverse?: boolean;
35
- }
36
-
37
- export const LanguageSwitcher = ({
38
- className,
39
- languages,
40
- option = "dropdown",
41
- value,
42
- defaultValue,
43
- onChange,
44
- align = "end",
45
- modalTitle,
46
- showFlag = false,
47
- inverse = false,
48
- ...props
49
- }: LanguageSwitcherProps) => {
50
- const listboxId = useId();
51
- const anchorName = `--ls-${listboxId.replace(/:/g, "")}`;
52
- const rootRef = useRef<HTMLDivElement>(null);
53
- const [open, setOpen] = useState(false);
54
- const [internal, setInternal] = useState(defaultValue ?? languages[0]?.code ?? "");
55
- const current = value ?? internal;
56
- const currentLang = languages.find((l) => l.code === current) ?? languages[0];
57
-
58
- const select = (code: string) => {
59
- setInternal(code);
60
- setOpen(false);
61
- onChange?.(code);
62
- };
63
-
64
- const renderFlagOrGlobe = (
65
- code: string,
66
- { size = "text-sm", globeClass = "text-muted-foreground" }: { size?: string; globeClass?: string } = {},
67
- ) => {
68
- const flag = iso2ToFlagEmoji(code);
69
- return flag ? (
70
- <span aria-hidden className={cn(size, "leading-none")}>
71
- {flag}
72
- </span>
73
- ) : (
74
- <Globe className={cn("h-4 w-4 shrink-0", globeClass)} />
75
- );
76
- };
77
-
78
- useEffect(() => {
79
- if (!open || option !== "dropdown") return;
80
- const onPointerDown = (event: PointerEvent) => {
81
- if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
82
- setOpen(false);
83
- }
84
- };
85
- const onKeyDown = (event: KeyboardEvent) => {
86
- if (event.key === "Escape") setOpen(false);
87
- };
88
- document.addEventListener("pointerdown", onPointerDown);
89
- document.addEventListener("keydown", onKeyDown);
90
- return () => {
91
- document.removeEventListener("pointerdown", onPointerDown);
92
- document.removeEventListener("keydown", onKeyDown);
93
- };
94
- }, [open, option]);
95
-
96
- if (languages.length === 0) return null;
97
-
98
- const trigger = (
99
- <button
100
- type="button"
101
- aria-haspopup={option === "dropdown" ? "listbox" : "dialog"}
102
- aria-expanded={open}
103
- aria-controls={option === "dropdown" ? listboxId : undefined}
104
- onClick={() => setOpen((o) => !o)}
105
- style={{ anchorName } as CSSProperties}
106
- className={cn(
107
- "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-lg border px-3 text-[13px] font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
108
- inverse
109
- ? "border-white/10 text-on-secondary hover:bg-white/10"
110
- : "border-border text-foreground hover:bg-surface",
111
- )}
112
- >
113
- {showFlag ? (
114
- renderFlagOrGlobe(currentLang.code, { globeClass: inverse ? "opacity-70" : "text-muted-foreground" })
115
- ) : (
116
- <Globe
117
- className={cn("h-4 w-4", inverse ? "opacity-70" : "text-muted-foreground")}
118
- />
119
- )}
120
- <span className="tracking-tight">{currentLang.label}</span>
121
- <ChevronDown
122
- className={cn(
123
- "h-3.5 w-3.5 transition-transform duration-150",
124
- open && "rotate-180",
125
- inverse ? "opacity-70" : "text-muted-foreground",
126
- )}
127
- />
128
- </button>
129
- );
130
-
131
- if (option === "modal") {
132
- return (
133
- <>
134
- <div ref={rootRef} className={cn("relative inline-flex", className)} {...props}>
135
- {trigger}
136
- </div>
137
- <Modal open={open} onClose={() => setOpen(false)} title={modalTitle} size="sm">
138
- <div role="listbox" aria-label="Select language" className="grid min-w-[19rem] gap-1.5">
139
- {languages.map((lang) => {
140
- const active = lang.code === current;
141
- return (
142
- <button
143
- key={lang.code}
144
- type="button"
145
- role="option"
146
- aria-selected={active}
147
- onClick={() => select(lang.code)}
148
- className={cn(
149
- "flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border px-4 py-3 text-left transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
150
- active
151
- ? "border-primary/40 bg-primary-soft text-foreground"
152
- : "border-border text-foreground hover:bg-surface-strong",
153
- )}
154
- >
155
- <span className="flex items-center gap-3">
156
- {showFlag ? renderFlagOrGlobe(lang.code, { size: "text-base" }) : null}
157
- <span className="text-sm font-medium">{lang.label}</span>
158
- </span>
159
- <span className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
160
- {lang.code}
161
- {active ? <Check className="h-4 w-4 text-primary" /> : null}
162
- </span>
163
- </button>
164
- );
165
- })}
166
- </div>
167
- </Modal>
168
- </>
169
- );
170
- }
171
-
172
- return (
173
- <div ref={rootRef} className={cn("relative inline-flex", className)} {...props}>
174
- {trigger}
175
-
176
- <div
177
- id={listboxId}
178
- role="listbox"
179
- aria-label="Select language"
180
- style={
181
- {
182
- positionAnchor: anchorName,
183
- top: "anchor(bottom)",
184
- ...(align === "start" ? { left: "anchor(left)" } : { right: "anchor(right)" }),
185
- } as CSSProperties
186
- }
187
- className={cn(
188
- "absolute top-full z-50 mt-2 min-w-[10rem] rounded-lg border border-border bg-surface p-1 shadow-raised transition-all duration-150",
189
- align === "start" ? "left-0" : "right-0",
190
- open
191
- ? "visible translate-y-0 opacity-100"
192
- : "pointer-events-none invisible -translate-y-1 opacity-0",
193
- )}
194
- >
195
- {languages.map((lang) => {
196
- const active = lang.code === current;
197
- return (
198
- <button
199
- key={lang.code}
200
- type="button"
201
- role="option"
202
- aria-selected={active}
203
- onClick={() => select(lang.code)}
204
- className={cn(
205
- "flex w-full cursor-pointer items-center justify-between gap-3 rounded-md px-3 py-2 text-[13px] font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
206
- active
207
- ? "bg-surface-strong text-foreground"
208
- : "text-muted-foreground hover:bg-surface-strong hover:text-foreground",
209
- )}
210
- >
211
- <span className="flex items-center gap-2.5">
212
- {showFlag ? renderFlagOrGlobe(lang.code) : null}
213
- {lang.label}
214
- </span>
215
- {active ? <Check className="h-3.5 w-3.5 text-primary" /> : null}
216
- </button>
217
- );
218
- })}
219
- </div>
220
- </div>
221
- );
222
- };
223
- LanguageSwitcher.displayName = "LanguageSwitcher";
@@ -1,100 +0,0 @@
1
- import { forwardRef, type HTMLAttributes } from "react";
2
- import { cn } from "../utils/cn";
3
-
4
- export interface LogoItem {
5
- name: string;
6
- src?: string;
7
- href?: string;
8
- }
9
-
10
- export interface LogoCloudProps extends HTMLAttributes<HTMLDivElement> {
11
- logos: LogoItem[];
12
- title?: string;
13
- /** Visual option: quiet list · infinite marquee · hairline strip */
14
- option?: "quiet" | "marquee" | "strip";
15
- }
16
-
17
- const LogoText = ({ name }: { name: string }) => (
18
- <span className="text-lg font-semibold tracking-tight text-foreground/45 transition-colors duration-200 group-hover:text-foreground">
19
- {name}
20
- </span>
21
- );
22
-
23
- const LogoMark = ({ logo }: { logo: LogoItem }) =>
24
- logo.src ? (
25
- <img
26
- src={logo.src}
27
- alt={logo.name}
28
- loading="lazy"
29
- className={cn(
30
- "max-h-7 w-auto opacity-50 grayscale transition-all duration-200 group-hover:opacity-90 group-hover:grayscale-0",
31
- "h-7",
32
- )}
33
- />
34
- ) : (
35
- <LogoText name={logo.name} />
36
- );
37
-
38
- export const LogoCloud = forwardRef<HTMLDivElement, LogoCloudProps>(
39
- ({ className, logos, title, option = "quiet", ...props }, ref) => {
40
- if (option === "marquee") {
41
- const doubled = [...logos, ...logos];
42
- return (
43
- <div ref={ref} className={cn("w-full overflow-hidden", className)} {...props}>
44
- {title ? (
45
- <p className="mb-8 text-center text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
46
- {title}
47
- </p>
48
- ) : null}
49
- <div className="mask-fade-x">
50
- <div className="flex w-max animate-marquee gap-14 pr-14 motion-reduce:animate-none">
51
- {doubled.map((logo, i) => (
52
- <div key={`${logo.name}-${i}`} className="group shrink-0">
53
- <LogoMark logo={logo} />
54
- </div>
55
- ))}
56
- </div>
57
- </div>
58
- </div>
59
- );
60
- }
61
-
62
- if (option === "strip") {
63
- return (
64
- <div ref={ref} className={cn("w-full", className)} {...props}>
65
- {title ? (
66
- <p className="mb-8 text-center text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
67
- {title}
68
- </p>
69
- ) : null}
70
- <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">
71
- {logos.map((logo) => (
72
- <li key={logo.name} className="group">
73
- <LogoMark logo={logo} />
74
- </li>
75
- ))}
76
- </ul>
77
- </div>
78
- );
79
- }
80
-
81
- // quiet — unbounded spread of wordmarks.
82
- return (
83
- <div ref={ref} className={cn("w-full", className)} {...props}>
84
- {title ? (
85
- <p className="mb-8 text-center text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
86
- {title}
87
- </p>
88
- ) : null}
89
- <ul className="flex flex-wrap items-center justify-center gap-x-12 gap-y-6">
90
- {logos.map((logo) => (
91
- <li key={logo.name} className="group">
92
- <LogoMark logo={logo} />
93
- </li>
94
- ))}
95
- </ul>
96
- </div>
97
- );
98
- },
99
- );
100
- LogoCloud.displayName = "LogoCloud";
@@ -1,195 +0,0 @@
1
- import { useState, type ElementType, type HTMLAttributes, type ReactNode } from "react";
2
- import { cn } from "../utils/cn";
3
- import { Menu, X } from "../icons";
4
-
5
- export interface NavLink {
6
- label: string;
7
- href: string;
8
- }
9
-
10
- export interface NavbarProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
11
- variant?: "classic" | "floating" | "inverse";
12
- /** Wordmark or fallback text rendered when no logo is supplied. */
13
- brand?: ReactNode;
14
- /** Custom logo node (component, <img>, inline SVG…). Replaces the wordmark. */
15
- logo?: ReactNode;
16
- /** Quick logo image source. */
17
- logoSrc?: string;
18
- logoAlt?: string;
19
- /** Size / position tuning for logo images. Defaults to `h-7 w-auto`. */
20
- logoClassName?: string;
21
- brandHref?: string;
22
- links: NavLink[];
23
- actions?: ReactNode;
24
- cta?: ReactNode;
25
- /** Optional language switcher rendered on the right (desktop) and beside the menu button (mobile). */
26
- languageSwitcher?: ReactNode;
27
- sticky?: boolean;
28
- /** Component used to render the brand and nav links. Defaults to `<a>`. Pass your router's `<Link>` (Next.js, Remix, React Router…) for framework-aware navigation. */
29
- LinkComponent?: ElementType;
30
- }
31
-
32
- const linkStyles = {
33
- classic: "text-muted-foreground hover:text-foreground",
34
- floating: "text-muted-foreground hover:text-foreground",
35
- inverse: "text-on-secondary/60 hover:text-on-secondary",
36
- };
37
-
38
- export const Navbar = ({
39
- className,
40
- variant = "classic",
41
- brand,
42
- logo,
43
- logoSrc,
44
- logoAlt,
45
- logoClassName,
46
- brandHref = "#",
47
- links,
48
- actions,
49
- cta,
50
- languageSwitcher,
51
- sticky = true,
52
- LinkComponent,
53
- ...props
54
- }: NavbarProps) => {
55
- const [open, setOpen] = useState(false);
56
- const inverse = variant === "inverse";
57
- const Link = LinkComponent ?? "a";
58
-
59
- const shell = cn(
60
- "w-full transition-colors duration-200",
61
- sticky && "sticky top-0 z-50",
62
- variant === "classic" &&
63
- "border-b border-border bg-background/85 backdrop-blur-md supports-[backdrop-filter]:bg-background/75",
64
- variant === "floating" && "px-0",
65
- variant === "inverse" && "border-b border-white/10 bg-[#101010]/85 backdrop-blur-md",
66
- );
67
-
68
- const bar = cn(
69
- "flex h-16 w-full items-center justify-between gap-4 transition-colors duration-200",
70
- variant === "floating" &&
71
- "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",
72
- variant === "classic" && "mx-auto max-w-6xl px-5 sm:px-8",
73
- variant === "inverse" && "mx-auto max-w-6xl px-5 sm:px-8",
74
- );
75
-
76
- const logoNode =
77
- logo ??
78
- (logoSrc ? (
79
- <img
80
- src={logoSrc}
81
- alt={logoAlt ?? (typeof brand === "string" ? brand : "Logo")}
82
- className={cn("h-7 w-auto", logoClassName)}
83
- />
84
- ) : null);
85
-
86
- const brandNode = logoNode ? (
87
- <span className="flex items-center">{logoNode}</span>
88
- ) : (
89
- <span
90
- className={cn(
91
- "flex items-center gap-2 font-display text-[17px] font-bold tracking-tight",
92
- inverse ? "text-on-secondary" : "text-foreground",
93
- )}
94
- >
95
- <span
96
- aria-hidden
97
- className={cn(
98
- "inline-block h-2.5 w-2.5 rounded-sm",
99
- inverse ? "bg-on-secondary" : "bg-foreground",
100
- )}
101
- />
102
- {brand}
103
- </span>
104
- );
105
-
106
- return (
107
- <header className={cn(shell, variant === "floating" && "px-3 sm:px-6", className)} {...props}>
108
- <nav className={bar} aria-label="Main navigation">
109
- <Link href={brandHref} className="shrink-0">
110
- {brandNode}
111
- </Link>
112
-
113
- <ul className="hidden items-center gap-7 md:flex">
114
- {links.map((link) => (
115
- <li key={link.href}>
116
- <Link
117
- href={link.href}
118
- className={cn(
119
- "text-sm font-medium transition-colors duration-150",
120
- linkStyles[variant],
121
- )}
122
- >
123
- {link.label}
124
- </Link>
125
- </li>
126
- ))}
127
- </ul>
128
-
129
- <div className="hidden items-center gap-3 md:flex">
130
- {languageSwitcher}
131
- {actions}
132
- {cta}
133
- </div>
134
-
135
- {languageSwitcher ? (
136
- <div className="flex items-center md:hidden">{languageSwitcher}</div>
137
- ) : null}
138
-
139
- <button
140
- type="button"
141
- aria-label={open ? "Close menu" : "Open menu"}
142
- aria-expanded={open}
143
- onClick={() => setOpen((o) => !o)}
144
- className={cn(
145
- "inline-flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-md md:hidden",
146
- "transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
147
- inverse ? "text-on-secondary hover:bg-white/10" : "text-foreground hover:bg-surface",
148
- )}
149
- >
150
- {open ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
151
- </button>
152
- </nav>
153
-
154
- {/* Mobile panel */}
155
- <div
156
- className={cn(
157
- "grid transition-[grid-template-rows,opacity] duration-200 md:hidden",
158
- open ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
159
- inverse
160
- ? "border-t border-white/10 bg-[#101010]"
161
- : variant === "floating"
162
- ? "mx-3 mb-3 max-w-5xl overflow-hidden rounded-b-xl border border-border bg-surface"
163
- : "border-t border-border bg-background",
164
- )}
165
- >
166
- <div className="min-h-0 overflow-hidden">
167
- <ul className={cn("flex flex-col py-4", variant !== "floating" && "px-5")}>
168
- {links.map((link) => (
169
- <li key={link.href}>
170
- <Link
171
- href={link.href}
172
- onClick={() => setOpen(false)}
173
- className={cn(
174
- "block rounded-md px-2 py-2.5 text-sm font-medium transition-colors duration-150",
175
- inverse
176
- ? "text-on-secondary/70 hover:bg-white/10 hover:text-on-secondary"
177
- : "text-muted-foreground hover:bg-surface hover:text-foreground",
178
- )}
179
- >
180
- {link.label}
181
- </Link>
182
- </li>
183
- ))}
184
- {cta ? (
185
- <li className="mt-3 px-2 pb-2">{cta}</li>
186
- ) : actions ? (
187
- <li className="mt-3 flex flex-wrap gap-3 px-2 pb-2">{actions}</li>
188
- ) : null}
189
- </ul>
190
- </div>
191
- </div>
192
- </header>
193
- );
194
- };
195
- Navbar.displayName = "Navbar";