@ugurdemirel/landcraft 0.1.4 → 0.1.9
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.
- package/dist/styles.css +12 -0
- package/package.json +14 -7
- package/src/components/Badge.tsx +0 -68
- package/src/components/Blog/BlogCard.tsx +0 -12
- package/src/components/Blog/BlogSection.tsx +0 -95
- package/src/components/Blog/index.ts +0 -8
- package/src/components/Blog/parts.tsx +0 -38
- package/src/components/Blog/types.ts +0 -42
- package/src/components/Blog/variants/Card.tsx +0 -62
- package/src/components/Blog/variants/Row.tsx +0 -41
- package/src/components/Button.tsx +0 -113
- package/src/components/CTA/CTA.tsx +0 -12
- package/src/components/CTA/index.ts +0 -2
- package/src/components/CTA/parts.tsx +0 -26
- package/src/components/CTA/types.ts +0 -12
- package/src/components/CTA/variants/Inverse.tsx +0 -50
- package/src/components/CTA/variants/Panel.tsx +0 -60
- package/src/components/CTA/variants/Surface.tsx +0 -34
- package/src/components/Card.tsx +0 -80
- package/src/components/Container.tsx +0 -86
- package/src/components/FAQ/FAQ.tsx +0 -48
- package/src/components/FAQ/index.ts +0 -2
- package/src/components/FAQ/parts.tsx +0 -74
- package/src/components/FAQ/types.ts +0 -18
- package/src/components/FAQ/variants/Accordion.tsx +0 -46
- package/src/components/FAQ/variants/Cards.tsx +0 -40
- package/src/components/FAQ/variants/Split.tsx +0 -44
- package/src/components/Features/FeatureCard.tsx +0 -44
- package/src/components/Features/FeatureGrid.tsx +0 -14
- package/src/components/Features/index.ts +0 -3
- package/src/components/Features/parts.ts +0 -15
- package/src/components/Features/types.ts +0 -25
- package/src/components/Features/variants/Bento.tsx +0 -32
- package/src/components/Features/variants/Columns.tsx +0 -21
- package/src/components/Features/variants/EditorialRows.tsx +0 -38
- package/src/components/Footer/Footer.tsx +0 -11
- package/src/components/Footer/index.ts +0 -2
- package/src/components/Footer/parts.tsx +0 -101
- package/src/components/Footer/types.ts +0 -32
- package/src/components/Footer/variants/Classic.tsx +0 -59
- package/src/components/Footer/variants/Editorial.tsx +0 -47
- package/src/components/Footer/variants/Minimal.tsx +0 -33
- package/src/components/Hero/Hero.tsx +0 -12
- package/src/components/Hero/index.ts +0 -2
- package/src/components/Hero/parts.tsx +0 -29
- package/src/components/Hero/types.ts +0 -20
- package/src/components/Hero/variants/Centered.tsx +0 -54
- package/src/components/Hero/variants/Split.tsx +0 -53
- package/src/components/Hero/variants/Statement.tsx +0 -60
- package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +0 -37
- package/src/components/LanguageSwitcher/index.ts +0 -6
- package/src/components/LanguageSwitcher/parts.tsx +0 -144
- package/src/components/LanguageSwitcher/types.ts +0 -40
- package/src/components/LanguageSwitcher/useLanguageSwitcher.ts +0 -23
- package/src/components/LanguageSwitcher/variants/Dropdown.tsx +0 -88
- package/src/components/LanguageSwitcher/variants/Modal.tsx +0 -56
- package/src/components/LogoCloud/LogoCloud.tsx +0 -14
- package/src/components/LogoCloud/index.ts +0 -2
- package/src/components/LogoCloud/parts.tsx +0 -32
- package/src/components/LogoCloud/types.ts +0 -16
- package/src/components/LogoCloud/variants/Marquee.tsx +0 -30
- package/src/components/LogoCloud/variants/Quiet.tsx +0 -26
- package/src/components/LogoCloud/variants/Strip.tsx +0 -25
- package/src/components/MegaMenu/MegaMenu.tsx +0 -454
- package/src/components/MegaMenu/index.ts +0 -9
- package/src/components/MegaMenu/types.ts +0 -56
- package/src/components/Modal.tsx +0 -125
- package/src/components/Navbar/Navbar.tsx +0 -185
- package/src/components/Navbar/index.ts +0 -2
- package/src/components/Navbar/types.ts +0 -30
- package/src/components/Newsletter/Newsletter.tsx +0 -12
- package/src/components/Newsletter/index.ts +0 -2
- package/src/components/Newsletter/parts.tsx +0 -23
- package/src/components/Newsletter/types.ts +0 -12
- package/src/components/Newsletter/useNewsletterForm.ts +0 -27
- package/src/components/Newsletter/variants/Card.tsx +0 -62
- package/src/components/Newsletter/variants/Inline.tsx +0 -54
- package/src/components/Newsletter/variants/Underline.tsx +0 -52
- package/src/components/Pricing/Pricing.tsx +0 -37
- package/src/components/Pricing/index.ts +0 -2
- package/src/components/Pricing/parts.tsx +0 -132
- package/src/components/Pricing/types.ts +0 -28
- package/src/components/Pricing/variants/Bento.tsx +0 -72
- package/src/components/Pricing/variants/Cards.tsx +0 -79
- package/src/components/Pricing/variants/Compact.tsx +0 -69
- package/src/components/Prose.tsx +0 -55
- package/src/components/Section.tsx +0 -106
- package/src/components/Slot.tsx +0 -78
- package/src/components/Stats/Stats.tsx +0 -16
- package/src/components/Stats/index.ts +0 -2
- package/src/components/Stats/parts.tsx +0 -29
- package/src/components/Stats/types.ts +0 -23
- package/src/components/Stats/variants/Cells.tsx +0 -34
- package/src/components/Stats/variants/Editorial.tsx +0 -41
- package/src/components/Stats/variants/Hairline.tsx +0 -31
- package/src/components/Stats/variants/Ticker.tsx +0 -36
- package/src/components/Testimonials/TestimonialCard.tsx +0 -61
- package/src/components/Testimonials/Testimonials.tsx +0 -14
- package/src/components/Testimonials/index.ts +0 -3
- package/src/components/Testimonials/parts.ts +0 -7
- package/src/components/Testimonials/types.ts +0 -24
- package/src/components/Testimonials/variants/Carousel.tsx +0 -94
- package/src/components/Testimonials/variants/Grid.tsx +0 -19
- package/src/components/Testimonials/variants/Marquee.tsx +0 -28
- package/src/icons.tsx +0 -290
- package/src/index.ts +0 -120
- package/src/styles/index.css +0 -18
- package/src/utils/cn.ts +0 -6
- package/src/utils/contrast.ts +0 -107
- package/src/utils/iso2Flag.ts +0 -26
- package/src/utils/useTokenForeground.ts +0 -39
- /package/{src/styles → dist}/theme.css +0 -0
|
@@ -1,454 +0,0 @@
|
|
|
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";
|
|
7
|
-
|
|
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;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const inverse = "text-on-secondary";
|
|
27
|
-
const light = "text-foreground";
|
|
28
|
-
|
|
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
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const MegaMenu = ({
|
|
84
|
-
className,
|
|
85
|
-
variant = "classic",
|
|
86
|
-
brand,
|
|
87
|
-
logo,
|
|
88
|
-
logoSrc,
|
|
89
|
-
logoAlt,
|
|
90
|
-
logoClassName,
|
|
91
|
-
brandHref = "#",
|
|
92
|
-
items,
|
|
93
|
-
actions,
|
|
94
|
-
cta,
|
|
95
|
-
languageSwitcher,
|
|
96
|
-
sticky = true,
|
|
97
|
-
LinkComponent,
|
|
98
|
-
onKeyDown,
|
|
99
|
-
...props
|
|
100
|
-
}: MegaMenuProps) => {
|
|
101
|
-
const ids = useId();
|
|
102
|
-
const Link = LinkComponent ?? "a";
|
|
103
|
-
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
|
104
|
-
const [mobileOpen, setMobileOpen] = useState(false);
|
|
105
|
-
const [mobileExpanded, setMobileExpanded] = useState<number | null>(null);
|
|
106
|
-
const s = styles[variant];
|
|
107
|
-
|
|
108
|
-
const closePanels = () => setOpenIndex(null);
|
|
109
|
-
const closeAll = () => {
|
|
110
|
-
setOpenIndex(null);
|
|
111
|
-
setMobileOpen(false);
|
|
112
|
-
setMobileExpanded(null);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const panelId = (i: number) => `${ids}-panel-${i}`;
|
|
116
|
-
|
|
117
|
-
const logoNode =
|
|
118
|
-
logo ??
|
|
119
|
-
(logoSrc ? (
|
|
120
|
-
<img
|
|
121
|
-
src={logoSrc}
|
|
122
|
-
alt={logoAlt ?? (typeof brand === "string" ? brand : "Logo")}
|
|
123
|
-
className={cn("h-7 w-auto", logoClassName)}
|
|
124
|
-
/>
|
|
125
|
-
) : null);
|
|
126
|
-
|
|
127
|
-
const brandNode = logoNode ? (
|
|
128
|
-
<span className="flex items-center">{logoNode}</span>
|
|
129
|
-
) : (
|
|
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)} />
|
|
132
|
-
{brand}
|
|
133
|
-
</span>
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const triggerClasses = cn(
|
|
137
|
-
"flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150",
|
|
138
|
-
s.trigger,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const renderColumns = (item: MegaMenuItem) => (
|
|
142
|
-
<div
|
|
143
|
-
className={cn(
|
|
144
|
-
"grid gap-x-10 gap-y-8 sm:grid-cols-2",
|
|
145
|
-
item.featured ? "" : "lg:grid-cols-3",
|
|
146
|
-
)}
|
|
147
|
-
>
|
|
148
|
-
{item.columns?.map((column, ci) => (
|
|
149
|
-
<div key={column.title ?? ci}>
|
|
150
|
-
{column.title ? (
|
|
151
|
-
<h4
|
|
152
|
-
className={cn(
|
|
153
|
-
"text-xs font-semibold uppercase tracking-[0.14em]",
|
|
154
|
-
s.columnTitle,
|
|
155
|
-
)}
|
|
156
|
-
>
|
|
157
|
-
{column.title}
|
|
158
|
-
</h4>
|
|
159
|
-
) : null}
|
|
160
|
-
<ul className={cn(column.title && "mt-3", "space-y-0.5")}>
|
|
161
|
-
{column.links.map((link) => (
|
|
162
|
-
<li key={link.href}>
|
|
163
|
-
<Link
|
|
164
|
-
href={link.href}
|
|
165
|
-
onClick={closeAll}
|
|
166
|
-
className={cn(
|
|
167
|
-
"-mx-2 block rounded-md px-2 py-2 transition-colors duration-150",
|
|
168
|
-
s.columnLink,
|
|
169
|
-
)}
|
|
170
|
-
>
|
|
171
|
-
<span className="block text-sm font-medium text-foreground">
|
|
172
|
-
{link.label}
|
|
173
|
-
</span>
|
|
174
|
-
{link.description ? (
|
|
175
|
-
<span
|
|
176
|
-
className={cn(
|
|
177
|
-
"mt-0.5 block text-[13px] leading-snug",
|
|
178
|
-
s.linkDescription,
|
|
179
|
-
)}
|
|
180
|
-
>
|
|
181
|
-
{link.description}
|
|
182
|
-
</span>
|
|
183
|
-
) : null}
|
|
184
|
-
</Link>
|
|
185
|
-
</li>
|
|
186
|
-
))}
|
|
187
|
-
</ul>
|
|
188
|
-
</div>
|
|
189
|
-
))}
|
|
190
|
-
</div>
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const renderFeatured = (item: MegaMenuItem, mobile = false) => {
|
|
194
|
-
const featured = item.featured;
|
|
195
|
-
if (!featured) return null;
|
|
196
|
-
const featuredStyle = featured.accent
|
|
197
|
-
? { backgroundColor: featured.accent, color: getContrastText(featured.accent) }
|
|
198
|
-
: undefined;
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<Link
|
|
202
|
-
href={featured.href}
|
|
203
|
-
onClick={closeAll}
|
|
204
|
-
className={cn(
|
|
205
|
-
"group relative block overflow-hidden rounded-lg p-6 transition-transform duration-150 hover:-translate-y-0.5",
|
|
206
|
-
mobile && "mt-8",
|
|
207
|
-
!featured.accent && "bg-primary text-on-primary",
|
|
208
|
-
)}
|
|
209
|
-
style={featuredStyle}
|
|
210
|
-
>
|
|
211
|
-
<div className="flex h-full flex-col justify-between gap-4">
|
|
212
|
-
<div>
|
|
213
|
-
<h4 className="font-display text-base font-semibold tracking-tight">
|
|
214
|
-
{featured.title}
|
|
215
|
-
</h4>
|
|
216
|
-
{featured.description ? (
|
|
217
|
-
<p className="mt-1.5 text-[13px] leading-relaxed opacity-80">
|
|
218
|
-
{featured.description}
|
|
219
|
-
</p>
|
|
220
|
-
) : null}
|
|
221
|
-
</div>
|
|
222
|
-
{featured.cta ? (
|
|
223
|
-
<span className="text-sm font-semibold underline decoration-1 underline-offset-4">
|
|
224
|
-
{featured.cta} →
|
|
225
|
-
</span>
|
|
226
|
-
) : null}
|
|
227
|
-
</div>
|
|
228
|
-
<div
|
|
229
|
-
aria-hidden
|
|
230
|
-
className="pointer-events-none absolute -right-8 -top-8 h-28 w-28 rounded-full opacity-20 blur-2xl bg-black/40"
|
|
231
|
-
/>
|
|
232
|
-
</Link>
|
|
233
|
-
);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
237
|
-
if (event.key === "Escape") {
|
|
238
|
-
closeAll();
|
|
239
|
-
}
|
|
240
|
-
onKeyDown?.(event);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
return (
|
|
244
|
-
<header
|
|
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
|
-
)}
|
|
252
|
-
onKeyDown={handleKeyDown}
|
|
253
|
-
{...props}
|
|
254
|
-
>
|
|
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">
|
|
256
|
-
<Link href={brandHref} className="shrink-0">
|
|
257
|
-
{brandNode}
|
|
258
|
-
</Link>
|
|
259
|
-
|
|
260
|
-
<ul className="hidden items-center gap-1 lg:flex">
|
|
261
|
-
{items.map((item, i) => {
|
|
262
|
-
const hasPanel = Boolean(item.columns?.length || item.featured);
|
|
263
|
-
const open = openIndex === i;
|
|
264
|
-
return (
|
|
265
|
-
<li key={item.label} className="relative">
|
|
266
|
-
{hasPanel ? (
|
|
267
|
-
<button
|
|
268
|
-
type="button"
|
|
269
|
-
aria-haspopup="true"
|
|
270
|
-
aria-expanded={open}
|
|
271
|
-
aria-controls={panelId(i)}
|
|
272
|
-
onClick={() => setOpenIndex(open ? null : i)}
|
|
273
|
-
onMouseEnter={() => setOpenIndex(i)}
|
|
274
|
-
className={cn(triggerClasses, open && s.triggerOpen)}
|
|
275
|
-
>
|
|
276
|
-
<span className="flex items-center gap-1.5">
|
|
277
|
-
{item.label}
|
|
278
|
-
{item.badge ? (
|
|
279
|
-
<span
|
|
280
|
-
className={cn(
|
|
281
|
-
"rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide",
|
|
282
|
-
s.badge,
|
|
283
|
-
)}
|
|
284
|
-
>
|
|
285
|
-
{item.badge}
|
|
286
|
-
</span>
|
|
287
|
-
) : null}
|
|
288
|
-
</span>
|
|
289
|
-
<ChevronDown
|
|
290
|
-
className={cn(
|
|
291
|
-
"h-4 w-4 shrink-0 transition-transform duration-150",
|
|
292
|
-
open && "rotate-180",
|
|
293
|
-
)}
|
|
294
|
-
/>
|
|
295
|
-
</button>
|
|
296
|
-
) : (
|
|
297
|
-
<Link href={item.href ?? "#"} className={triggerClasses}>
|
|
298
|
-
{item.label}
|
|
299
|
-
{item.badge ? (
|
|
300
|
-
<span className={cn("rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide", s.badge)}>
|
|
301
|
-
{item.badge}
|
|
302
|
-
</span>
|
|
303
|
-
) : null}
|
|
304
|
-
</Link>
|
|
305
|
-
)}
|
|
306
|
-
</li>
|
|
307
|
-
);
|
|
308
|
-
})}
|
|
309
|
-
</ul>
|
|
310
|
-
|
|
311
|
-
<div className="hidden items-center gap-3 lg:flex">
|
|
312
|
-
{languageSwitcher}
|
|
313
|
-
{actions}
|
|
314
|
-
{cta}
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
{languageSwitcher ? (
|
|
318
|
-
<div className="flex items-center lg:hidden">{languageSwitcher}</div>
|
|
319
|
-
) : null}
|
|
320
|
-
|
|
321
|
-
<button
|
|
322
|
-
type="button"
|
|
323
|
-
aria-label={mobileOpen ? "Close menu" : "Open menu"}
|
|
324
|
-
aria-expanded={mobileOpen}
|
|
325
|
-
onClick={() => setMobileOpen((o) => !o)}
|
|
326
|
-
className={cn(
|
|
327
|
-
"inline-flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-md lg:hidden",
|
|
328
|
-
"transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
329
|
-
s.mobileButton,
|
|
330
|
-
)}
|
|
331
|
-
>
|
|
332
|
-
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
|
333
|
-
</button>
|
|
334
|
-
</nav>
|
|
335
|
-
|
|
336
|
-
{items.map((item, i) => {
|
|
337
|
-
if (!item.columns?.length && !item.featured) return null;
|
|
338
|
-
const open = openIndex === i;
|
|
339
|
-
return (
|
|
340
|
-
<div
|
|
341
|
-
key={item.label}
|
|
342
|
-
id={panelId(i)}
|
|
343
|
-
role="region"
|
|
344
|
-
aria-label={`${item.label} menu`}
|
|
345
|
-
onMouseEnter={() => setOpenIndex(i)}
|
|
346
|
-
className={cn(
|
|
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",
|
|
349
|
-
open
|
|
350
|
-
? "visible translate-y-0 opacity-100"
|
|
351
|
-
: "pointer-events-none invisible -translate-y-1 opacity-0",
|
|
352
|
-
)}
|
|
353
|
-
>
|
|
354
|
-
<div
|
|
355
|
-
className={cn(
|
|
356
|
-
"overflow-hidden rounded-xl border p-6 md:p-8",
|
|
357
|
-
s.panel,
|
|
358
|
-
item.featured ? "lg:grid lg:grid-cols-[minmax(0,1fr)_auto] lg:gap-12" : "",
|
|
359
|
-
)}
|
|
360
|
-
>
|
|
361
|
-
{renderColumns(item)}
|
|
362
|
-
{item.featured && (
|
|
363
|
-
<div className={cn("mt-8 max-w-sm lg:mt-0 lg:w-64")}>
|
|
364
|
-
{renderFeatured(item)}
|
|
365
|
-
</div>
|
|
366
|
-
)}
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
);
|
|
370
|
-
})}
|
|
371
|
-
|
|
372
|
-
<div
|
|
373
|
-
className={cn(
|
|
374
|
-
"grid transition-[grid-template-rows,opacity] duration-200 lg:hidden",
|
|
375
|
-
mobileOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
|
|
376
|
-
s.mobilePanel,
|
|
377
|
-
)}
|
|
378
|
-
>
|
|
379
|
-
<div className="min-h-0 overflow-hidden">
|
|
380
|
-
<ul className="flex flex-col py-4">
|
|
381
|
-
{items.map((item, i) =>
|
|
382
|
-
item.columns?.length || item.featured ? (
|
|
383
|
-
<li key={item.label} className="px-5">
|
|
384
|
-
<button
|
|
385
|
-
type="button"
|
|
386
|
-
aria-expanded={mobileExpanded === i}
|
|
387
|
-
onClick={() =>
|
|
388
|
-
setMobileExpanded((prev) => (prev === i ? null : i))
|
|
389
|
-
}
|
|
390
|
-
className={cn(
|
|
391
|
-
"flex w-full items-center justify-between rounded-md px-2 py-2.5 text-sm font-medium transition-colors duration-150",
|
|
392
|
-
s.mobileButton,
|
|
393
|
-
)}
|
|
394
|
-
>
|
|
395
|
-
<span className="flex items-center gap-2">
|
|
396
|
-
{item.label}
|
|
397
|
-
{item.badge ? (
|
|
398
|
-
<span className={cn("rounded-full px-1.5 py-0.5 text-[10px] font-semibold tracking-wide", s.badge)}>
|
|
399
|
-
{item.badge}
|
|
400
|
-
</span>
|
|
401
|
-
) : null}
|
|
402
|
-
</span>
|
|
403
|
-
<ChevronDown
|
|
404
|
-
className={cn(
|
|
405
|
-
"h-4 w-4 shrink-0 transition-transform duration-150",
|
|
406
|
-
mobileExpanded === i && "rotate-180",
|
|
407
|
-
)}
|
|
408
|
-
/>
|
|
409
|
-
</button>
|
|
410
|
-
<div
|
|
411
|
-
className={cn(
|
|
412
|
-
"grid transition-[grid-template-rows,opacity] duration-200",
|
|
413
|
-
mobileExpanded === i
|
|
414
|
-
? "grid-rows-[1fr] opacity-100"
|
|
415
|
-
: "grid-rows-[0fr] opacity-0",
|
|
416
|
-
)}
|
|
417
|
-
>
|
|
418
|
-
<div className="min-h-0 overflow-hidden">
|
|
419
|
-
<div className="py-2 pl-1">
|
|
420
|
-
{renderColumns(item)}
|
|
421
|
-
{item.featured ? (
|
|
422
|
-
<div className="max-w-sm">{renderFeatured(item, true)}</div>
|
|
423
|
-
) : null}
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
</div>
|
|
427
|
-
</li>
|
|
428
|
-
) : (
|
|
429
|
-
<li key={item.label} className="px-5">
|
|
430
|
-
<Link
|
|
431
|
-
href={item.href ?? "#"}
|
|
432
|
-
onClick={() => setMobileOpen(false)}
|
|
433
|
-
className={cn(
|
|
434
|
-
"block rounded-md px-2 py-2.5 text-sm font-medium transition-colors duration-150",
|
|
435
|
-
s.mobileLink,
|
|
436
|
-
)}
|
|
437
|
-
>
|
|
438
|
-
{item.label}
|
|
439
|
-
</Link>
|
|
440
|
-
</li>
|
|
441
|
-
),
|
|
442
|
-
)}
|
|
443
|
-
{cta ? (
|
|
444
|
-
<li className="px-8 pb-2 pt-3">{cta}</li>
|
|
445
|
-
) : actions ? (
|
|
446
|
-
<li className="mt-3 flex flex-wrap gap-3 px-8 pb-2">{actions}</li>
|
|
447
|
-
) : null}
|
|
448
|
-
</ul>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
</header>
|
|
452
|
-
);
|
|
453
|
-
};
|
|
454
|
-
MegaMenu.displayName = "MegaMenu";
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
}
|
package/src/components/Modal.tsx
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { forwardRef, useEffect, useId, useRef, type HTMLAttributes, type ReactNode } from "react";
|
|
3
|
-
import { cn } from "../utils/cn";
|
|
4
|
-
import { X } from "../icons";
|
|
5
|
-
|
|
6
|
-
export interface ModalProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
7
|
-
open: boolean;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
/** Dialog heading (required for a11y; auto-linked via `aria-labelledby`). */
|
|
10
|
-
title?: ReactNode;
|
|
11
|
-
description?: ReactNode;
|
|
12
|
-
/** Rendered below the content, e.g. actions. */
|
|
13
|
-
footer?: ReactNode;
|
|
14
|
-
/** Content width. Defaults to `md`. */
|
|
15
|
-
size?: "sm" | "md" | "lg";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const sizes: Record<NonNullable<ModalProps["size"]>, string> = {
|
|
19
|
-
sm: "max-w-sm",
|
|
20
|
-
md: "max-w-lg",
|
|
21
|
-
lg: "max-w-2xl",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Accessible dialog built on the HTML Popover API.
|
|
26
|
-
*
|
|
27
|
-
* `popover="auto"` renders in the top layer and gives light-dismiss (outside
|
|
28
|
-
* click + `Escape`), automatic focus movement and restoration for free, so no
|
|
29
|
-
* portal, focus trap or key handlers are needed. State stays controlled via
|
|
30
|
-
* `open` / `onClose`.
|
|
31
|
-
*/
|
|
32
|
-
export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|
33
|
-
({ className, open, onClose, title, description, footer, size = "md", children, ...props }, ref) => {
|
|
34
|
-
const titleId = useId();
|
|
35
|
-
const descriptionId = useId();
|
|
36
|
-
const internalRef = useRef<HTMLDivElement | null>(null);
|
|
37
|
-
const onCloseRef = useRef(onClose);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
onCloseRef.current = onClose;
|
|
41
|
-
}, [onClose]);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const el = internalRef.current;
|
|
45
|
-
if (!el) return;
|
|
46
|
-
if (!el.hasAttribute("popover")) el.setAttribute("popover", "auto");
|
|
47
|
-
}, []);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const el = internalRef.current;
|
|
51
|
-
if (!el) return;
|
|
52
|
-
if (open) {
|
|
53
|
-
if (!el.matches(":popover-open")) el.showPopover();
|
|
54
|
-
document.body.style.overflow = "hidden";
|
|
55
|
-
} else {
|
|
56
|
-
if (el.matches(":popover-open")) el.hidePopover();
|
|
57
|
-
document.body.style.overflow = "";
|
|
58
|
-
}
|
|
59
|
-
}, [open]);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
const el = internalRef.current;
|
|
63
|
-
if (!el) return;
|
|
64
|
-
const onToggle = (event: Event) => {
|
|
65
|
-
if ((event as ToggleEvent).newState === "closed") onCloseRef.current?.();
|
|
66
|
-
};
|
|
67
|
-
el.addEventListener("toggle", onToggle);
|
|
68
|
-
return () => el.removeEventListener("toggle", onToggle);
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
useEffect(
|
|
72
|
-
() => () => {
|
|
73
|
-
document.body.style.overflow = "";
|
|
74
|
-
},
|
|
75
|
-
[],
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div
|
|
80
|
-
ref={(node) => {
|
|
81
|
-
internalRef.current = node;
|
|
82
|
-
if (typeof ref === "function") ref(node);
|
|
83
|
-
else if (ref) ref.current = node;
|
|
84
|
-
}}
|
|
85
|
-
role="dialog"
|
|
86
|
-
aria-modal="true"
|
|
87
|
-
aria-labelledby={title ? titleId : undefined}
|
|
88
|
-
aria-describedby={description ? descriptionId : undefined}
|
|
89
|
-
className={cn(
|
|
90
|
-
"modal-anim modal-backdrop fixed inset-0 z-[100] m-auto h-fit max-h-[calc(100vh-2rem)] w-fit max-w-[calc(100vw-2rem)] overflow-y-auto rounded-2xl border border-border bg-surface p-6 text-foreground shadow-overlay",
|
|
91
|
-
sizes[size],
|
|
92
|
-
className,
|
|
93
|
-
)}
|
|
94
|
-
{...props}
|
|
95
|
-
>
|
|
96
|
-
{title ? (
|
|
97
|
-
<h2
|
|
98
|
-
id={titleId}
|
|
99
|
-
className="pr-8 font-display text-lg font-semibold tracking-tight text-foreground"
|
|
100
|
-
>
|
|
101
|
-
{title}
|
|
102
|
-
</h2>
|
|
103
|
-
) : null}
|
|
104
|
-
{description ? (
|
|
105
|
-
<p id={descriptionId} className="mt-1.5 text-sm leading-6 text-muted-foreground">
|
|
106
|
-
{description}
|
|
107
|
-
</p>
|
|
108
|
-
) : null}
|
|
109
|
-
<button
|
|
110
|
-
type="button"
|
|
111
|
-
aria-label="Close dialog"
|
|
112
|
-
onClick={onClose}
|
|
113
|
-
className="absolute right-4 top-4 inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 hover:bg-surface-strong hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
114
|
-
>
|
|
115
|
-
<X className="h-4 w-4" />
|
|
116
|
-
</button>
|
|
117
|
-
<div className={title || description ? "mt-4" : "pt-8"}>{children}</div>
|
|
118
|
-
{footer ? (
|
|
119
|
-
<div className="mt-6 flex flex-wrap items-center justify-end gap-3">{footer}</div>
|
|
120
|
-
) : null}
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
},
|
|
124
|
-
);
|
|
125
|
-
Modal.displayName = "Modal";
|