feat: initial @jason/ui-kit — tokens, preset, components, motion, blocks, Impeccable
ci / build-and-design (push) Failing after 15s
ci / build-and-design (push) Failing after 15s
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "../lib/cn";
|
||||
|
||||
export interface NavItem {
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
href?: string;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/** Dashboard chrome: fixed sidebar + sticky topbar + scrolling content. */
|
||||
export function AppShell({
|
||||
brand,
|
||||
nav,
|
||||
topbar,
|
||||
children,
|
||||
footer,
|
||||
}: {
|
||||
brand: React.ReactNode;
|
||||
nav: NavItem[];
|
||||
topbar?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex min-h-screen bg-bg text-text">
|
||||
<aside className="hidden w-60 shrink-0 flex-col border-r border-border bg-surface md:flex">
|
||||
<div className="flex h-14 items-center px-5 font-display text-lg font-bold">{brand}</div>
|
||||
<nav className="flex-1 space-y-0.5 px-3 py-3">
|
||||
{nav.map((item) => {
|
||||
const Comp = item.href ? "a" : "button";
|
||||
return (
|
||||
<Comp
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
onClick={item.onClick}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-3 rounded px-3 py-2 text-sm font-medium transition-colors duration-fast [&_svg]:size-4",
|
||||
item.active ? "bg-brand/15 text-brand" : "text-muted hover:bg-surface-2/60 hover:text-text"
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</Comp>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
{footer && <div className="border-t border-border p-3 text-sm text-muted">{footer}</div>}
|
||||
</aside>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<header className="sticky top-0 z-30 flex h-14 items-center justify-between gap-4 border-b border-border bg-bg/80 px-5 backdrop-blur">
|
||||
{topbar}
|
||||
</header>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
import * as React from "react";
|
||||
import { Command } from "cmdk";
|
||||
import { Search } from "lucide-react";
|
||||
import { cn } from "../lib/cn";
|
||||
|
||||
export interface CommandAction {
|
||||
id: string;
|
||||
label: string;
|
||||
group?: string;
|
||||
icon?: React.ReactNode;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
/** ⌘K palette. Opens on Cmd/Ctrl+K by default; big perceived-quality boost on any tool. */
|
||||
export function CommandPalette({
|
||||
actions,
|
||||
placeholder = "Type a command or search…",
|
||||
}: {
|
||||
actions: CommandAction[];
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
setOpen((o) => !o);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, []);
|
||||
|
||||
const groups = React.useMemo(() => {
|
||||
const map = new Map<string, CommandAction[]>();
|
||||
for (const a of actions) {
|
||||
const g = a.group ?? "Actions";
|
||||
map.set(g, [...(map.get(g) ?? []), a]);
|
||||
}
|
||||
return [...map.entries()];
|
||||
}, [actions]);
|
||||
|
||||
return (
|
||||
<Command.Dialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
label="Command palette"
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/4 z-50 w-full max-w-xl -translate-x-1/2 overflow-hidden",
|
||||
"rounded-lg border border-border bg-surface shadow-lg data-[state=open]:animate-scale-in"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 border-b border-border px-4">
|
||||
<Search className="size-4 text-muted" />
|
||||
<Command.Input placeholder={placeholder} className="h-12 flex-1 bg-transparent text-sm text-text outline-none placeholder:text-muted" />
|
||||
</div>
|
||||
<Command.List className="max-h-80 overflow-auto p-2">
|
||||
<Command.Empty className="py-6 text-center text-sm text-muted">No results.</Command.Empty>
|
||||
{groups.map(([group, items]) => (
|
||||
<Command.Group key={group} heading={group} className="px-1 py-1 text-xs font-semibold uppercase tracking-wide text-muted [&_[cmdk-group-items]]:mt-1">
|
||||
{items.map((a) => (
|
||||
<Command.Item
|
||||
key={a.id}
|
||||
onSelect={() => { a.onSelect(); setOpen(false); }}
|
||||
className="flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-sm text-text aria-selected:bg-surface-2 [&_svg]:size-4"
|
||||
>
|
||||
{a.icon}
|
||||
{a.label}
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
))}
|
||||
</Command.List>
|
||||
</Command.Dialog>
|
||||
);
|
||||
}
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
import { Stagger, StaggerItem } from "../motion";
|
||||
import { SpotlightCard } from "../animated/spotlight-card";
|
||||
import { cn } from "../lib/cn";
|
||||
|
||||
export interface Feature {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function FeatureGrid({
|
||||
heading,
|
||||
features,
|
||||
columns = 3,
|
||||
className,
|
||||
}: {
|
||||
heading?: React.ReactNode;
|
||||
features: Feature[];
|
||||
columns?: 2 | 3 | 4;
|
||||
className?: string;
|
||||
}) {
|
||||
const cols = { 2: "sm:grid-cols-2", 3: "sm:grid-cols-2 lg:grid-cols-3", 4: "sm:grid-cols-2 lg:grid-cols-4" }[columns];
|
||||
return (
|
||||
<section className={cn("mx-auto max-w-6xl px-6 py-20", className)}>
|
||||
{heading && <h2 className="mb-10 text-center font-display text-3xl font-bold tracking-tight">{heading}</h2>}
|
||||
<Stagger className={cn("grid grid-cols-1 gap-4", cols)}>
|
||||
{features.map((f) => (
|
||||
<StaggerItem key={f.title}>
|
||||
<SpotlightCard className="h-full">
|
||||
{f.icon && (
|
||||
<div className="mb-4 inline-flex size-10 items-center justify-center rounded bg-brand/15 text-brand [&_svg]:size-5">
|
||||
{f.icon}
|
||||
</div>
|
||||
)}
|
||||
<h3 className="font-display text-lg font-semibold">{f.title}</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted">{f.description}</p>
|
||||
</SpotlightCard>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</Stagger>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
import * as React from "react";
|
||||
import { FadeIn } from "../motion";
|
||||
import { Button } from "../components/button";
|
||||
import { cn } from "../lib/cn";
|
||||
|
||||
export function Hero({
|
||||
eyebrow,
|
||||
title,
|
||||
subtitle,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
className,
|
||||
}: {
|
||||
eyebrow?: string;
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
primaryCta?: { label: string; href?: string; onClick?: () => void };
|
||||
secondaryCta?: { label: string; href?: string; onClick?: () => void };
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section className={cn("relative overflow-hidden bg-bg px-6 py-24 text-center", className)}>
|
||||
{/* Subtle brand aura — depth, not a purple gradient. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 top-0 h-[420px] opacity-60"
|
||||
style={{ background: "radial-gradient(600px 300px at 50% 0%, hsl(var(--brand) / 0.14), transparent 70%)" }}
|
||||
/>
|
||||
<div className="relative mx-auto max-w-3xl">
|
||||
{eyebrow && (
|
||||
<FadeIn>
|
||||
<span className="mb-4 inline-block rounded-sm bg-brand/15 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-brand">
|
||||
{eyebrow}
|
||||
</span>
|
||||
</FadeIn>
|
||||
)}
|
||||
<FadeIn delay={0.05}>
|
||||
<h1 className="font-display text-4xl font-bold leading-[1.1] tracking-tight sm:text-6xl">{title}</h1>
|
||||
</FadeIn>
|
||||
{subtitle && (
|
||||
<FadeIn delay={0.1}>
|
||||
<p className="mx-auto mt-6 max-w-xl text-lg text-muted">{subtitle}</p>
|
||||
</FadeIn>
|
||||
)}
|
||||
{(primaryCta || secondaryCta) && (
|
||||
<FadeIn delay={0.15}>
|
||||
<div className="mt-10 flex items-center justify-center gap-3">
|
||||
{primaryCta && (
|
||||
<Button size="lg" asChild={!!primaryCta.href} onClick={primaryCta.onClick}>
|
||||
{primaryCta.href ? <a href={primaryCta.href}>{primaryCta.label}</a> : primaryCta.label}
|
||||
</Button>
|
||||
)}
|
||||
{secondaryCta && (
|
||||
<Button size="lg" variant="outline" asChild={!!secondaryCta.href} onClick={secondaryCta.onClick}>
|
||||
{secondaryCta.href ? <a href={secondaryCta.href}>{secondaryCta.label}</a> : secondaryCta.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</FadeIn>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
import * as React from "react";
|
||||
import { Check } from "lucide-react";
|
||||
import { Stagger, StaggerItem } from "../motion";
|
||||
import { Button } from "../components/button";
|
||||
import { Badge } from "../components/badge";
|
||||
import { cn } from "../lib/cn";
|
||||
|
||||
export interface PricingTier {
|
||||
name: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
description?: string;
|
||||
features: string[];
|
||||
cta: { label: string; href?: string; onClick?: () => void };
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export function Pricing({
|
||||
heading,
|
||||
tiers,
|
||||
className,
|
||||
}: {
|
||||
heading?: React.ReactNode;
|
||||
tiers: PricingTier[];
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section className={cn("mx-auto max-w-5xl px-6 py-20", className)}>
|
||||
{heading && <h2 className="mb-10 text-center font-display text-3xl font-bold tracking-tight">{heading}</h2>}
|
||||
<Stagger className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
{tiers.map((t) => (
|
||||
<StaggerItem key={t.name}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full flex-col rounded-lg border bg-surface p-6 transition-shadow duration-slow",
|
||||
t.featured ? "border-brand/50 shadow-glow" : "border-border shadow-sm hover:shadow-md"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-display text-lg font-semibold">{t.name}</h3>
|
||||
{t.featured && <Badge variant="brand">Popular</Badge>}
|
||||
</div>
|
||||
{t.description && <p className="mt-1 text-sm text-muted">{t.description}</p>}
|
||||
<div className="mt-5 flex items-baseline gap-1">
|
||||
<span className="font-display text-4xl font-bold">{t.price}</span>
|
||||
{t.period && <span className="text-sm text-muted">/{t.period}</span>}
|
||||
</div>
|
||||
<ul className="mt-6 flex-1 space-y-2.5">
|
||||
{t.features.map((f) => (
|
||||
<li key={f} className="flex items-start gap-2 text-sm text-text">
|
||||
<Check className="mt-0.5 size-4 shrink-0 text-brand" />
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
className="mt-6 w-full"
|
||||
variant={t.featured ? "primary" : "outline"}
|
||||
asChild={!!t.cta.href}
|
||||
onClick={t.cta.onClick}
|
||||
>
|
||||
{t.cta.href ? <a href={t.cta.href}>{t.cta.label}</a> : t.cta.label}
|
||||
</Button>
|
||||
</div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</Stagger>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user