ruler-components 0.1.0 → 0.1.2
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/index.d.mts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +534 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +498 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -2
- package/AGENTS.md +0 -9
- package/CLAUDE.md +0 -1
- package/app/docs/[slug]/page.tsx +0 -147
- package/app/docs/layout.tsx +0 -26
- package/app/docs/page.tsx +0 -148
- package/app/favicon.ico +0 -0
- package/app/globals.css +0 -168
- package/app/icon.png +0 -0
- package/app/layout.tsx +0 -90
- package/app/opengraph-image.tsx +0 -10
- package/app/page.tsx +0 -34
- package/app/twitter-image.tsx +0 -10
- package/components/docs/accordion-doc-page.tsx +0 -352
- package/components/docs/alert-doc-page.tsx +0 -293
- package/components/docs/allinput-doc-page.tsx +0 -183
- package/components/docs/avatar-doc-page.tsx +0 -424
- package/components/docs/badge-doc-page.tsx +0 -363
- package/components/docs/button-doc-page.tsx +0 -406
- package/components/docs/card-doc-page.tsx +0 -364
- package/components/docs/checkbox-doc-page.tsx +0 -277
- package/components/docs/colors-doc-page.tsx +0 -430
- package/components/docs/dialog-doc-page.tsx +0 -364
- package/components/docs/docs-footer.tsx +0 -11
- package/components/docs/docs-mobile-menu-bar.tsx +0 -22
- package/components/docs/docs-mobile-nav-context.tsx +0 -27
- package/components/docs/docs-page.tsx +0 -263
- package/components/docs/docs-search.tsx +0 -303
- package/components/docs/docs-sidebar.tsx +0 -165
- package/components/docs/docs-topbar.tsx +0 -32
- package/components/docs/dropdown-doc-page.tsx +0 -522
- package/components/docs/getting-started-doc-page.tsx +0 -205
- package/components/docs/label-doc-page.tsx +0 -173
- package/components/docs/loaders-doc-page.tsx +0 -244
- package/components/docs/menu-doc-page.tsx +0 -373
- package/components/docs/modal-doc-page.tsx +0 -436
- package/components/docs/next-steps.tsx +0 -53
- package/components/docs/pagination-doc-page.tsx +0 -316
- package/components/docs/progress-doc-page.tsx +0 -387
- package/components/docs/radio-doc-page.tsx +0 -270
- package/components/docs/skeleton-doc-page.tsx +0 -230
- package/components/docs/stepper-doc-page.tsx +0 -309
- package/components/docs/switch-doc-page.tsx +0 -264
- package/components/docs/table-doc-page.tsx +0 -689
- package/components/docs/tabs-doc-page.tsx +0 -359
- package/components/docs/theming-doc-page.tsx +0 -243
- package/components/docs/tooltip-doc-page.tsx +0 -357
- package/components/docs/usage-section.tsx +0 -21
- package/components/icons/discord.tsx +0 -9
- package/components/icons/github.tsx +0 -9
- package/components/icons/x.tsx +0 -9
- package/components/installation-section.tsx +0 -34
- package/components/landing/feature-highlights.tsx +0 -105
- package/components/landing/footer-strip.tsx +0 -45
- package/components/landing/hero-card.tsx +0 -14
- package/components/landing/hero-spotlight.tsx +0 -126
- package/components/landing/hero.tsx +0 -64
- package/components/landing/navbar.tsx +0 -44
- package/components/landing/quick-start-section.tsx +0 -59
- package/components/landing/site-footer.tsx +0 -55
- package/components/theme-provider.tsx +0 -60
- package/components/ui/accordion.tsx +0 -174
- package/components/ui/alert.tsx +0 -103
- package/components/ui/allinput.tsx +0 -210
- package/components/ui/avatar.tsx +0 -179
- package/components/ui/badge.tsx +0 -52
- package/components/ui/button.tsx +0 -75
- package/components/ui/card.tsx +0 -105
- package/components/ui/checkbox.tsx +0 -130
- package/components/ui/code-block.tsx +0 -60
- package/components/ui/data-table.tsx +0 -235
- package/components/ui/dialog.tsx +0 -173
- package/components/ui/dropdown.tsx +0 -78
- package/components/ui/feature-card.tsx +0 -38
- package/components/ui/input.tsx +0 -44
- package/components/ui/label.tsx +0 -110
- package/components/ui/loaders.tsx +0 -139
- package/components/ui/menu.tsx +0 -118
- package/components/ui/modal.tsx +0 -399
- package/components/ui/package-manager-tabs.tsx +0 -40
- package/components/ui/pagination.tsx +0 -200
- package/components/ui/progress.tsx +0 -134
- package/components/ui/radio.tsx +0 -262
- package/components/ui/skeleton.tsx +0 -102
- package/components/ui/steppers.tsx +0 -309
- package/components/ui/switch.tsx +0 -153
- package/components/ui/table.tsx +0 -58
- package/components/ui/tabs.tsx +0 -166
- package/components/ui/theme-toggle.tsx +0 -60
- package/components/ui/tooltip.tsx +0 -238
- package/data/color-palette.ts +0 -35
- package/data/docs-nav.ts +0 -84
- package/eslint.config.mjs +0 -18
- package/index.ts +0 -8
- package/lib/cn.ts +0 -4
- package/lib/color.ts +0 -85
- package/lib/og-image.tsx +0 -138
- package/lib/theme.ts +0 -33
- package/next.config.ts +0 -7
- package/postcss.config.mjs +0 -7
- package/public/dark.png +0 -0
- package/public/dark1.png +0 -0
- package/public/dark2.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/light.png +0 -0
- package/public/light1.png +0 -0
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/video/hero-demo.mp4 +0 -0
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -34
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useSyncExternalStore, type ReactNode } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
7
|
-
|
|
8
|
-
function subscribeReducedMotion(onChange: () => void) {
|
|
9
|
-
const media = window.matchMedia(REDUCED_MOTION_QUERY);
|
|
10
|
-
media.addEventListener("change", onChange);
|
|
11
|
-
return () => media.removeEventListener("change", onChange);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function useReducedMotion() {
|
|
15
|
-
return useSyncExternalStore(
|
|
16
|
-
subscribeReducedMotion,
|
|
17
|
-
() => window.matchMedia(REDUCED_MOTION_QUERY).matches,
|
|
18
|
-
() => false,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function spawnSnowParticle(container: HTMLElement, x: number, y: number) {
|
|
23
|
-
const particle = document.createElement("span");
|
|
24
|
-
particle.className = "hero-particle";
|
|
25
|
-
|
|
26
|
-
const offsetX = (Math.random() - 0.5) * 16;
|
|
27
|
-
const offsetY = (Math.random() - 0.5) * 16;
|
|
28
|
-
particle.style.left = `${x + offsetX}px`;
|
|
29
|
-
particle.style.top = `${y + offsetY}px`;
|
|
30
|
-
|
|
31
|
-
const size = 2 + Math.random() * 2.5;
|
|
32
|
-
particle.style.width = `${size}px`;
|
|
33
|
-
particle.style.height = `${size}px`;
|
|
34
|
-
|
|
35
|
-
const dx1 = (Math.random() - 0.5) * 40;
|
|
36
|
-
const dy1 = 12 + Math.random() * 18;
|
|
37
|
-
const dx2 = dx1 + (Math.random() - 0.5) * 50;
|
|
38
|
-
const dy2 = dy1 + 25 + Math.random() * 35;
|
|
39
|
-
const duration = 1800 + Math.random() * 1600;
|
|
40
|
-
const peakOpacity = 0.5 + Math.random() * 0.4;
|
|
41
|
-
|
|
42
|
-
particle.style.setProperty("--dx1", `${dx1}px`);
|
|
43
|
-
particle.style.setProperty("--dy1", `${dy1}px`);
|
|
44
|
-
particle.style.setProperty("--dx2", `${dx2}px`);
|
|
45
|
-
particle.style.setProperty("--dy2", `${dy2}px`);
|
|
46
|
-
particle.style.setProperty("--dur", `${duration}ms`);
|
|
47
|
-
particle.style.setProperty("--peak-opacity", `${peakOpacity}`);
|
|
48
|
-
|
|
49
|
-
particle.addEventListener("animationend", () => particle.remove());
|
|
50
|
-
container.appendChild(particle);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function HeroSpotlight({ children }: { children: ReactNode }) {
|
|
54
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
55
|
-
const gridRef = useRef<HTMLDivElement>(null);
|
|
56
|
-
const lastSpawnRef = useRef(0);
|
|
57
|
-
const reducedMotion = useReducedMotion();
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
const container = containerRef.current;
|
|
61
|
-
const grid = gridRef.current;
|
|
62
|
-
if (!container || !grid) return;
|
|
63
|
-
|
|
64
|
-
const handleMove = (event: PointerEvent) => {
|
|
65
|
-
const rect = container.getBoundingClientRect();
|
|
66
|
-
const x = event.clientX - rect.left;
|
|
67
|
-
const y = event.clientY - rect.top;
|
|
68
|
-
grid.style.setProperty("--mask-x", `${x - 280}px`);
|
|
69
|
-
grid.style.setProperty("--mask-y", `${y - 280}px`);
|
|
70
|
-
|
|
71
|
-
if (reducedMotion) return;
|
|
72
|
-
if (event.timeStamp - lastSpawnRef.current < 130) return;
|
|
73
|
-
lastSpawnRef.current = event.timeStamp;
|
|
74
|
-
|
|
75
|
-
spawnSnowParticle(container, x, y);
|
|
76
|
-
spawnSnowParticle(container, x, y);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const activate = () => grid.classList.add("is-active");
|
|
80
|
-
const deactivate = () => grid.classList.remove("is-active");
|
|
81
|
-
|
|
82
|
-
container.addEventListener("pointermove", handleMove);
|
|
83
|
-
container.addEventListener("pointerenter", activate);
|
|
84
|
-
container.addEventListener("pointerleave", deactivate);
|
|
85
|
-
return () => {
|
|
86
|
-
container.removeEventListener("pointermove", handleMove);
|
|
87
|
-
container.removeEventListener("pointerenter", activate);
|
|
88
|
-
container.removeEventListener("pointerleave", deactivate);
|
|
89
|
-
};
|
|
90
|
-
}, [reducedMotion]);
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<div ref={containerRef} className="relative overflow-hidden rounded-3xl bg-zinc-950">
|
|
94
|
-
<div
|
|
95
|
-
aria-hidden
|
|
96
|
-
className="pointer-events-none absolute inset-0 opacity-90"
|
|
97
|
-
style={{
|
|
98
|
-
backgroundImage:
|
|
99
|
-
"radial-gradient(60% 50% at 22% -10%, rgba(99,102,241,0.20), transparent 70%)," +
|
|
100
|
-
"radial-gradient(55% 45% at 100% 115%, rgba(217,119,87,0.16), transparent 70%)," +
|
|
101
|
-
"radial-gradient(40% 35% at 78% 8%, rgba(56,189,248,0.10), transparent 70%)",
|
|
102
|
-
}}
|
|
103
|
-
/>
|
|
104
|
-
<div
|
|
105
|
-
aria-hidden
|
|
106
|
-
className="pointer-events-none absolute inset-0 opacity-[0.05] mix-blend-overlay"
|
|
107
|
-
style={{
|
|
108
|
-
backgroundImage:
|
|
109
|
-
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")",
|
|
110
|
-
backgroundSize: "160px 160px",
|
|
111
|
-
}}
|
|
112
|
-
/>
|
|
113
|
-
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/20 to-transparent" />
|
|
114
|
-
<div ref={gridRef} className="hero-spotlight-grid pointer-events-none absolute inset-0" />
|
|
115
|
-
<div className="pointer-events-none absolute inset-x-0 bottom-0 hidden h-6 items-end justify-between px-10 pb-2 opacity-[0.15] sm:flex">
|
|
116
|
-
{Array.from({ length: 48 }).map((_, i) => (
|
|
117
|
-
<span
|
|
118
|
-
key={i}
|
|
119
|
-
className={cn("w-px bg-white", i % 4 === 0 ? "h-3.5" : "h-1.5")}
|
|
120
|
-
/>
|
|
121
|
-
))}
|
|
122
|
-
</div>
|
|
123
|
-
{children}
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { ArrowRight, CheckCircle2, FileCode2, SlidersHorizontal } from "lucide-react";
|
|
3
|
-
import { GithubIcon } from "@/components/icons/github";
|
|
4
|
-
import { buttonVariants } from "@/components/ui/button";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
|
|
7
|
-
const TRUST_ITEMS = [
|
|
8
|
-
{ icon: FileCode2, label: "TypeScript First" },
|
|
9
|
-
{ icon: CheckCircle2, label: "Accessible (WAI-ARIA)" },
|
|
10
|
-
{ icon: SlidersHorizontal, label: "Customizable" },
|
|
11
|
-
{ icon: GithubIcon, label: "Open Source" },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
export function Hero() {
|
|
15
|
-
return (
|
|
16
|
-
<div className="mt-10 min-w-0 max-w-xl sm:mt-16">
|
|
17
|
-
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs font-medium text-zinc-300">
|
|
18
|
-
<span className="h-1.5 w-1.5 rounded-full bg-zinc-300" />
|
|
19
|
-
v1.0.0 is now available
|
|
20
|
-
</div>
|
|
21
|
-
<h1 className="mt-5 text-[1.85rem] font-bold leading-[1.2] tracking-tight text-zinc-300 sm:text-5xl sm:leading-[1.15]">
|
|
22
|
-
Build beautiful interfaces, faster with{" "}
|
|
23
|
-
<span className="font-brand bg-gradient-to-r from-white via-zinc-200 to-zinc-400 bg-clip-text tracking-wide text-transparent">
|
|
24
|
-
ruler.ui
|
|
25
|
-
</span>
|
|
26
|
-
</h1>
|
|
27
|
-
<p className="mt-5 text-base leading-relaxed text-zinc-400">
|
|
28
|
-
A modern, accessible, and customizable component library for React &
|
|
29
|
-
TypeScript.
|
|
30
|
-
</p>
|
|
31
|
-
<div className="mt-8 flex flex-wrap items-center gap-3">
|
|
32
|
-
<Link
|
|
33
|
-
href="/docs"
|
|
34
|
-
className={cn(
|
|
35
|
-
buttonVariants({ variant: "white", size: "lg" }),
|
|
36
|
-
"group"
|
|
37
|
-
)}
|
|
38
|
-
>
|
|
39
|
-
Get Started
|
|
40
|
-
<ArrowRight className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5" />
|
|
41
|
-
</Link>
|
|
42
|
-
|
|
43
|
-
<Link
|
|
44
|
-
href="#"
|
|
45
|
-
className="group inline-flex h-11 items-center justify-center gap-2 rounded-xl border border-zinc-700 bg-transparent px-6 text-base font-medium text-white transition-all duration-200 hover:-translate-y-0.5 hover:border-zinc-700 hover:bg-transparent hover:text-white"
|
|
46
|
-
>
|
|
47
|
-
<GithubIcon className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5" />
|
|
48
|
-
View on GitHub
|
|
49
|
-
</Link>
|
|
50
|
-
</div>
|
|
51
|
-
<div className="mt-8 flex flex-wrap items-center gap-x-5 gap-y-2">
|
|
52
|
-
{TRUST_ITEMS.map((item) => (
|
|
53
|
-
<span
|
|
54
|
-
key={item.label}
|
|
55
|
-
className="inline-flex items-center gap-1.5 text-xs font-medium text-zinc-500"
|
|
56
|
-
>
|
|
57
|
-
<item.icon className="h-3.5 w-3.5" />
|
|
58
|
-
{item.label}
|
|
59
|
-
</span>
|
|
60
|
-
))}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { GithubIcon } from "@/components/icons/github";
|
|
3
|
-
import { ThemeToggle } from "@/components/ui/theme-toggle";
|
|
4
|
-
|
|
5
|
-
const NAV_LINKS = [
|
|
6
|
-
{ label: "Docs", href: "/docs" },
|
|
7
|
-
{ label: "Components", href: "/docs" },
|
|
8
|
-
{ label: "Themes", href: "/docs/theming" },
|
|
9
|
-
{ label: "Resources", href: "/docs" }
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
export function Navbar() {
|
|
13
|
-
return (
|
|
14
|
-
<nav className="flex min-w-0 items-center justify-between gap-3 border-b border-white/5 pb-6 sm:gap-6">
|
|
15
|
-
<Link href="/" className="min-w-0 shrink">
|
|
16
|
-
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
17
|
-
<img src="/dark2.png" alt="ruler.ui" className="h-8 w-auto max-w-[9rem] object-contain sm:h-9 sm:max-w-none" />
|
|
18
|
-
</Link>
|
|
19
|
-
|
|
20
|
-
<div className="hidden items-center gap-1 rounded-full border border-white/10 bg-white/[0.03] p-1 md:flex">
|
|
21
|
-
{NAV_LINKS.map((link) => (
|
|
22
|
-
<Link
|
|
23
|
-
key={link.label}
|
|
24
|
-
href={link.href}
|
|
25
|
-
className="rounded-full px-3.5 py-1.5 text-sm font-medium text-zinc-300 transition-colors hover:bg-white/10 hover:text-white"
|
|
26
|
-
>
|
|
27
|
-
{link.label}
|
|
28
|
-
</Link>
|
|
29
|
-
))}
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div className="flex items-center gap-3">
|
|
33
|
-
<ThemeToggle inverted />
|
|
34
|
-
<Link
|
|
35
|
-
href="#"
|
|
36
|
-
className="hidden items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium text-zinc-300 transition-colors hover:bg-white/10 hover:text-white sm:flex"
|
|
37
|
-
>
|
|
38
|
-
<GithubIcon className="h-4 w-4" />
|
|
39
|
-
GitHub
|
|
40
|
-
</Link>
|
|
41
|
-
</div>
|
|
42
|
-
</nav>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { ArrowRight, Zap } from "lucide-react";
|
|
3
|
-
import { CodeBlock } from "@/components/ui/code-block";
|
|
4
|
-
import { buttonVariants } from "@/components/ui/button";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
|
|
7
|
-
const USAGE_CODE = `import { Button } from 'ruler.ui'
|
|
8
|
-
|
|
9
|
-
export default function App() {
|
|
10
|
-
return (
|
|
11
|
-
<Button variant="primary">
|
|
12
|
-
Get Started
|
|
13
|
-
</Button>
|
|
14
|
-
)
|
|
15
|
-
}`;
|
|
16
|
-
|
|
17
|
-
export function QuickStartSection() {
|
|
18
|
-
return (
|
|
19
|
-
<div className="py-20 max-w-[1200px] mx-auto border-t border-zinc-100 dark:border-zinc-900">
|
|
20
|
-
<div className="grid gap-12 lg:grid-cols-[minmax(0,320px)_1fr] lg:items-center">
|
|
21
|
-
{/* Left Side */}
|
|
22
|
-
<div className="space-y-6">
|
|
23
|
-
<div className="space-y-4">
|
|
24
|
-
<div className="flex items-center gap-3">
|
|
25
|
-
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-zinc-900 text-white dark:bg-zinc-50 dark:text-zinc-900">
|
|
26
|
-
<Zap className="h-5 w-5 fill-current" />
|
|
27
|
-
</span>
|
|
28
|
-
<h2 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">
|
|
29
|
-
Quick Start
|
|
30
|
-
</h2>
|
|
31
|
-
</div>
|
|
32
|
-
<p className="text-[15px] leading-relaxed text-zinc-500 dark:text-zinc-400">
|
|
33
|
-
Start building with ruler.ui in seconds. Copy this example to see it in action.
|
|
34
|
-
</p>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<Link
|
|
38
|
-
href="/docs"
|
|
39
|
-
className={cn(buttonVariants({ variant: "primary", size: "lg" }), "group")}
|
|
40
|
-
>
|
|
41
|
-
Explore Components
|
|
42
|
-
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
|
|
43
|
-
</Link>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
{/* Right Side: IDE Window with Mac Dots */}
|
|
47
|
-
<div className="relative group">
|
|
48
|
-
<div className="relative overflow-hidden rounded-2xl border border-zinc-200/50 bg-zinc-950 shadow-2xl dark:border-zinc-800/60">
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<div className="p-0">
|
|
52
|
-
<CodeBlock code={USAGE_CODE} filename="App.tsx" />
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { GithubIcon } from "@/components/icons/github";
|
|
3
|
-
import { XIcon } from "@/components/icons/x";
|
|
4
|
-
import { DiscordIcon } from "@/components/icons/discord";
|
|
5
|
-
|
|
6
|
-
const FOOTER_LINKS = [
|
|
7
|
-
{ label: "Docs", href: "/docs" },
|
|
8
|
-
{ label: "Components", href: "/docs" },
|
|
9
|
-
{ label: "Themes", href: "/docs/theming" },
|
|
10
|
-
{ label: "Resources", href: "/docs" },
|
|
11
|
-
{ label: "Playground", href: "#" },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
const SOCIAL_LINKS = [
|
|
15
|
-
{ label: "GitHub", href: "#", icon: GithubIcon },
|
|
16
|
-
{ label: "X", href: "#", icon: XIcon },
|
|
17
|
-
{ label: "Discord", href: "#", icon: DiscordIcon },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
export function SiteFooter() {
|
|
21
|
-
return (
|
|
22
|
-
<footer className="flex flex-col items-center justify-between gap-6 border-t border-zinc-200 py-8 dark:border-zinc-800 sm:flex-row">
|
|
23
|
-
<div>
|
|
24
|
-
<p className="mt-1 text-xs text-zinc-400 dark:text-zinc-600">
|
|
25
|
-
© 2026 ruler.ui. All rights reserved.
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<nav className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">
|
|
30
|
-
{FOOTER_LINKS.map((link) => (
|
|
31
|
-
<Link
|
|
32
|
-
key={link.label}
|
|
33
|
-
href={link.href}
|
|
34
|
-
className="text-sm text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
|
35
|
-
>
|
|
36
|
-
{link.label}
|
|
37
|
-
</Link>
|
|
38
|
-
))}
|
|
39
|
-
</nav>
|
|
40
|
-
|
|
41
|
-
<div className="flex items-center gap-4">
|
|
42
|
-
{SOCIAL_LINKS.map((social) => (
|
|
43
|
-
<Link
|
|
44
|
-
key={social.label}
|
|
45
|
-
href={social.href}
|
|
46
|
-
aria-label={social.label}
|
|
47
|
-
className="text-zinc-400 transition-colors hover:text-zinc-900 dark:text-zinc-500 dark:hover:text-zinc-100"
|
|
48
|
-
>
|
|
49
|
-
<social.icon className="h-4 w-4" />
|
|
50
|
-
</Link>
|
|
51
|
-
))}
|
|
52
|
-
</div>
|
|
53
|
-
</footer>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createContext,
|
|
5
|
-
useCallback,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
useMemo,
|
|
9
|
-
useState,
|
|
10
|
-
type ReactNode,
|
|
11
|
-
} from "react";
|
|
12
|
-
import {
|
|
13
|
-
applyTheme,
|
|
14
|
-
parseTheme,
|
|
15
|
-
persistTheme,
|
|
16
|
-
readStoredTheme,
|
|
17
|
-
type Theme,
|
|
18
|
-
} from "@/lib/theme";
|
|
19
|
-
|
|
20
|
-
type ThemeContextValue = {
|
|
21
|
-
theme: Theme;
|
|
22
|
-
setTheme: (theme: Theme) => void;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
26
|
-
|
|
27
|
-
export function ThemeProvider({
|
|
28
|
-
children,
|
|
29
|
-
defaultTheme = "light",
|
|
30
|
-
}: {
|
|
31
|
-
children: ReactNode;
|
|
32
|
-
defaultTheme?: Theme;
|
|
33
|
-
}) {
|
|
34
|
-
const [theme, setThemeState] = useState<Theme>(defaultTheme);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const stored = readStoredTheme();
|
|
38
|
-
const resolved = stored ?? defaultTheme;
|
|
39
|
-
setThemeState(resolved);
|
|
40
|
-
applyTheme(resolved);
|
|
41
|
-
}, [defaultTheme]);
|
|
42
|
-
|
|
43
|
-
const setTheme = useCallback((nextTheme: Theme) => {
|
|
44
|
-
setThemeState(nextTheme);
|
|
45
|
-
persistTheme(nextTheme);
|
|
46
|
-
applyTheme(nextTheme);
|
|
47
|
-
}, []);
|
|
48
|
-
|
|
49
|
-
const value = useMemo(() => ({ theme, setTheme }), [theme, setTheme]);
|
|
50
|
-
|
|
51
|
-
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function useTheme() {
|
|
55
|
-
const context = useContext(ThemeContext);
|
|
56
|
-
if (!context) {
|
|
57
|
-
throw new Error("useTheme must be used within ThemeProvider");
|
|
58
|
-
}
|
|
59
|
-
return context;
|
|
60
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useState, useRef, useLayoutEffect, useId } from "react";
|
|
4
|
-
import { Plus, ChevronDown, ChevronRight } from "lucide-react";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
|
|
7
|
-
type AccordionContextType = {
|
|
8
|
-
openValue: string | null;
|
|
9
|
-
toggle: (value: string) => void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const AccordionContext = createContext<AccordionContextType | null>(null);
|
|
13
|
-
|
|
14
|
-
function useAccordionContext() {
|
|
15
|
-
const ctx = useContext(AccordionContext);
|
|
16
|
-
if (!ctx) throw new Error("Accordion components must be used inside Accordion");
|
|
17
|
-
return ctx;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const EASE = { transitionTimingFunction: "cubic-bezier(0.16,1,0.3,1)" };
|
|
21
|
-
|
|
22
|
-
export function Accordion({ children, defaultValue = null, className = "" }: { children: React.ReactNode, defaultValue?: string | null, className?: string }) {
|
|
23
|
-
const [openValue, setOpenValue] = useState<string | null>(defaultValue);
|
|
24
|
-
const toggle = (value: string) => setOpenValue((current) => (current === value ? null : value));
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<AccordionContext.Provider value={{ openValue, toggle }}>
|
|
28
|
-
<div className={className}>{children}</div>
|
|
29
|
-
</AccordionContext.Provider>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function AccordionItem({ value, title, children, variant = "plus" }: { value: string, title: string, children: React.ReactNode, variant?: "plus" | "chevron" }) {
|
|
34
|
-
const { openValue, toggle } = useAccordionContext();
|
|
35
|
-
const isOpen = openValue === value;
|
|
36
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
37
|
-
const [height, setHeight] = useState(0);
|
|
38
|
-
const panelId = useId();
|
|
39
|
-
|
|
40
|
-
useLayoutEffect(() => {
|
|
41
|
-
if (!contentRef.current) return;
|
|
42
|
-
setHeight(isOpen ? contentRef.current.scrollHeight : 0);
|
|
43
|
-
}, [isOpen]);
|
|
44
|
-
|
|
45
|
-
if (variant === "chevron") {
|
|
46
|
-
return (
|
|
47
|
-
<div className="border-b border-zinc-200 dark:border-zinc-800 last:border-0">
|
|
48
|
-
<button
|
|
49
|
-
type="button"
|
|
50
|
-
onClick={() => toggle(value)}
|
|
51
|
-
aria-expanded={isOpen}
|
|
52
|
-
aria-controls={panelId}
|
|
53
|
-
className="group flex w-full items-center justify-between rounded-lg py-5 text-left outline-none transition-colors duration-200 hover:bg-zinc-50 focus-visible:ring-2 focus-visible:ring-zinc-900/15 dark:hover:bg-zinc-900/40 dark:focus-visible:ring-white/20"
|
|
54
|
-
>
|
|
55
|
-
<span
|
|
56
|
-
className={cn(
|
|
57
|
-
"text-[15px] font-medium transition-colors duration-200",
|
|
58
|
-
isOpen
|
|
59
|
-
? "text-zinc-900 dark:text-white"
|
|
60
|
-
: "text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-200",
|
|
61
|
-
)}
|
|
62
|
-
>
|
|
63
|
-
{title}
|
|
64
|
-
</span>
|
|
65
|
-
<ChevronDown
|
|
66
|
-
size={18}
|
|
67
|
-
style={EASE}
|
|
68
|
-
className={cn(
|
|
69
|
-
"shrink-0 text-zinc-400 transition-transform duration-300 group-hover:text-zinc-600 dark:group-hover:text-zinc-300",
|
|
70
|
-
isOpen && "rotate-180 text-zinc-900 dark:text-white",
|
|
71
|
-
)}
|
|
72
|
-
/>
|
|
73
|
-
</button>
|
|
74
|
-
<div style={{ ...EASE, height }} className="overflow-hidden transition-[height] duration-300">
|
|
75
|
-
<div id={panelId} ref={contentRef} className="pb-5 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">
|
|
76
|
-
{children}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div className="border-b border-zinc-200 dark:border-zinc-800 last:border-0">
|
|
85
|
-
<button
|
|
86
|
-
type="button"
|
|
87
|
-
onClick={() => toggle(value)}
|
|
88
|
-
aria-expanded={isOpen}
|
|
89
|
-
aria-controls={panelId}
|
|
90
|
-
className="group flex w-full items-center gap-3 rounded-lg py-4 text-left outline-none transition-colors duration-200 hover:bg-zinc-50 focus-visible:ring-2 focus-visible:ring-zinc-900/15 dark:hover:bg-zinc-900/40 dark:focus-visible:ring-white/20"
|
|
91
|
-
>
|
|
92
|
-
<span
|
|
93
|
-
style={EASE}
|
|
94
|
-
className={cn(
|
|
95
|
-
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border transition-all duration-300",
|
|
96
|
-
isOpen
|
|
97
|
-
? "border-zinc-900 bg-zinc-900 text-white shadow-sm dark:border-zinc-100 dark:bg-zinc-100 dark:text-zinc-900"
|
|
98
|
-
: "border-zinc-300 text-zinc-400 group-hover:border-zinc-400 group-hover:text-zinc-500 dark:border-zinc-700 dark:group-hover:border-zinc-600",
|
|
99
|
-
)}
|
|
100
|
-
>
|
|
101
|
-
<Plus size={12} style={EASE} className={cn("transition-transform duration-300", isOpen && "rotate-45")} />
|
|
102
|
-
</span>
|
|
103
|
-
<span
|
|
104
|
-
className={cn(
|
|
105
|
-
"text-[15px] font-medium transition-colors duration-200",
|
|
106
|
-
isOpen
|
|
107
|
-
? "text-zinc-900 dark:text-zinc-100"
|
|
108
|
-
: "text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-200",
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
{title}
|
|
112
|
-
</span>
|
|
113
|
-
</button>
|
|
114
|
-
<div style={{ ...EASE, height }} className="overflow-hidden transition-[height] duration-300">
|
|
115
|
-
<div id={panelId} ref={contentRef} className="pb-4 pl-9 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">
|
|
116
|
-
{children}
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function AccordionNested({ title, children, level = 0 }: { title: string, children?: React.ReactNode, level?: number }) {
|
|
124
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
125
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
126
|
-
const [height, setHeight] = useState(0);
|
|
127
|
-
const panelId = useId();
|
|
128
|
-
|
|
129
|
-
useLayoutEffect(() => {
|
|
130
|
-
if (!contentRef.current) return;
|
|
131
|
-
setHeight(isOpen ? contentRef.current.scrollHeight : 0);
|
|
132
|
-
}, [isOpen, children]);
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<div className="border-b border-zinc-100 dark:border-zinc-800 last:border-0">
|
|
136
|
-
<button
|
|
137
|
-
type="button"
|
|
138
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
139
|
-
aria-expanded={isOpen}
|
|
140
|
-
aria-controls={panelId}
|
|
141
|
-
className="group flex w-full items-center gap-2.5 rounded-lg py-3.5 text-left outline-none transition-colors duration-200 hover:bg-zinc-50 focus-visible:ring-2 focus-visible:ring-zinc-900/15 dark:hover:bg-zinc-900/40 dark:focus-visible:ring-white/20"
|
|
142
|
-
style={{ paddingLeft: `${level * 1.5}rem` }}
|
|
143
|
-
>
|
|
144
|
-
<ChevronRight
|
|
145
|
-
size={15}
|
|
146
|
-
style={EASE}
|
|
147
|
-
className={cn(
|
|
148
|
-
"shrink-0 text-zinc-400 transition-transform duration-300 group-hover:text-zinc-600 dark:group-hover:text-zinc-300",
|
|
149
|
-
isOpen && "rotate-90 text-zinc-600 dark:text-zinc-300",
|
|
150
|
-
)}
|
|
151
|
-
/>
|
|
152
|
-
<span className="text-[15px] font-medium text-zinc-900 dark:text-zinc-100">{title}</span>
|
|
153
|
-
</button>
|
|
154
|
-
<div style={{ ...EASE, height }} className="overflow-hidden transition-[height] duration-300">
|
|
155
|
-
<div id={panelId} ref={contentRef} className="flex flex-col">
|
|
156
|
-
{children}
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function AccordionLink({ title, level = 0 }: { title: string, level?: number }) {
|
|
164
|
-
return (
|
|
165
|
-
<div className="border-b border-zinc-100 dark:border-zinc-800 last:border-0">
|
|
166
|
-
<div
|
|
167
|
-
className="cursor-pointer rounded-lg py-3.5 text-[15px] font-medium text-zinc-600 transition-colors duration-200 hover:bg-zinc-50 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-900/40 dark:hover:text-zinc-100"
|
|
168
|
-
style={{ paddingLeft: `${level * 1.5 + 1.5625}rem` }}
|
|
169
|
-
>
|
|
170
|
-
{title}
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
}
|
package/components/ui/alert.tsx
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { CheckCircle2, TriangleAlert, Info, XCircle, X } from "lucide-react";
|
|
3
|
-
|
|
4
|
-
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
variant?: "info" | "success" | "warning" | "error";
|
|
6
|
-
title?: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
dismissible?: boolean;
|
|
9
|
-
onDismiss?: () => void;
|
|
10
|
-
action?: React.ReactNode;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
14
|
-
({ className = "", variant = "info", title, description, dismissible, onDismiss, action, children, ...props }, ref) => {
|
|
15
|
-
const configs = {
|
|
16
|
-
info: {
|
|
17
|
-
bar: "bg-[#0070BA]",
|
|
18
|
-
icon: (
|
|
19
|
-
<Info className="h-5 w-5 text-[#0070BA] shrink-0" />
|
|
20
|
-
),
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
success: {
|
|
24
|
-
bar: "bg-[#28A745]",
|
|
25
|
-
icon: (
|
|
26
|
-
<CheckCircle2 className="h-5 w-5 text-[#28A745] shrink-0" />
|
|
27
|
-
),
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
warning: {
|
|
31
|
-
bar: "bg-[#E58E00]",
|
|
32
|
-
icon: (
|
|
33
|
-
<TriangleAlert className="h-5 w-5 text-[#E58E00] shrink-0" />
|
|
34
|
-
),
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
error: {
|
|
38
|
-
bar: "bg-[#D93025]",
|
|
39
|
-
icon: (
|
|
40
|
-
<XCircle className="h-5 w-5 text-[#D93025] shrink-0" />
|
|
41
|
-
),
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const current = configs[variant];
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div
|
|
49
|
-
ref={ref}
|
|
50
|
-
className={`relative flex w-full min-h-[64px] items-center gap-2.5 rounded-lg border border-gray-200 bg-white px-3 py-2.5 shadow-[0_3px_12px_rgba(0,0,0,0.03)] dark:border-zinc-800 dark:bg-zinc-900 dark:shadow-none ${className}`}
|
|
51
|
-
{...props}
|
|
52
|
-
>
|
|
53
|
-
<div
|
|
54
|
-
className={`absolute left-0 top-0 w-16 h-1 ${current.bar} rounded-tl-lg`}
|
|
55
|
-
style={{
|
|
56
|
-
clipPath: "polygon(0 0, 100% 0, 92% 100%, 0 100%)",
|
|
57
|
-
}}
|
|
58
|
-
/>
|
|
59
|
-
|
|
60
|
-
{/* Icon */}
|
|
61
|
-
<div className="shrink-0 flex items-center justify-center">
|
|
62
|
-
{current.icon}
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<div className="flex-1 min-w-0">
|
|
66
|
-
{title && (
|
|
67
|
-
<h5 className="text-[13px] font-semibold tracking-tight text-zinc-900 dark:text-white leading-tight">
|
|
68
|
-
{title}
|
|
69
|
-
</h5>
|
|
70
|
-
)}
|
|
71
|
-
|
|
72
|
-
{description && (
|
|
73
|
-
<p className="mt-0.5 text-[11px] font-medium text-zinc-500 dark:text-zinc-300 leading-tight">
|
|
74
|
-
{description}
|
|
75
|
-
</p>
|
|
76
|
-
)}
|
|
77
|
-
|
|
78
|
-
{children}
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
{action && (
|
|
82
|
-
<div className={`shrink-0 ml-auto ${dismissible || onDismiss ? "mr-5" : ""}`}>
|
|
83
|
-
{action}
|
|
84
|
-
</div>
|
|
85
|
-
)}
|
|
86
|
-
|
|
87
|
-
{/* Close button */}
|
|
88
|
-
{(dismissible || onDismiss) && (
|
|
89
|
-
<button
|
|
90
|
-
onClick={onDismiss}
|
|
91
|
-
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-200 transition-colors"
|
|
92
|
-
>
|
|
93
|
-
<X className="h-3.5 w-3.5 stroke-[3]" />
|
|
94
|
-
</button>
|
|
95
|
-
)}
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
Alert.displayName = "Alert";
|
|
102
|
-
|
|
103
|
-
export { Alert };
|