@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
package/dist/styles.css
CHANGED
|
@@ -1679,6 +1679,9 @@
|
|
|
1679
1679
|
.aspect-\[16\/10\] {
|
|
1680
1680
|
aspect-ratio: 16/10;
|
|
1681
1681
|
}
|
|
1682
|
+
.h-1 {
|
|
1683
|
+
height: var(--spacing);
|
|
1684
|
+
}
|
|
1682
1685
|
.h-1\.5 {
|
|
1683
1686
|
height: calc(var(--spacing) * 1.5);
|
|
1684
1687
|
}
|
|
@@ -2268,6 +2271,12 @@
|
|
|
2268
2271
|
.p-1 {
|
|
2269
2272
|
padding: var(--spacing);
|
|
2270
2273
|
}
|
|
2274
|
+
.p-2 {
|
|
2275
|
+
padding: calc(var(--spacing) * 2);
|
|
2276
|
+
}
|
|
2277
|
+
.p-4 {
|
|
2278
|
+
padding: calc(var(--spacing) * 4);
|
|
2279
|
+
}
|
|
2271
2280
|
.p-5 {
|
|
2272
2281
|
padding: calc(var(--spacing) * 5);
|
|
2273
2282
|
}
|
|
@@ -2736,6 +2745,9 @@
|
|
|
2736
2745
|
.text-white {
|
|
2737
2746
|
color: var(--color-white);
|
|
2738
2747
|
}
|
|
2748
|
+
.lowercase {
|
|
2749
|
+
text-transform: lowercase;
|
|
2750
|
+
}
|
|
2739
2751
|
.uppercase {
|
|
2740
2752
|
text-transform: uppercase;
|
|
2741
2753
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ugurdemirel/landcraft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,29 +20,31 @@
|
|
|
20
20
|
"require": "./dist/index.cjs"
|
|
21
21
|
},
|
|
22
22
|
"./styles.css": "./dist/styles.css",
|
|
23
|
-
"./theme.css": "./
|
|
23
|
+
"./theme.css": "./dist/theme.css"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
-
"src",
|
|
28
27
|
"README.md"
|
|
29
28
|
],
|
|
30
29
|
"sideEffects": [
|
|
31
30
|
"*.css"
|
|
32
31
|
],
|
|
33
32
|
"scripts": {
|
|
34
|
-
"build": "pnpm run build:js && pnpm run build:css",
|
|
33
|
+
"build": "pnpm run build:js && pnpm run build:css && pnpm run copy:theme",
|
|
35
34
|
"build:js": "vite build",
|
|
36
35
|
"build:css": "tailwindcss -i ./src/styles/index.css -o ./dist/styles.css",
|
|
36
|
+
"copy:theme": "cp ./src/styles/theme.css ./dist/theme.css",
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
38
40
|
"clean": "rimraf dist",
|
|
39
41
|
"prepublishOnly": "pnpm run build"
|
|
40
42
|
},
|
|
41
43
|
"peerDependencies": {
|
|
44
|
+
"@tailwindcss/typography": "^0.5.15",
|
|
42
45
|
"react": ">=18.0.0",
|
|
43
46
|
"react-dom": ">=18.0.0",
|
|
44
|
-
"tailwindcss": "^4.0.0"
|
|
45
|
-
"@tailwindcss/typography": "^0.5.15"
|
|
47
|
+
"tailwindcss": "^4.0.0"
|
|
46
48
|
},
|
|
47
49
|
"peerDependenciesMeta": {
|
|
48
50
|
"@tailwindcss/typography": {
|
|
@@ -56,14 +58,19 @@
|
|
|
56
58
|
"devDependencies": {
|
|
57
59
|
"@tailwindcss/cli": "^4.0.0",
|
|
58
60
|
"@tailwindcss/typography": "^0.5.15",
|
|
61
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
62
|
+
"@testing-library/react": "^16.3.2",
|
|
63
|
+
"@testing-library/user-event": "^14.6.5",
|
|
59
64
|
"@types/node": "^22.9.0",
|
|
60
65
|
"@types/react": "^18.3.12",
|
|
61
66
|
"@types/react-dom": "^18.3.1",
|
|
62
67
|
"@vitejs/plugin-react": "^4.3.4",
|
|
68
|
+
"jsdom": "^30.0.1",
|
|
63
69
|
"rimraf": "^6.0.1",
|
|
64
70
|
"tailwindcss": "^4.0.0",
|
|
65
71
|
"typescript": "^5.6.3",
|
|
66
72
|
"vite": "^5.4.11",
|
|
67
|
-
"vite-plugin-dts": "^4.3.0"
|
|
73
|
+
"vite-plugin-dts": "^4.3.0",
|
|
74
|
+
"vitest": "^3.2.7"
|
|
68
75
|
}
|
|
69
76
|
}
|
package/src/components/Badge.tsx
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
import { getContrastText } from "../utils/contrast";
|
|
4
|
-
|
|
5
|
-
export type BadgeVariant = "solid" | "soft" | "outline" | "dot";
|
|
6
|
-
export type BadgeSize = "sm" | "md";
|
|
7
|
-
|
|
8
|
-
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
9
|
-
variant?: BadgeVariant;
|
|
10
|
-
size?: BadgeSize;
|
|
11
|
-
icon?: ReactNode;
|
|
12
|
-
/** Paints the chip with an arbitrary CSS color; text color is computed for contrast. */
|
|
13
|
-
customColor?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const base =
|
|
17
|
-
"inline-flex items-center rounded-full font-medium tracking-tight transition-colors duration-200";
|
|
18
|
-
|
|
19
|
-
const sizes: Record<BadgeSize, string> = {
|
|
20
|
-
sm: "px-2.5 py-0.5 text-xs gap-1",
|
|
21
|
-
md: "px-3 py-1 text-[13px] gap-1.5",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const variants: Record<BadgeVariant, string> = {
|
|
25
|
-
solid: "bg-primary text-on-primary",
|
|
26
|
-
soft: "bg-primary-soft text-primary",
|
|
27
|
-
outline: "text-muted-foreground ring-1 ring-inset ring-border",
|
|
28
|
-
dot: "text-foreground ring-1 ring-inset ring-border bg-surface",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
|
32
|
-
(
|
|
33
|
-
{ className, variant = "soft", size = "md", icon, customColor, style, children, ...props },
|
|
34
|
-
ref,
|
|
35
|
-
) => {
|
|
36
|
-
const customStyle = customColor
|
|
37
|
-
? { ...style, backgroundColor: customColor, color: getContrastText(customColor, "#111111", "#ffffff") }
|
|
38
|
-
: style;
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<span
|
|
42
|
-
ref={ref}
|
|
43
|
-
style={customStyle}
|
|
44
|
-
className={cn(
|
|
45
|
-
base,
|
|
46
|
-
sizes[size],
|
|
47
|
-
customColor ? undefined : variants[variant],
|
|
48
|
-
className,
|
|
49
|
-
)}
|
|
50
|
-
{...props}
|
|
51
|
-
>
|
|
52
|
-
{variant === "dot" ? (
|
|
53
|
-
<span
|
|
54
|
-
aria-hidden
|
|
55
|
-
className={cn(
|
|
56
|
-
"h-1.5 w-1.5 shrink-0 rounded-full",
|
|
57
|
-
customColor ? "currentColor" : "bg-primary",
|
|
58
|
-
)}
|
|
59
|
-
style={customColor ? { backgroundColor: customColor } : undefined}
|
|
60
|
-
/>
|
|
61
|
-
) : null}
|
|
62
|
-
{icon ? <span className="shrink-0 opacity-70">{icon}</span> : null}
|
|
63
|
-
{children}
|
|
64
|
-
</span>
|
|
65
|
-
);
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
Badge.displayName = "Badge";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { BlogCardProps } from "./types";
|
|
3
|
-
import { BlogCardCard } from "./variants/Card";
|
|
4
|
-
import { BlogCardRow } from "./variants/Row";
|
|
5
|
-
|
|
6
|
-
export const BlogCard = forwardRef<HTMLAnchorElement, BlogCardProps>(
|
|
7
|
-
({ option = "card", ...props }, ref) => {
|
|
8
|
-
if (option === "row") return <BlogCardRow ref={ref} {...props} />;
|
|
9
|
-
return <BlogCardCard ref={ref} {...props} />;
|
|
10
|
-
},
|
|
11
|
-
);
|
|
12
|
-
BlogCard.displayName = "BlogCard";
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import { cn } from "../../utils/cn";
|
|
3
|
-
import { ArrowRight } from "../../icons";
|
|
4
|
-
import { Container } from "../Container";
|
|
5
|
-
import { BlogCard } from "./BlogCard";
|
|
6
|
-
import type { BlogSectionProps } from "./types";
|
|
7
|
-
|
|
8
|
-
const gridCols: Record<2 | 3, string> = {
|
|
9
|
-
2: "sm:grid-cols-2",
|
|
10
|
-
3: "sm:grid-cols-2 lg:grid-cols-3",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const BlogSection = forwardRef<HTMLElement, BlogSectionProps>(
|
|
14
|
-
(
|
|
15
|
-
{
|
|
16
|
-
className,
|
|
17
|
-
posts,
|
|
18
|
-
limit,
|
|
19
|
-
columns = 3,
|
|
20
|
-
option = "card",
|
|
21
|
-
eyebrow,
|
|
22
|
-
eyebrowStyle = "caps",
|
|
23
|
-
title,
|
|
24
|
-
description,
|
|
25
|
-
showAllLabel,
|
|
26
|
-
showAllHref,
|
|
27
|
-
LinkComponent,
|
|
28
|
-
...props
|
|
29
|
-
},
|
|
30
|
-
ref,
|
|
31
|
-
) => {
|
|
32
|
-
const visible = limit ? posts.slice(0, limit) : posts;
|
|
33
|
-
const Link = LinkComponent ?? "a";
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<section ref={ref} className={cn("w-full py-20 sm:py-24", className)} {...props}>
|
|
37
|
-
<Container>
|
|
38
|
-
{(eyebrow || title || description || showAllHref) && (
|
|
39
|
-
<div className="mb-12 flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
|
|
40
|
-
<div className="max-w-2xl">
|
|
41
|
-
{eyebrow ? (
|
|
42
|
-
<p
|
|
43
|
-
className={cn(
|
|
44
|
-
"text-xs font-semibold uppercase tracking-[0.18em]",
|
|
45
|
-
eyebrowStyle === "caps" && "text-foreground/50",
|
|
46
|
-
eyebrowStyle === "soft" &&
|
|
47
|
-
"inline-flex items-center rounded-full bg-primary-soft px-3 py-1.5 text-primary",
|
|
48
|
-
eyebrowStyle === "plain" && "text-primary",
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
{eyebrow}
|
|
52
|
-
</p>
|
|
53
|
-
) : null}
|
|
54
|
-
{title ? (
|
|
55
|
-
<h2 className="mt-4 font-display text-balance text-3xl font-semibold leading-[1.08] tracking-tight text-foreground sm:text-4xl">
|
|
56
|
-
{title}
|
|
57
|
-
</h2>
|
|
58
|
-
) : null}
|
|
59
|
-
{description ? (
|
|
60
|
-
<p className="mt-4 text-pretty text-base leading-7 text-muted-foreground">
|
|
61
|
-
{description}
|
|
62
|
-
</p>
|
|
63
|
-
) : null}
|
|
64
|
-
</div>
|
|
65
|
-
{showAllHref ? (
|
|
66
|
-
<Link
|
|
67
|
-
href={showAllHref}
|
|
68
|
-
className="group inline-flex shrink-0 cursor-pointer items-center gap-1.5 text-sm font-semibold text-foreground transition-colors duration-150 hover:text-primary"
|
|
69
|
-
>
|
|
70
|
-
{showAllLabel ?? "All posts"}
|
|
71
|
-
<ArrowRight className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5" />
|
|
72
|
-
</Link>
|
|
73
|
-
) : null}
|
|
74
|
-
</div>
|
|
75
|
-
)}
|
|
76
|
-
|
|
77
|
-
{option === "row" ? (
|
|
78
|
-
<div className="divide-y divide-border border-t border-border">
|
|
79
|
-
{visible.map((post) => (
|
|
80
|
-
<BlogCard key={post.title} post={post} option="row" LinkComponent={LinkComponent} />
|
|
81
|
-
))}
|
|
82
|
-
</div>
|
|
83
|
-
) : (
|
|
84
|
-
<div className={cn("grid grid-cols-1 gap-4", gridCols[columns])}>
|
|
85
|
-
{visible.map((post) => (
|
|
86
|
-
<BlogCard key={post.title} post={post} option="card" LinkComponent={LinkComponent} />
|
|
87
|
-
))}
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
90
|
-
</Container>
|
|
91
|
-
</section>
|
|
92
|
-
);
|
|
93
|
-
},
|
|
94
|
-
);
|
|
95
|
-
BlogSection.displayName = "BlogSection";
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { BlogPost } from "./types";
|
|
3
|
-
|
|
4
|
-
export function AuthorMark({ name, avatar }: { name: string; avatar?: ReactNode }) {
|
|
5
|
-
if (avatar) return avatar;
|
|
6
|
-
const initials = name
|
|
7
|
-
.split(" ")
|
|
8
|
-
.map((p) => p[0])
|
|
9
|
-
.join("")
|
|
10
|
-
.slice(0, 2)
|
|
11
|
-
.toUpperCase();
|
|
12
|
-
return (
|
|
13
|
-
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-primary-soft text-[11px] font-bold text-primary">
|
|
14
|
-
{initials}
|
|
15
|
-
</span>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const Cover = ({ post }: { post: BlogPost }) =>
|
|
20
|
-
post.cover ? (
|
|
21
|
-
<img src={post.cover} alt="" loading="lazy" className="aspect-[16/10] w-full object-cover" />
|
|
22
|
-
) : (
|
|
23
|
-
<div
|
|
24
|
-
className="grid aspect-[16/10] w-full place-items-center bg-surface-strong"
|
|
25
|
-
style={
|
|
26
|
-
post.accent
|
|
27
|
-
? { background: `linear-gradient(135deg, ${post.accent}22, ${post.accent}11)` }
|
|
28
|
-
: undefined
|
|
29
|
-
}
|
|
30
|
-
>
|
|
31
|
-
<span
|
|
32
|
-
className="font-display text-4xl font-bold tracking-[-0.04em]"
|
|
33
|
-
style={{ color: post.accent ?? "rgb(var(--color-foreground) / 0.12)" }}
|
|
34
|
-
>
|
|
35
|
-
{post.category?.[0]?.toUpperCase()}
|
|
36
|
-
</span>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { ElementType, HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
export interface BlogAuthor {
|
|
4
|
-
name: string;
|
|
5
|
-
avatar?: ReactNode;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface BlogPost {
|
|
9
|
-
title: string;
|
|
10
|
-
excerpt?: string;
|
|
11
|
-
/** Display date, e.g. "12 Aug 2026". */
|
|
12
|
-
date: string;
|
|
13
|
-
readTime?: string;
|
|
14
|
-
category?: string;
|
|
15
|
-
cover?: string;
|
|
16
|
-
/** Arbitrary CSS color for the cover placeholder accent. */
|
|
17
|
-
accent?: string;
|
|
18
|
-
href?: string;
|
|
19
|
-
author?: BlogAuthor;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface BlogCardProps extends HTMLAttributes<HTMLAnchorElement> {
|
|
23
|
-
post: BlogPost;
|
|
24
|
-
option?: "card" | "row";
|
|
25
|
-
/** Component used to render the card link. Defaults to `<a>`. Pass your router's `<Link>` (Next.js, Remix, React Router…) for framework-aware navigation. */
|
|
26
|
-
LinkComponent?: ElementType;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface BlogSectionProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
30
|
-
posts: BlogPost[];
|
|
31
|
-
/** Show only the first N posts. */
|
|
32
|
-
limit?: number;
|
|
33
|
-
columns?: 2 | 3;
|
|
34
|
-
option?: "card" | "row";
|
|
35
|
-
eyebrow?: ReactNode;
|
|
36
|
-
eyebrowStyle?: "caps" | "soft" | "plain";
|
|
37
|
-
title?: ReactNode;
|
|
38
|
-
description?: ReactNode;
|
|
39
|
-
showAllLabel?: string;
|
|
40
|
-
showAllHref?: string;
|
|
41
|
-
LinkComponent?: ElementType;
|
|
42
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type ElementType } from "react";
|
|
2
|
-
import { cn } from "../../../utils/cn";
|
|
3
|
-
import { ArrowRight } from "../../../icons";
|
|
4
|
-
import { Badge } from "../../Badge";
|
|
5
|
-
import { AuthorMark, Cover } from "../parts";
|
|
6
|
-
import type { BlogPost } from "../types";
|
|
7
|
-
|
|
8
|
-
interface BlogCardCardProps {
|
|
9
|
-
post: BlogPost;
|
|
10
|
-
LinkComponent?: ElementType;
|
|
11
|
-
className?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const BlogCardCard = forwardRef<HTMLAnchorElement, BlogCardCardProps>(
|
|
15
|
-
({ className, post, LinkComponent, ...props }, ref) => {
|
|
16
|
-
const Link = LinkComponent ?? "a";
|
|
17
|
-
const author = post.author;
|
|
18
|
-
return (
|
|
19
|
-
<Link
|
|
20
|
-
ref={ref}
|
|
21
|
-
href={post.href ?? "#"}
|
|
22
|
-
className={cn(
|
|
23
|
-
"group flex h-full cursor-pointer flex-col overflow-hidden rounded-xl border border-border bg-surface transition-colors duration-200 hover:border-foreground/20",
|
|
24
|
-
className,
|
|
25
|
-
)}
|
|
26
|
-
{...props}
|
|
27
|
-
>
|
|
28
|
-
<div className="overflow-hidden">
|
|
29
|
-
<Cover post={post} />
|
|
30
|
-
</div>
|
|
31
|
-
<div className="flex flex-1 flex-col p-6">
|
|
32
|
-
<div className="flex items-center justify-between gap-3">
|
|
33
|
-
{post.category ? <Badge variant="soft" size="sm">{post.category}</Badge> : <span />}
|
|
34
|
-
{post.readTime ? (
|
|
35
|
-
<span className="text-xs text-muted-foreground">{post.readTime}</span>
|
|
36
|
-
) : null}
|
|
37
|
-
</div>
|
|
38
|
-
<h3 className="mt-4 font-display text-lg font-semibold leading-snug tracking-tight text-foreground sm:text-xl">
|
|
39
|
-
{post.title}
|
|
40
|
-
</h3>
|
|
41
|
-
{post.excerpt ? (
|
|
42
|
-
<p className="mt-2.5 line-clamp-2 text-sm leading-6 text-muted-foreground">
|
|
43
|
-
{post.excerpt}
|
|
44
|
-
</p>
|
|
45
|
-
) : null}
|
|
46
|
-
<div className="mt-5 flex items-center justify-between gap-3 border-t border-border pt-4">
|
|
47
|
-
{author ? (
|
|
48
|
-
<span className="flex min-w-0 items-center gap-2.5">
|
|
49
|
-
<AuthorMark name={author.name} avatar={author.avatar} />
|
|
50
|
-
<span className="truncate text-xs font-medium text-foreground">{author.name}</span>
|
|
51
|
-
</span>
|
|
52
|
-
) : (
|
|
53
|
-
<span className="text-xs tabular-nums text-muted-foreground">{post.date}</span>
|
|
54
|
-
)}
|
|
55
|
-
<ArrowRight className="h-4 w-4 text-foreground/35 transition-all duration-200 group-hover:translate-x-0.5 group-hover:text-foreground" />
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</Link>
|
|
59
|
-
);
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
BlogCardCard.displayName = "BlogCardCard";
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type ElementType } from "react";
|
|
2
|
-
import { cn } from "../../../utils/cn";
|
|
3
|
-
import { ArrowUpRight } from "../../../icons";
|
|
4
|
-
import type { BlogPost } from "../types";
|
|
5
|
-
|
|
6
|
-
interface BlogCardRowProps {
|
|
7
|
-
post: BlogPost;
|
|
8
|
-
LinkComponent?: ElementType;
|
|
9
|
-
className?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const BlogCardRow = forwardRef<HTMLAnchorElement, BlogCardRowProps>(
|
|
13
|
-
({ className, post, LinkComponent, ...props }, ref) => {
|
|
14
|
-
const Link = LinkComponent ?? "a";
|
|
15
|
-
return (
|
|
16
|
-
<Link
|
|
17
|
-
ref={ref}
|
|
18
|
-
href={post.href ?? "#"}
|
|
19
|
-
className={cn("group flex cursor-pointer items-baseline gap-5 py-6", className)}
|
|
20
|
-
{...props}
|
|
21
|
-
>
|
|
22
|
-
<div className="min-w-0 flex-1">
|
|
23
|
-
<div className="flex items-center gap-3">
|
|
24
|
-
{post.category ? <span className="rounded-full bg-primary-soft px-2.5 py-0.5 text-xs font-semibold text-primary">{post.category}</span> : null}
|
|
25
|
-
<span className="text-xs tabular-nums text-muted-foreground">
|
|
26
|
-
{post.date}
|
|
27
|
-
{post.readTime ? ` · ${post.readTime}` : ""}
|
|
28
|
-
</span>
|
|
29
|
-
</div>
|
|
30
|
-
<h3 className="mt-2.5 font-display text-xl font-semibold tracking-tight text-foreground transition-transform duration-200 group-hover:translate-x-1 sm:text-2xl">
|
|
31
|
-
{post.title}
|
|
32
|
-
</h3>
|
|
33
|
-
</div>
|
|
34
|
-
<span className="text-foreground/30 transition-all duration-200 group-hover:translate-x-0.5 group-hover:text-foreground">
|
|
35
|
-
<ArrowUpRight className="h-5 w-5" />
|
|
36
|
-
</span>
|
|
37
|
-
</Link>
|
|
38
|
-
);
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
BlogCardRow.displayName = "BlogCardRow";
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type ButtonHTMLAttributes, type CSSProperties, type ReactNode } from "react";
|
|
2
|
-
import { cn } from "../utils/cn";
|
|
3
|
-
import { getContrastText } from "../utils/contrast";
|
|
4
|
-
import { Slot } from "./Slot";
|
|
5
|
-
|
|
6
|
-
export type ButtonVariant = "primary" | "dark" | "outline" | "ghost" | "link";
|
|
7
|
-
export type ButtonSize = "sm" | "md" | "lg";
|
|
8
|
-
|
|
9
|
-
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
10
|
-
variant?: ButtonVariant;
|
|
11
|
-
size?: ButtonSize;
|
|
12
|
-
iconLeft?: ReactNode;
|
|
13
|
-
iconRight?: ReactNode;
|
|
14
|
-
/** Paints the button with an arbitrary CSS color; text color is computed for contrast. */
|
|
15
|
-
customColor?: string;
|
|
16
|
-
fullWidth?: boolean;
|
|
17
|
-
/**
|
|
18
|
-
* When `true`, the button styling is applied to a single child element
|
|
19
|
-
* instead of a `<button>`. Use it to render a framework router `<Link>`
|
|
20
|
-
* (Next.js, Remix, React Router…). `iconLeft` / `iconRight` are ignored;
|
|
21
|
-
* pass icons inside the child instead.
|
|
22
|
-
*/
|
|
23
|
-
asChild?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const variants: Record<ButtonVariant, string> = {
|
|
27
|
-
primary: "bg-primary text-on-primary hover:bg-primary-hover",
|
|
28
|
-
dark: "bg-secondary text-on-secondary hover:bg-secondary-hover",
|
|
29
|
-
outline:
|
|
30
|
-
"border border-border bg-transparent text-foreground hover:border-foreground/30 hover:bg-surface",
|
|
31
|
-
ghost: "text-foreground hover:bg-surface",
|
|
32
|
-
link: "px-0 h-auto text-primary hover:text-primary-hover",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const sizes: Record<ButtonSize, string> = {
|
|
36
|
-
sm: "h-9 px-4 text-[13px] gap-1.5",
|
|
37
|
-
md: "h-11 px-5 text-sm gap-2",
|
|
38
|
-
lg: "h-12 px-7 text-[15px] gap-2",
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const base =
|
|
42
|
-
"group inline-flex select-none items-center justify-center rounded-lg font-medium tracking-tight transition-[background-color,border-color,color,box-shadow] duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 cursor-pointer";
|
|
43
|
-
|
|
44
|
-
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
45
|
-
(
|
|
46
|
-
{
|
|
47
|
-
className,
|
|
48
|
-
variant = "primary",
|
|
49
|
-
size = "md",
|
|
50
|
-
iconLeft,
|
|
51
|
-
iconRight,
|
|
52
|
-
customColor,
|
|
53
|
-
fullWidth,
|
|
54
|
-
asChild,
|
|
55
|
-
style,
|
|
56
|
-
type = "button",
|
|
57
|
-
children,
|
|
58
|
-
...props
|
|
59
|
-
},
|
|
60
|
-
ref,
|
|
61
|
-
) => {
|
|
62
|
-
const customStyle: CSSProperties | undefined = customColor
|
|
63
|
-
? {
|
|
64
|
-
...style,
|
|
65
|
-
backgroundColor: customColor,
|
|
66
|
-
color: getContrastText(customColor),
|
|
67
|
-
}
|
|
68
|
-
: style;
|
|
69
|
-
|
|
70
|
-
const isLink = variant === "link";
|
|
71
|
-
|
|
72
|
-
const classes = cn(
|
|
73
|
-
base,
|
|
74
|
-
sizes[size],
|
|
75
|
-
customColor ? "hover:brightness-95" : variants[variant],
|
|
76
|
-
isLink && "underline-offset-4 hover:underline",
|
|
77
|
-
fullWidth && "w-full",
|
|
78
|
-
className,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
if (asChild) {
|
|
82
|
-
return (
|
|
83
|
-
<Slot ref={ref} style={customStyle} className={classes} {...props}>
|
|
84
|
-
{children}
|
|
85
|
-
</Slot>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<button
|
|
91
|
-
ref={ref}
|
|
92
|
-
type={type}
|
|
93
|
-
style={customStyle}
|
|
94
|
-
className={classes}
|
|
95
|
-
{...props}
|
|
96
|
-
>
|
|
97
|
-
{iconLeft ? <span className={cn("shrink-0", !isLink && "opacity-70")}>{iconLeft}</span> : null}
|
|
98
|
-
{children}
|
|
99
|
-
{iconRight ? (
|
|
100
|
-
<span
|
|
101
|
-
className={cn(
|
|
102
|
-
"shrink-0 transition-transform duration-200",
|
|
103
|
-
!isLink && "opacity-70 group-hover:translate-x-0.5",
|
|
104
|
-
)}
|
|
105
|
-
>
|
|
106
|
-
{iconRight}
|
|
107
|
-
</span>
|
|
108
|
-
) : null}
|
|
109
|
-
</button>
|
|
110
|
-
);
|
|
111
|
-
},
|
|
112
|
-
);
|
|
113
|
-
Button.displayName = "Button";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { CTAProps } from "./types";
|
|
3
|
-
import { CTAPanel } from "./variants/Panel";
|
|
4
|
-
import { CTASurface } from "./variants/Surface";
|
|
5
|
-
import { CTAInverse } from "./variants/Inverse";
|
|
6
|
-
|
|
7
|
-
export const CTA = forwardRef<HTMLElement, CTAProps>(({ option = "panel", ...props }, ref) => {
|
|
8
|
-
if (option === "surface") return <CTASurface ref={ref} {...props} />;
|
|
9
|
-
if (option === "inverse") return <CTAInverse ref={ref} {...props} />;
|
|
10
|
-
return <CTAPanel ref={ref} {...props} />;
|
|
11
|
-
});
|
|
12
|
-
CTA.displayName = "CTA";
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { cn } from "../../utils/cn";
|
|
3
|
-
|
|
4
|
-
export function CTAActions({
|
|
5
|
-
action,
|
|
6
|
-
secondaryAction,
|
|
7
|
-
align,
|
|
8
|
-
actionClassName = "flex flex-wrap items-center gap-4",
|
|
9
|
-
}: {
|
|
10
|
-
action?: ReactNode;
|
|
11
|
-
secondaryAction?: ReactNode;
|
|
12
|
-
align?: "left" | "center";
|
|
13
|
-
actionClassName?: string;
|
|
14
|
-
}) {
|
|
15
|
-
return (
|
|
16
|
-
<div
|
|
17
|
-
className={cn(
|
|
18
|
-
actionClassName,
|
|
19
|
-
align === "center" && "justify-center",
|
|
20
|
-
)}
|
|
21
|
-
>
|
|
22
|
-
{action}
|
|
23
|
-
{secondaryAction}
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
export type CTAOption = "panel" | "surface" | "inverse";
|
|
4
|
-
|
|
5
|
-
export interface CTAProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
6
|
-
title: ReactNode;
|
|
7
|
-
description?: ReactNode;
|
|
8
|
-
action?: ReactNode;
|
|
9
|
-
secondaryAction?: ReactNode;
|
|
10
|
-
option?: CTAOption;
|
|
11
|
-
align?: "left" | "center";
|
|
12
|
-
}
|