@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.
Files changed (112) hide show
  1. package/dist/styles.css +12 -0
  2. package/package.json +14 -7
  3. package/src/components/Badge.tsx +0 -68
  4. package/src/components/Blog/BlogCard.tsx +0 -12
  5. package/src/components/Blog/BlogSection.tsx +0 -95
  6. package/src/components/Blog/index.ts +0 -8
  7. package/src/components/Blog/parts.tsx +0 -38
  8. package/src/components/Blog/types.ts +0 -42
  9. package/src/components/Blog/variants/Card.tsx +0 -62
  10. package/src/components/Blog/variants/Row.tsx +0 -41
  11. package/src/components/Button.tsx +0 -113
  12. package/src/components/CTA/CTA.tsx +0 -12
  13. package/src/components/CTA/index.ts +0 -2
  14. package/src/components/CTA/parts.tsx +0 -26
  15. package/src/components/CTA/types.ts +0 -12
  16. package/src/components/CTA/variants/Inverse.tsx +0 -50
  17. package/src/components/CTA/variants/Panel.tsx +0 -60
  18. package/src/components/CTA/variants/Surface.tsx +0 -34
  19. package/src/components/Card.tsx +0 -80
  20. package/src/components/Container.tsx +0 -86
  21. package/src/components/FAQ/FAQ.tsx +0 -48
  22. package/src/components/FAQ/index.ts +0 -2
  23. package/src/components/FAQ/parts.tsx +0 -74
  24. package/src/components/FAQ/types.ts +0 -18
  25. package/src/components/FAQ/variants/Accordion.tsx +0 -46
  26. package/src/components/FAQ/variants/Cards.tsx +0 -40
  27. package/src/components/FAQ/variants/Split.tsx +0 -44
  28. package/src/components/Features/FeatureCard.tsx +0 -44
  29. package/src/components/Features/FeatureGrid.tsx +0 -14
  30. package/src/components/Features/index.ts +0 -3
  31. package/src/components/Features/parts.ts +0 -15
  32. package/src/components/Features/types.ts +0 -25
  33. package/src/components/Features/variants/Bento.tsx +0 -32
  34. package/src/components/Features/variants/Columns.tsx +0 -21
  35. package/src/components/Features/variants/EditorialRows.tsx +0 -38
  36. package/src/components/Footer/Footer.tsx +0 -11
  37. package/src/components/Footer/index.ts +0 -2
  38. package/src/components/Footer/parts.tsx +0 -101
  39. package/src/components/Footer/types.ts +0 -32
  40. package/src/components/Footer/variants/Classic.tsx +0 -59
  41. package/src/components/Footer/variants/Editorial.tsx +0 -47
  42. package/src/components/Footer/variants/Minimal.tsx +0 -33
  43. package/src/components/Hero/Hero.tsx +0 -12
  44. package/src/components/Hero/index.ts +0 -2
  45. package/src/components/Hero/parts.tsx +0 -29
  46. package/src/components/Hero/types.ts +0 -20
  47. package/src/components/Hero/variants/Centered.tsx +0 -54
  48. package/src/components/Hero/variants/Split.tsx +0 -53
  49. package/src/components/Hero/variants/Statement.tsx +0 -60
  50. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +0 -37
  51. package/src/components/LanguageSwitcher/index.ts +0 -6
  52. package/src/components/LanguageSwitcher/parts.tsx +0 -144
  53. package/src/components/LanguageSwitcher/types.ts +0 -40
  54. package/src/components/LanguageSwitcher/useLanguageSwitcher.ts +0 -23
  55. package/src/components/LanguageSwitcher/variants/Dropdown.tsx +0 -88
  56. package/src/components/LanguageSwitcher/variants/Modal.tsx +0 -56
  57. package/src/components/LogoCloud/LogoCloud.tsx +0 -14
  58. package/src/components/LogoCloud/index.ts +0 -2
  59. package/src/components/LogoCloud/parts.tsx +0 -32
  60. package/src/components/LogoCloud/types.ts +0 -16
  61. package/src/components/LogoCloud/variants/Marquee.tsx +0 -30
  62. package/src/components/LogoCloud/variants/Quiet.tsx +0 -26
  63. package/src/components/LogoCloud/variants/Strip.tsx +0 -25
  64. package/src/components/MegaMenu/MegaMenu.tsx +0 -454
  65. package/src/components/MegaMenu/index.ts +0 -9
  66. package/src/components/MegaMenu/types.ts +0 -56
  67. package/src/components/Modal.tsx +0 -125
  68. package/src/components/Navbar/Navbar.tsx +0 -185
  69. package/src/components/Navbar/index.ts +0 -2
  70. package/src/components/Navbar/types.ts +0 -30
  71. package/src/components/Newsletter/Newsletter.tsx +0 -12
  72. package/src/components/Newsletter/index.ts +0 -2
  73. package/src/components/Newsletter/parts.tsx +0 -23
  74. package/src/components/Newsletter/types.ts +0 -12
  75. package/src/components/Newsletter/useNewsletterForm.ts +0 -27
  76. package/src/components/Newsletter/variants/Card.tsx +0 -62
  77. package/src/components/Newsletter/variants/Inline.tsx +0 -54
  78. package/src/components/Newsletter/variants/Underline.tsx +0 -52
  79. package/src/components/Pricing/Pricing.tsx +0 -37
  80. package/src/components/Pricing/index.ts +0 -2
  81. package/src/components/Pricing/parts.tsx +0 -132
  82. package/src/components/Pricing/types.ts +0 -28
  83. package/src/components/Pricing/variants/Bento.tsx +0 -72
  84. package/src/components/Pricing/variants/Cards.tsx +0 -79
  85. package/src/components/Pricing/variants/Compact.tsx +0 -69
  86. package/src/components/Prose.tsx +0 -55
  87. package/src/components/Section.tsx +0 -106
  88. package/src/components/Slot.tsx +0 -78
  89. package/src/components/Stats/Stats.tsx +0 -16
  90. package/src/components/Stats/index.ts +0 -2
  91. package/src/components/Stats/parts.tsx +0 -29
  92. package/src/components/Stats/types.ts +0 -23
  93. package/src/components/Stats/variants/Cells.tsx +0 -34
  94. package/src/components/Stats/variants/Editorial.tsx +0 -41
  95. package/src/components/Stats/variants/Hairline.tsx +0 -31
  96. package/src/components/Stats/variants/Ticker.tsx +0 -36
  97. package/src/components/Testimonials/TestimonialCard.tsx +0 -61
  98. package/src/components/Testimonials/Testimonials.tsx +0 -14
  99. package/src/components/Testimonials/index.ts +0 -3
  100. package/src/components/Testimonials/parts.ts +0 -7
  101. package/src/components/Testimonials/types.ts +0 -24
  102. package/src/components/Testimonials/variants/Carousel.tsx +0 -94
  103. package/src/components/Testimonials/variants/Grid.tsx +0 -19
  104. package/src/components/Testimonials/variants/Marquee.tsx +0 -28
  105. package/src/icons.tsx +0 -290
  106. package/src/index.ts +0 -120
  107. package/src/styles/index.css +0 -18
  108. package/src/utils/cn.ts +0 -6
  109. package/src/utils/contrast.ts +0 -107
  110. package/src/utils/iso2Flag.ts +0 -26
  111. package/src/utils/useTokenForeground.ts +0 -39
  112. /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.4",
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": "./src/styles/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
  }
@@ -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,8 +0,0 @@
1
- export { BlogCard } from "./BlogCard";
2
- export { BlogSection } from "./BlogSection";
3
- export type {
4
- BlogCardProps,
5
- BlogSectionProps,
6
- BlogPost,
7
- BlogAuthor,
8
- } from "./types";
@@ -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,2 +0,0 @@
1
- export { CTA } from "./CTA";
2
- export type { CTAProps, CTAOption } from "./types";
@@ -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
- }