@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,50 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import { cn } from "../../../utils/cn";
|
|
4
|
-
import { Container } from "../../Container";
|
|
5
|
-
import { useTokenForeground } from "../../../utils/useTokenForeground";
|
|
6
|
-
import { CTAActions } from "../parts";
|
|
7
|
-
import type { CTAProps } from "../types";
|
|
8
|
-
|
|
9
|
-
/** inverse — ink panel with a wide left-aligned split. */
|
|
10
|
-
export const CTAInverse = forwardRef<HTMLElement, CTAProps>(
|
|
11
|
-
({ className, title, description, action, secondaryAction, align = "left", id, ...props }, ref) => {
|
|
12
|
-
const fg = useTokenForeground("--color-secondary");
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<section ref={ref} id={id} className={cn("w-full py-6 pb-20", className)} {...props}>
|
|
16
|
-
<Container>
|
|
17
|
-
<div
|
|
18
|
-
className="relative overflow-hidden rounded-[1.75rem] px-7 py-16 sm:px-16 sm:py-20"
|
|
19
|
-
style={{
|
|
20
|
-
backgroundColor: "rgb(var(--color-secondary))",
|
|
21
|
-
color: fg,
|
|
22
|
-
}}
|
|
23
|
-
>
|
|
24
|
-
<div aria-hidden className="pointer-events-none absolute inset-0 grain opacity-[0.09] mix-blend-overlay" />
|
|
25
|
-
<div
|
|
26
|
-
className={cn(
|
|
27
|
-
"relative flex flex-col items-start gap-10",
|
|
28
|
-
align === "center" && "items-center text-center",
|
|
29
|
-
"lg:flex-row lg:items-end lg:justify-between",
|
|
30
|
-
)}
|
|
31
|
-
>
|
|
32
|
-
<div className={cn("max-w-3xl", align === "center" && "mx-auto")}>
|
|
33
|
-
<h2 className="font-display text-balance text-4xl font-semibold leading-[1.02] tracking-[-0.03em] sm:text-6xl">
|
|
34
|
-
{title}
|
|
35
|
-
</h2>
|
|
36
|
-
{description ? (
|
|
37
|
-
<p className={cn("mt-5 max-w-xl text-pretty text-base leading-7 sm:text-lg", align === "center" && "mx-auto")}>
|
|
38
|
-
{description}
|
|
39
|
-
</p>
|
|
40
|
-
) : null}
|
|
41
|
-
</div>
|
|
42
|
-
<CTAActions action={action} secondaryAction={secondaryAction} align={align} />
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
</Container>
|
|
46
|
-
</section>
|
|
47
|
-
);
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
CTAInverse.displayName = "CTAInverse";
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import { cn } from "../../../utils/cn";
|
|
4
|
-
import { Container } from "../../Container";
|
|
5
|
-
import { useTokenForeground } from "../../../utils/useTokenForeground";
|
|
6
|
-
import { CTAActions } from "../parts";
|
|
7
|
-
import type { CTAProps } from "../types";
|
|
8
|
-
|
|
9
|
-
/** panel — brand gradient panel, rounded corners. */
|
|
10
|
-
export const CTAPanel = forwardRef<HTMLElement, CTAProps>(
|
|
11
|
-
({ className, title, description, action, secondaryAction, align = "left", id, ...props }, ref) => {
|
|
12
|
-
const fg = useTokenForeground("--color-primary");
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<section ref={ref} id={id} className={cn("w-full py-6 pb-20", className)} {...props}>
|
|
16
|
-
<Container>
|
|
17
|
-
<div
|
|
18
|
-
className="relative overflow-hidden rounded-[1.75rem] px-7 py-14 sm:px-14 sm:py-20"
|
|
19
|
-
style={{
|
|
20
|
-
background:
|
|
21
|
-
"radial-gradient(120% 160% at 0% 0%, rgb(var(--color-primary)), rgb(var(--color-primary-hover)) 55%, rgb(var(--color-primary) / 0.85))",
|
|
22
|
-
color: fg,
|
|
23
|
-
}}
|
|
24
|
-
>
|
|
25
|
-
<div aria-hidden className="pointer-events-none absolute inset-0 grain opacity-[0.07] mix-blend-overlay" />
|
|
26
|
-
<div
|
|
27
|
-
aria-hidden
|
|
28
|
-
className="pointer-events-none absolute -right-24 -top-24 h-72 w-72 rounded-full"
|
|
29
|
-
style={{ background: "rgb(var(--color-accent))", opacity: 0.35, filter: "blur(60px)" }}
|
|
30
|
-
/>
|
|
31
|
-
<div
|
|
32
|
-
className={cn(
|
|
33
|
-
"relative flex flex-col gap-9",
|
|
34
|
-
align === "center" ? "items-center text-center" : "items-start",
|
|
35
|
-
)}
|
|
36
|
-
>
|
|
37
|
-
<div className={cn("max-w-2xl", align === "center" && "mx-auto")}>
|
|
38
|
-
<h2 className="font-display text-balance text-3xl font-semibold leading-[1.06] tracking-[-0.02em] sm:text-5xl">
|
|
39
|
-
{title}
|
|
40
|
-
</h2>
|
|
41
|
-
{description ? (
|
|
42
|
-
<p
|
|
43
|
-
className={cn(
|
|
44
|
-
"mt-4 text-pretty text-base leading-7 sm:text-lg",
|
|
45
|
-
align === "center" && "mx-auto max-w-xl",
|
|
46
|
-
)}
|
|
47
|
-
>
|
|
48
|
-
{description}
|
|
49
|
-
</p>
|
|
50
|
-
) : null}
|
|
51
|
-
</div>
|
|
52
|
-
<CTAActions action={action} secondaryAction={secondaryAction} align={align} />
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</Container>
|
|
56
|
-
</section>
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
CTAPanel.displayName = "CTAPanel";
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import { cn } from "../../../utils/cn";
|
|
3
|
-
import { Container } from "../../Container";
|
|
4
|
-
import { CTAActions } from "../parts";
|
|
5
|
-
import type { CTAProps } from "../types";
|
|
6
|
-
|
|
7
|
-
/** surface — quiet paper band: hairline rule, big display type, actions right. */
|
|
8
|
-
export const CTASurface = forwardRef<HTMLElement, CTAProps>(
|
|
9
|
-
({ className, title, description, action, secondaryAction, align = "left", id, ...props }, ref) => (
|
|
10
|
-
<section ref={ref} id={id} className={cn("w-full", className)} {...props}>
|
|
11
|
-
<Container className="py-20 sm:py-24">
|
|
12
|
-
<div
|
|
13
|
-
className={cn(
|
|
14
|
-
"flex flex-col gap-10 border-t border-border pt-14 sm:flex-row sm:items-end sm:justify-between sm:pt-16",
|
|
15
|
-
align === "center" && "items-center text-center sm:flex-col sm:items-center",
|
|
16
|
-
)}
|
|
17
|
-
>
|
|
18
|
-
<div className={cn("max-w-3xl", align === "center" && "mx-auto")}>
|
|
19
|
-
<h2 className="font-display text-4xl font-semibold leading-[1.03] tracking-[-0.03em] text-balance text-foreground sm:text-6xl">
|
|
20
|
-
{title}
|
|
21
|
-
</h2>
|
|
22
|
-
{description ? (
|
|
23
|
-
<p className={cn("mt-5 max-w-xl text-pretty text-base leading-7 text-muted-foreground sm:text-lg", align === "center" && "mx-auto")}>
|
|
24
|
-
{description}
|
|
25
|
-
</p>
|
|
26
|
-
) : null}
|
|
27
|
-
</div>
|
|
28
|
-
<CTAActions action={action} secondaryAction={secondaryAction} align={align} />
|
|
29
|
-
</div>
|
|
30
|
-
</Container>
|
|
31
|
-
</section>
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
CTASurface.displayName = "CTASurface";
|
package/src/components/Card.tsx
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
|
|
4
|
-
export type CardVariant = "outlined" | "elevated" | "inset";
|
|
5
|
-
|
|
6
|
-
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
variant?: CardVariant;
|
|
8
|
-
/** Softens the whole card with hover lift + border tint. */
|
|
9
|
-
interactive?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const variants: Record<CardVariant, string> = {
|
|
13
|
-
outlined: "border border-border bg-surface",
|
|
14
|
-
elevated: "border border-border bg-surface shadow-soft",
|
|
15
|
-
inset: "bg-surface-strong",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const Card = forwardRef<HTMLDivElement, CardProps>(
|
|
19
|
-
({ className, variant = "outlined", interactive = false, ...props }, ref) => (
|
|
20
|
-
<div
|
|
21
|
-
ref={ref}
|
|
22
|
-
className={cn(
|
|
23
|
-
"rounded-xl transition-[border-color,box-shadow,transform] duration-200",
|
|
24
|
-
variants[variant],
|
|
25
|
-
interactive &&
|
|
26
|
-
"cursor-pointer hover:-translate-y-0.5 hover:border-foreground/20 hover:shadow-raised",
|
|
27
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
28
|
-
className,
|
|
29
|
-
)}
|
|
30
|
-
{...props}
|
|
31
|
-
/>
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
Card.displayName = "Card";
|
|
35
|
-
|
|
36
|
-
export interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
37
|
-
icon?: ReactNode;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
|
41
|
-
({ className, icon, children, ...props }, ref) => (
|
|
42
|
-
<div ref={ref} className={cn("flex flex-col gap-2 p-6 pb-0", className)} {...props}>
|
|
43
|
-
{icon ? <div className="mb-1.5">{icon}</div> : null}
|
|
44
|
-
{children}
|
|
45
|
-
</div>
|
|
46
|
-
),
|
|
47
|
-
);
|
|
48
|
-
CardHeader.displayName = "CardHeader";
|
|
49
|
-
|
|
50
|
-
export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
|
|
51
|
-
({ className, ...props }, ref) => (
|
|
52
|
-
<h3
|
|
53
|
-
ref={ref}
|
|
54
|
-
className={cn("font-display text-lg font-semibold tracking-tight text-foreground", className)}
|
|
55
|
-
{...props}
|
|
56
|
-
/>
|
|
57
|
-
),
|
|
58
|
-
);
|
|
59
|
-
CardTitle.displayName = "CardTitle";
|
|
60
|
-
|
|
61
|
-
export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
|
62
|
-
({ className, ...props }, ref) => (
|
|
63
|
-
<p ref={ref} className={cn("text-sm leading-6 text-muted-foreground", className)} {...props} />
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
CardDescription.displayName = "CardDescription";
|
|
67
|
-
|
|
68
|
-
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
69
|
-
({ className, ...props }, ref) => (
|
|
70
|
-
<div ref={ref} className={cn("p-6 pt-5", className)} {...props} />
|
|
71
|
-
),
|
|
72
|
-
);
|
|
73
|
-
CardContent.displayName = "CardContent";
|
|
74
|
-
|
|
75
|
-
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
76
|
-
({ className, ...props }, ref) => (
|
|
77
|
-
<div ref={ref} className={cn("flex items-center gap-3 border-t border-border/70 p-6 pt-5", className)} {...props} />
|
|
78
|
-
),
|
|
79
|
-
);
|
|
80
|
-
CardFooter.displayName = "CardFooter";
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type ElementType, type HTMLAttributes } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
|
|
4
|
-
export type ContainerSize = "sm" | "md" | "lg" | "xl" | "full";
|
|
5
|
-
|
|
6
|
-
export interface ContainerProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
-
/** Render as a specific element (section, header, …). */
|
|
8
|
-
as?: ElementType;
|
|
9
|
-
/**
|
|
10
|
-
* Maximum content width:
|
|
11
|
-
* `sm` → prose · `md` → reading · `lg` → plans/splits ·
|
|
12
|
-
* `xl` → default marketing page · `full` → edge-to-edge.
|
|
13
|
-
*/
|
|
14
|
-
size?: ContainerSize;
|
|
15
|
-
/** Responsive gutters (px-5 / sm:px-8). Disable for flush content. */
|
|
16
|
-
gutters?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const sizes: Record<Exclude<ContainerSize, "full">, string> = {
|
|
20
|
-
sm: "max-w-xl",
|
|
21
|
-
md: "max-w-3xl",
|
|
22
|
-
lg: "max-w-5xl",
|
|
23
|
-
xl: "max-w-6xl",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Shared layout wrapper — the single source of the container + gutter rules
|
|
28
|
-
* used across Section, Footer, CTA, templates…
|
|
29
|
-
*/
|
|
30
|
-
export const Container = forwardRef<HTMLElement, ContainerProps>(
|
|
31
|
-
({ className, as, size = "xl", gutters = true, ...props }, ref) => {
|
|
32
|
-
const Tag = (as ?? "div") as ElementType;
|
|
33
|
-
return (
|
|
34
|
-
<Tag
|
|
35
|
-
ref={ref as never}
|
|
36
|
-
className={cn(
|
|
37
|
-
"mx-auto w-full",
|
|
38
|
-
size !== "full" && sizes[size],
|
|
39
|
-
gutters && "px-5 sm:px-8",
|
|
40
|
-
className,
|
|
41
|
-
)}
|
|
42
|
-
{...props}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
Container.displayName = "Container";
|
|
48
|
-
|
|
49
|
-
export type StackGap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20;
|
|
50
|
-
|
|
51
|
-
const gaps: Record<StackGap, string> = {
|
|
52
|
-
0: "gap-0",
|
|
53
|
-
1: "gap-1",
|
|
54
|
-
2: "gap-2",
|
|
55
|
-
3: "gap-3",
|
|
56
|
-
4: "gap-4",
|
|
57
|
-
5: "gap-5",
|
|
58
|
-
6: "gap-6",
|
|
59
|
-
8: "gap-8",
|
|
60
|
-
10: "gap-10",
|
|
61
|
-
12: "gap-12",
|
|
62
|
-
16: "gap-16",
|
|
63
|
-
20: "gap-20",
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
|
|
67
|
-
gap?: StackGap;
|
|
68
|
-
horizontal?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Vertical (or horizontal) rhythm helper using flex gaps. */
|
|
72
|
-
export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
73
|
-
({ className, gap = 4, horizontal = false, ...props }, ref) => (
|
|
74
|
-
<div
|
|
75
|
-
ref={ref}
|
|
76
|
-
className={cn(
|
|
77
|
-
"flex",
|
|
78
|
-
horizontal ? "flex-row flex-wrap items-center" : "flex-col",
|
|
79
|
-
gaps[gap],
|
|
80
|
-
className,
|
|
81
|
-
)}
|
|
82
|
-
{...props}
|
|
83
|
-
/>
|
|
84
|
-
),
|
|
85
|
-
);
|
|
86
|
-
Stack.displayName = "Stack";
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { FAQProps } from "./types";
|
|
2
|
-
import { FaqCards } from "./variants/Cards";
|
|
3
|
-
import { FaqAccordion } from "./variants/Accordion";
|
|
4
|
-
import { FaqSplit } from "./variants/Split";
|
|
5
|
-
|
|
6
|
-
export const FAQ = ({
|
|
7
|
-
className,
|
|
8
|
-
items,
|
|
9
|
-
option = "accordion",
|
|
10
|
-
allowMultiple = false,
|
|
11
|
-
defaultOpen = [],
|
|
12
|
-
eyebrow,
|
|
13
|
-
title,
|
|
14
|
-
description,
|
|
15
|
-
...props
|
|
16
|
-
}: FAQProps) => {
|
|
17
|
-
if (option === "cards") {
|
|
18
|
-
return <FaqCards className={className} items={items} eyebrow={eyebrow} {...props} />;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (option === "split") {
|
|
22
|
-
return (
|
|
23
|
-
<FaqSplit
|
|
24
|
-
className={className}
|
|
25
|
-
items={items}
|
|
26
|
-
allowMultiple={allowMultiple}
|
|
27
|
-
defaultOpen={defaultOpen}
|
|
28
|
-
eyebrow={eyebrow}
|
|
29
|
-
title={title}
|
|
30
|
-
description={description}
|
|
31
|
-
{...props}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// accordion
|
|
37
|
-
return (
|
|
38
|
-
<div className="mx-auto w-full max-w-3xl">
|
|
39
|
-
<FaqAccordion
|
|
40
|
-
className={className}
|
|
41
|
-
items={items}
|
|
42
|
-
allowMultiple={allowMultiple}
|
|
43
|
-
defaultOpen={defaultOpen}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
FAQ.displayName = "FAQ";
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { cn } from "../../utils/cn";
|
|
4
|
-
import { Plus } from "../../icons";
|
|
5
|
-
import type { FaqItem } from "./types";
|
|
6
|
-
|
|
7
|
-
export function useFaqState(allowMultiple: boolean, defaultOpen: number[]) {
|
|
8
|
-
const [openSet, setOpenSet] = useState<Set<number>>(
|
|
9
|
-
() => new Set(allowMultiple ? defaultOpen : defaultOpen.slice(0, 1)),
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
const toggle = (index: number) => {
|
|
13
|
-
setOpenSet((prev) => {
|
|
14
|
-
const next = new Set(prev);
|
|
15
|
-
if (next.has(index)) {
|
|
16
|
-
next.delete(index);
|
|
17
|
-
} else {
|
|
18
|
-
if (!allowMultiple) next.clear();
|
|
19
|
-
next.add(index);
|
|
20
|
-
}
|
|
21
|
-
return next;
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return { openSet, toggle, isOpen: (i: number) => openSet.has(i) };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function FaqItemRow({
|
|
29
|
-
item,
|
|
30
|
-
open,
|
|
31
|
-
onToggle,
|
|
32
|
-
style,
|
|
33
|
-
}: {
|
|
34
|
-
item: FaqItem;
|
|
35
|
-
open: boolean;
|
|
36
|
-
onToggle: () => void;
|
|
37
|
-
style: "boxed" | "bare";
|
|
38
|
-
}) {
|
|
39
|
-
return (
|
|
40
|
-
<div>
|
|
41
|
-
<h3>
|
|
42
|
-
<button
|
|
43
|
-
type="button"
|
|
44
|
-
aria-expanded={open}
|
|
45
|
-
onClick={onToggle}
|
|
46
|
-
className={
|
|
47
|
-
style === "boxed"
|
|
48
|
-
? "flex w-full cursor-pointer items-center justify-between gap-4 px-6 py-5 text-left text-[15px] font-medium text-foreground transition-colors duration-150 hover:bg-surface-strong/60 focus-visible:outline-none"
|
|
49
|
-
: "flex w-full cursor-pointer items-center justify-between gap-4 py-5 text-left text-base font-semibold text-foreground transition-colors duration-150 focus-visible:outline-none"
|
|
50
|
-
}
|
|
51
|
-
>
|
|
52
|
-
{item.question}
|
|
53
|
-
<Plus
|
|
54
|
-
className={cn(
|
|
55
|
-
"h-4.5 w-4.5 shrink-0 text-foreground/45 transition-transform duration-200 sm:h-5 sm:w-5",
|
|
56
|
-
open && "rotate-45 text-primary",
|
|
57
|
-
)}
|
|
58
|
-
/>
|
|
59
|
-
</button>
|
|
60
|
-
</h3>
|
|
61
|
-
{open ? (
|
|
62
|
-
<p className={style === "boxed" ? "px-6 pb-6 text-sm leading-7 text-muted-foreground" : "pb-6 text-sm leading-7 text-muted-foreground"}>
|
|
63
|
-
{item.answer}
|
|
64
|
-
</p>
|
|
65
|
-
) : null}
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function FaqEyebrow({ label }: { label: string }) {
|
|
71
|
-
return (
|
|
72
|
-
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary">{label}</p>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
export type FAQOption = "accordion" | "split" | "cards";
|
|
4
|
-
|
|
5
|
-
export interface FaqItem {
|
|
6
|
-
question: string;
|
|
7
|
-
answer: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface FAQProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
11
|
-
items: FaqItem[];
|
|
12
|
-
option?: FAQOption;
|
|
13
|
-
allowMultiple?: boolean;
|
|
14
|
-
defaultOpen?: number[];
|
|
15
|
-
eyebrow?: ReactNode;
|
|
16
|
-
title?: ReactNode;
|
|
17
|
-
description?: ReactNode;
|
|
18
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { cn } from "../../../utils/cn";
|
|
3
|
-
import { useFaqState, FaqItemRow } from "../parts";
|
|
4
|
-
import type { FAQProps } from "../types";
|
|
5
|
-
|
|
6
|
-
interface AccordionProps extends FAQProps {
|
|
7
|
-
allowMultiple?: boolean;
|
|
8
|
-
defaultOpen?: number[];
|
|
9
|
-
bare?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const FaqAccordion = ({
|
|
13
|
-
className,
|
|
14
|
-
items,
|
|
15
|
-
allowMultiple = false,
|
|
16
|
-
defaultOpen = [],
|
|
17
|
-
bare = false,
|
|
18
|
-
option,
|
|
19
|
-
title,
|
|
20
|
-
eyebrow,
|
|
21
|
-
description,
|
|
22
|
-
...props
|
|
23
|
-
}: AccordionProps) => {
|
|
24
|
-
const { isOpen, toggle } = useFaqState(allowMultiple, defaultOpen);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div
|
|
28
|
-
className={cn(
|
|
29
|
-
!bare && "divide-y divide-border rounded-2xl border border-border bg-surface",
|
|
30
|
-
className,
|
|
31
|
-
)}
|
|
32
|
-
{...props}
|
|
33
|
-
>
|
|
34
|
-
{items.map((item, index) => (
|
|
35
|
-
<FaqItemRow
|
|
36
|
-
key={item.question}
|
|
37
|
-
item={item}
|
|
38
|
-
open={isOpen(index)}
|
|
39
|
-
onToggle={() => toggle(index)}
|
|
40
|
-
style={bare ? "bare" : "boxed"}
|
|
41
|
-
/>
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
FaqAccordion.displayName = "FaqAccordion";
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { cn } from "../../../utils/cn";
|
|
2
|
-
import { FaqEyebrow } from "../parts";
|
|
3
|
-
import type { FAQProps } from "../types";
|
|
4
|
-
|
|
5
|
-
interface CardsProps extends FAQProps {}
|
|
6
|
-
|
|
7
|
-
export const FaqCards = ({
|
|
8
|
-
className,
|
|
9
|
-
items,
|
|
10
|
-
eyebrow,
|
|
11
|
-
title,
|
|
12
|
-
description,
|
|
13
|
-
option,
|
|
14
|
-
...props
|
|
15
|
-
}: CardsProps) => (
|
|
16
|
-
<div className={cn("w-full", className)} {...props}>
|
|
17
|
-
{eyebrow ? <div className="mb-10"><FaqEyebrow label={String(eyebrow)} /></div> : null}
|
|
18
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
19
|
-
{items.map((item, i) => (
|
|
20
|
-
<div
|
|
21
|
-
key={item.question}
|
|
22
|
-
className="flex flex-col rounded-xl border border-border bg-surface p-7"
|
|
23
|
-
>
|
|
24
|
-
<div className="flex items-baseline gap-4">
|
|
25
|
-
<span className="font-display text-sm font-semibold tabular-nums text-foreground/35">
|
|
26
|
-
{String(i + 1).padStart(2, "0")}
|
|
27
|
-
</span>
|
|
28
|
-
<h3 className="font-display text-lg font-semibold tracking-tight text-foreground">
|
|
29
|
-
{item.question}
|
|
30
|
-
</h3>
|
|
31
|
-
</div>
|
|
32
|
-
<p className="mt-4 border-t border-border pt-4 text-sm leading-6 text-muted-foreground">
|
|
33
|
-
{item.answer}
|
|
34
|
-
</p>
|
|
35
|
-
</div>
|
|
36
|
-
))}
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
FaqCards.displayName = "FaqCards";
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { cn } from "../../../utils/cn";
|
|
2
|
-
import { FaqEyebrow } from "../parts";
|
|
3
|
-
import { FaqAccordion } from "./Accordion";
|
|
4
|
-
import type { FAQProps } from "../types";
|
|
5
|
-
|
|
6
|
-
interface SplitProps extends FAQProps {
|
|
7
|
-
allowMultiple?: boolean;
|
|
8
|
-
defaultOpen?: number[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const FaqSplit = ({
|
|
12
|
-
className,
|
|
13
|
-
items,
|
|
14
|
-
allowMultiple = false,
|
|
15
|
-
defaultOpen = [],
|
|
16
|
-
eyebrow,
|
|
17
|
-
title,
|
|
18
|
-
description,
|
|
19
|
-
option,
|
|
20
|
-
...props
|
|
21
|
-
}: SplitProps) => (
|
|
22
|
-
<div className={cn("grid w-full gap-10 lg:grid-cols-[0.9fr_1.1fr] lg:gap-20", className)} {...props}>
|
|
23
|
-
<div className="lg:sticky lg:top-24 lg:self-start">
|
|
24
|
-
{eyebrow ? <FaqEyebrow label={String(eyebrow)} /> : null}
|
|
25
|
-
{title ? (
|
|
26
|
-
<h2 className="mt-4 font-display text-3xl font-semibold leading-[1.08] tracking-tight text-foreground sm:text-4xl">
|
|
27
|
-
{title}
|
|
28
|
-
</h2>
|
|
29
|
-
) : null}
|
|
30
|
-
{description ? (
|
|
31
|
-
<p className="mt-4 text-pretty text-base leading-7 text-muted-foreground">{description}</p>
|
|
32
|
-
) : null}
|
|
33
|
-
</div>
|
|
34
|
-
<div className="divide-y divide-border">
|
|
35
|
-
<FaqAccordion
|
|
36
|
-
items={items}
|
|
37
|
-
allowMultiple={allowMultiple}
|
|
38
|
-
defaultOpen={defaultOpen}
|
|
39
|
-
bare
|
|
40
|
-
/>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
FaqSplit.displayName = "FaqSplit";
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import { cn } from "../../utils/cn";
|
|
3
|
-
import { getContrastText } from "../../utils/contrast";
|
|
4
|
-
import type { FeatureCardProps } from "./types";
|
|
5
|
-
|
|
6
|
-
export const FeatureCard = forwardRef<HTMLDivElement, FeatureCardProps>(
|
|
7
|
-
({ className, icon, title, description, accent, large = false, ...props }, ref) => {
|
|
8
|
-
const chipStyle = accent
|
|
9
|
-
? { backgroundColor: accent, color: getContrastText(accent, "#111111", "#ffffff") }
|
|
10
|
-
: undefined;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
ref={ref}
|
|
15
|
-
className={cn(
|
|
16
|
-
"group relative flex h-full flex-col rounded-xl border border-border bg-surface p-7 transition-colors duration-200 hover:border-foreground/15",
|
|
17
|
-
className,
|
|
18
|
-
)}
|
|
19
|
-
{...props}
|
|
20
|
-
>
|
|
21
|
-
{icon ? (
|
|
22
|
-
<div
|
|
23
|
-
className={cn(
|
|
24
|
-
"mb-5 inline-flex h-10 w-10 items-center justify-center rounded-lg text-[1.05rem]",
|
|
25
|
-
!accent && "bg-primary-soft text-primary",
|
|
26
|
-
)}
|
|
27
|
-
style={chipStyle}
|
|
28
|
-
>
|
|
29
|
-
{icon}
|
|
30
|
-
</div>
|
|
31
|
-
) : null}
|
|
32
|
-
<h3 className={cn("font-display font-semibold tracking-tight text-foreground", large ? "text-2xl" : "text-lg")}>
|
|
33
|
-
{title}
|
|
34
|
-
</h3>
|
|
35
|
-
{description ? (
|
|
36
|
-
<p className={cn("mt-2.5 text-pretty leading-relaxed text-muted-foreground", large ? "text-[15px]" : "text-sm")}>
|
|
37
|
-
{description}
|
|
38
|
-
</p>
|
|
39
|
-
) : null}
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
FeatureCard.displayName = "FeatureCard";
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { FeatureGridProps } from "./types";
|
|
3
|
-
import { FeatureColumns } from "./variants/Columns";
|
|
4
|
-
import { FeatureBento } from "./variants/Bento";
|
|
5
|
-
import { FeatureEditorialRows } from "./variants/EditorialRows";
|
|
6
|
-
|
|
7
|
-
export const FeatureGrid = forwardRef<HTMLDivElement, FeatureGridProps>(
|
|
8
|
-
({ option = "columns", columns = 3, ...props }, ref) => {
|
|
9
|
-
if (option === "editorialRows") return <FeatureEditorialRows ref={ref} {...props} />;
|
|
10
|
-
if (option === "bento") return <FeatureBento ref={ref} {...props} />;
|
|
11
|
-
return <FeatureColumns ref={ref} columns={columns} {...props} />;
|
|
12
|
-
},
|
|
13
|
-
);
|
|
14
|
-
FeatureGrid.displayName = "FeatureGrid";
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const gridCols: Record<2 | 3 | 4, string> = {
|
|
2
|
-
2: "sm:grid-cols-2",
|
|
3
|
-
3: "sm:grid-cols-2 lg:grid-cols-3",
|
|
4
|
-
4: "sm:grid-cols-2 lg:grid-cols-4",
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
/** Asymmetric bento layout for up to 6 features. */
|
|
8
|
-
export const bentoSpans: string[] = [
|
|
9
|
-
"sm:col-span-2 lg:row-span-2",
|
|
10
|
-
"",
|
|
11
|
-
"",
|
|
12
|
-
"lg:col-span-2",
|
|
13
|
-
"",
|
|
14
|
-
"lg:col-span-2",
|
|
15
|
-
];
|