223 lines
9.3 KiB
TypeScript
223 lines
9.3 KiB
TypeScript
import {
|
|
authoringChecklist,
|
|
commonSlotNames,
|
|
commonStateNames,
|
|
cvaConventions,
|
|
getMotionRecipeClassNames,
|
|
motionRecipes
|
|
} from "@ai-ui/ui";
|
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
|
|
const componentRecipeExample = `const buttonVariants = cva(
|
|
[
|
|
"inline-flex items-center justify-center gap-2",
|
|
"rounded-[var(--radius-sm)] font-medium",
|
|
"motion-pressable motion-ring"
|
|
],
|
|
{
|
|
variants: {
|
|
variant: {
|
|
primary: "bg-[var(--color-primary)] text-[var(--color-primary-foreground)]",
|
|
secondary: "bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)]"
|
|
},
|
|
size: {
|
|
sm: "h-9 px-3 text-sm",
|
|
md: "h-10 px-4 text-sm",
|
|
lg: "h-12 px-5 text-base"
|
|
}
|
|
},
|
|
defaultVariants: {
|
|
variant: "primary",
|
|
size: "md"
|
|
}
|
|
}
|
|
);`;
|
|
|
|
const stateRecipeExample = `const rootProps = withRootProps(
|
|
{ className: cn(buttonVariants({ variant, size }), className) },
|
|
{
|
|
slot: "root",
|
|
states: {
|
|
disabled,
|
|
loading,
|
|
state: open ? "open" : "closed"
|
|
}
|
|
}
|
|
);`;
|
|
|
|
function ContractsOverview() {
|
|
return (
|
|
<div className="min-h-screen bg-[var(--color-background)] px-6 py-10 text-[var(--color-foreground)] sm:px-10">
|
|
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
|
|
<header className="max-w-3xl space-y-3">
|
|
<p className="text-sm uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
|
|
AI UI / Phase 2
|
|
</p>
|
|
<h1
|
|
className="font-semibold tracking-[var(--tracking-tight)]"
|
|
style={{
|
|
fontFamily: "var(--font-display)",
|
|
fontSize: "var(--text-4xl)",
|
|
lineHeight: "var(--leading-tight)"
|
|
}}
|
|
>
|
|
Component authoring now follows one repeatable contract.
|
|
</h1>
|
|
<p className="text-[var(--text-lg)] leading-[var(--leading-loose)] text-[var(--color-muted-foreground)]">
|
|
Phase 2 now ships real components and codifies the shared state, slot,
|
|
variant, and motion conventions they follow.
|
|
</p>
|
|
</header>
|
|
|
|
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">Authoring Checklist</h2>
|
|
<div className="mt-5 grid gap-3">
|
|
{authoringChecklist.map((item) => (
|
|
<div
|
|
key={item}
|
|
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
|
|
>
|
|
<p className="text-sm leading-6 text-[var(--color-foreground)]">{item}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</article>
|
|
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">CVA Conventions</h2>
|
|
<div className="mt-5 grid gap-3">
|
|
{cvaConventions.map((item) => (
|
|
<div
|
|
key={item}
|
|
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
|
|
>
|
|
<p className="text-sm leading-6 text-[var(--color-foreground)]">{item}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</article>
|
|
</section>
|
|
|
|
<section className="grid gap-4 lg:grid-cols-2">
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">State Naming</h2>
|
|
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
|
|
Public styling state should flow through stable `data-*` attributes.
|
|
</p>
|
|
<div className="mt-5 grid gap-3">
|
|
{commonStateNames.map((item) => (
|
|
<div
|
|
key={item.state}
|
|
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<code className="text-sm font-medium">{`data-${item.state}`}</code>
|
|
<span className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
|
|
convention
|
|
</span>
|
|
</div>
|
|
<p className="mt-2 text-sm leading-6 text-[var(--color-muted-foreground)]">
|
|
{item.guidance}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</article>
|
|
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">Slot Naming</h2>
|
|
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
|
|
Slots create stable styling hooks for component internals and docs.
|
|
</p>
|
|
<div className="mt-5 grid gap-3">
|
|
{commonSlotNames.map((item) => (
|
|
<div
|
|
key={item.slot}
|
|
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<code className="text-sm font-medium">{`data-slot="${item.slot}"`}</code>
|
|
<span className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
|
|
slot
|
|
</span>
|
|
</div>
|
|
<p className="mt-2 text-sm leading-6 text-[var(--color-muted-foreground)]">
|
|
{item.guidance}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</article>
|
|
</section>
|
|
|
|
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">Variant Base Example</h2>
|
|
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
|
|
Variants should start from a strong base string, then branch only where
|
|
appearance semantics actually change.
|
|
</p>
|
|
<pre className="mt-5 overflow-x-auto rounded-[var(--radius-md)] bg-[var(--color-surface-contrast)] p-4 text-sm leading-6 text-[var(--color-background)]">
|
|
<code>{componentRecipeExample}</code>
|
|
</pre>
|
|
</article>
|
|
|
|
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<h2 className="text-2xl font-semibold">State Helper Example</h2>
|
|
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
|
|
Shared helpers standardize slot names and `data-*` state attributes.
|
|
</p>
|
|
<pre className="mt-5 overflow-x-auto rounded-[var(--radius-md)] bg-[var(--color-surface-contrast)] p-4 text-sm leading-6 text-[var(--color-background)]">
|
|
<code>{stateRecipeExample}</code>
|
|
</pre>
|
|
</article>
|
|
</section>
|
|
|
|
<section className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
|
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-2xl font-semibold">Motion Recipe Helpers</h2>
|
|
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
|
|
These class names map directly to the recipe layer defined in
|
|
`motion.css`.
|
|
</p>
|
|
</div>
|
|
<code className="rounded-[var(--radius-full)] bg-[var(--color-surface)] px-3 py-2 text-sm">
|
|
{getMotionRecipeClassNames("transition", "ring", "pressable")}
|
|
</code>
|
|
</div>
|
|
|
|
<div className="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
{Object.entries(motionRecipes).map(([name, className]) => (
|
|
<div
|
|
key={name}
|
|
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<p className="text-sm font-medium">{name}</p>
|
|
<code className="text-xs text-[var(--color-muted-foreground)]">{className}</code>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const meta = {
|
|
title: "Foundation/Contracts",
|
|
component: ContractsOverview,
|
|
parameters: {
|
|
layout: "fullscreen"
|
|
}
|
|
} satisfies Meta<typeof ContractsOverview>;
|
|
|
|
export default meta;
|
|
|
|
type Story = StoryObj<typeof meta>;
|
|
|
|
export const Overview: Story = {};
|