feat: add core UI components and baseline tests
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { Checkbox, Label } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Checkbox",
|
||||
component: Checkbox,
|
||||
args: {
|
||||
defaultChecked: true
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
defaultChecked: {
|
||||
control: "boolean"
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean"
|
||||
},
|
||||
invalid: {
|
||||
control: "boolean"
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Checkbox>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox defaultChecked id="checkbox-default" />
|
||||
<Label htmlFor="checkbox-default">Default checked</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id="checkbox-unchecked" />
|
||||
<Label htmlFor="checkbox-unchecked">Unchecked</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox disabled id="checkbox-disabled" />
|
||||
<Label htmlFor="checkbox-disabled">Disabled</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id="checkbox-invalid" invalid />
|
||||
<Label htmlFor="checkbox-invalid">Invalid state</Label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Dialog",
|
||||
component: Dialog,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Dialog>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Open approval dialog</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Launch this release?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This will notify the routing team and publish the release note to the activity feed.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost">Cancel</Button>
|
||||
<Button>Confirm launch</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger
|
||||
} from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/DropdownMenu",
|
||||
component: DropdownMenu,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof DropdownMenu>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary">Open menu</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Launch actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
Review summary
|
||||
<DropdownMenuShortcut>R</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Share preview
|
||||
<DropdownMenuShortcut>S</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuCheckboxItem checked>Notify stakeholders</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup value="staged">
|
||||
<DropdownMenuRadioItem value="staged">Staged rollout</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="global">Global rollout</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger inset>More actions</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Duplicate release</DropdownMenuItem>
|
||||
<DropdownMenuItem variant="destructive">Archive release</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Field, FieldControl, FieldDescription, FieldError, Input, Label, Textarea } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
function FieldExamples() {
|
||||
return (
|
||||
<div className="grid w-[720px] gap-6">
|
||||
<Field required>
|
||||
<Label requiredIndicator>Project name</Label>
|
||||
<FieldControl>
|
||||
<Input placeholder="Cadence launch" />
|
||||
<FieldDescription>
|
||||
This appears in your internal release log and changelog.
|
||||
</FieldDescription>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
|
||||
<Field invalid>
|
||||
<Label requiredIndicator>Slug</Label>
|
||||
<FieldControl>
|
||||
<Input defaultValue="launch party!" />
|
||||
<FieldDescription>Use lowercase letters, numbers, and hyphens only.</FieldDescription>
|
||||
<FieldError>Slug cannot contain spaces or punctuation.</FieldError>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
|
||||
<Field orientation="horizontal" readOnly>
|
||||
<Label>Internal note</Label>
|
||||
<FieldControl>
|
||||
<Textarea defaultValue="Read-only example for system generated notes." />
|
||||
<FieldDescription>Horizontal layout is useful for denser settings forms.</FieldDescription>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Components/Field",
|
||||
component: FieldExamples,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof FieldExamples>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const States: Story = {};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Field, FieldControl, FieldDescription, FieldError, Input, Label } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Input",
|
||||
component: Input,
|
||||
args: {
|
||||
placeholder: "name@company.com",
|
||||
size: "md"
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean"
|
||||
},
|
||||
invalid: {
|
||||
control: "boolean"
|
||||
},
|
||||
placeholder: {
|
||||
control: "text"
|
||||
},
|
||||
readOnly: {
|
||||
control: "boolean"
|
||||
},
|
||||
required: {
|
||||
control: "boolean"
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["sm", "md", "lg"]
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Input>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: (args) => <Input {...args} className="w-[320px]" />
|
||||
};
|
||||
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-[720px] gap-4 sm:grid-cols-2">
|
||||
<Input defaultValue="studio@cadence.dev" />
|
||||
<Input disabled defaultValue="Disabled value" />
|
||||
<Input invalid defaultValue="launch!" />
|
||||
<Input readOnly defaultValue="Read only value" />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithField: Story = {
|
||||
render: () => (
|
||||
<Field invalid className="w-[420px]">
|
||||
<Label requiredIndicator>Email address</Label>
|
||||
<FieldControl>
|
||||
<Input defaultValue="studio" required />
|
||||
<FieldDescription>Use your company email so teammates can identify you.</FieldDescription>
|
||||
<FieldError>Please enter a valid email address.</FieldError>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Label } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Label",
|
||||
component: Label,
|
||||
args: {
|
||||
children: "Email address"
|
||||
},
|
||||
argTypes: {
|
||||
children: {
|
||||
control: "text"
|
||||
},
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
requiredIndicator: {
|
||||
control: "boolean"
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Label>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
requiredIndicator: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Invalid: Story = {
|
||||
args: {
|
||||
"aria-invalid": true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Button, Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Popover",
|
||||
component: Popover,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Popover>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="secondary">Inspect summary</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="grid gap-3">
|
||||
<p className="text-sm font-medium">Release health</p>
|
||||
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
|
||||
12 checks passed, 2 reviewers pending, and rollout is limited to 10% of traffic.
|
||||
</p>
|
||||
<PopoverArrow />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Label, RadioGroup, RadioGroupItem } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
function RadioGroupExamples() {
|
||||
return (
|
||||
<div className="grid w-[720px] gap-6">
|
||||
<RadioGroup defaultValue="startup">
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem id="mode-startup" value="startup" />
|
||||
<Label htmlFor="mode-startup">Startup review</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem id="mode-scale" value="scale" />
|
||||
<Label htmlFor="mode-scale">Scale review</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem id="mode-enterprise" value="enterprise" />
|
||||
<Label htmlFor="mode-enterprise">Enterprise review</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup defaultValue="email" orientation="horizontal">
|
||||
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-4">
|
||||
<RadioGroupItem id="channel-email" value="email" />
|
||||
<Label htmlFor="channel-email">Email</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-4">
|
||||
<RadioGroupItem id="channel-slack" value="slack" />
|
||||
<Label htmlFor="channel-slack">Slack</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-4">
|
||||
<RadioGroupItem id="channel-push" value="push" />
|
||||
<Label htmlFor="channel-push">Push</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Components/RadioGroup",
|
||||
component: RadioGroupExamples,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof RadioGroupExamples>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const States: Story = {};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Field, FieldControl, FieldDescription, FieldError, Label, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Select",
|
||||
component: Select,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Select>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<div className="w-[320px]">
|
||||
<Select defaultValue="editorial">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose a review lane" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Review lane</SelectLabel>
|
||||
<SelectItem value="editorial">Editorial review</SelectItem>
|
||||
<SelectItem value="design">Design review</SelectItem>
|
||||
<SelectItem value="legal">Legal review</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithField: Story = {
|
||||
render: () => (
|
||||
<Field invalid className="w-[420px]">
|
||||
<Label requiredIndicator>Routing team</Label>
|
||||
<FieldControl>
|
||||
<Select>
|
||||
<SelectTrigger invalid>
|
||||
<SelectValue placeholder="Choose a team" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Primary owners</SelectLabel>
|
||||
<SelectItem value="product">Product</SelectItem>
|
||||
<SelectItem value="design">Design</SelectItem>
|
||||
<SelectItem value="engineering">Engineering</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectSeparator />
|
||||
<SelectGroup>
|
||||
<SelectLabel>Support teams</SelectLabel>
|
||||
<SelectItem value="ops">Operations</SelectItem>
|
||||
<SelectItem value="marketing">Marketing</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldDescription>The routing team receives launch and rollout notifications.</FieldDescription>
|
||||
<FieldError>Select a primary owning team before publishing.</FieldError>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Separator } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Separator",
|
||||
component: Separator,
|
||||
args: {
|
||||
orientation: "horizontal",
|
||||
tone: "subtle"
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
decorative: {
|
||||
control: "boolean"
|
||||
},
|
||||
orientation: {
|
||||
control: "radio",
|
||||
options: ["horizontal", "vertical"]
|
||||
},
|
||||
tone: {
|
||||
control: "radio",
|
||||
options: ["subtle", "strong"]
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Separator>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: (args) => (
|
||||
<div className="w-80">
|
||||
<Separator {...args} />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const InCard: Story = {
|
||||
render: () => (
|
||||
<div className="w-[420px] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-5 shadow-[var(--shadow-sm)]">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="m-0 text-base font-semibold">Workspace</h3>
|
||||
<p className="m-0 text-sm text-[var(--color-muted-foreground)]">
|
||||
Project status and recent activity
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid gap-2 text-sm text-[var(--color-muted-foreground)]">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Tokens</span>
|
||||
<span className="text-[var(--color-foreground)]">Stable</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Components</span>
|
||||
<span className="text-[var(--color-foreground)]">In progress</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const Vertical: Story = {
|
||||
render: () => (
|
||||
<div className="flex h-16 items-center gap-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] px-5 shadow-[var(--shadow-xs)]">
|
||||
<span className="text-sm text-[var(--color-muted-foreground)]">Overview</span>
|
||||
<Separator className="h-8" orientation="vertical" />
|
||||
<span className="text-sm text-[var(--color-muted-foreground)]">Metrics</span>
|
||||
<Separator className="h-8" orientation="vertical" tone="strong" />
|
||||
<span className="text-sm text-[var(--color-muted-foreground)]">Activity</span>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Skeleton } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Skeleton",
|
||||
component: Skeleton,
|
||||
args: {
|
||||
shape: "line",
|
||||
tone: "default"
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
shape: {
|
||||
control: "select",
|
||||
options: ["line", "block", "pill", "avatar"]
|
||||
},
|
||||
tone: {
|
||||
control: "radio",
|
||||
options: ["default", "muted"]
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Skeleton>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const ContentPlaceholder: Story = {
|
||||
render: () => (
|
||||
<div className="w-[420px] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-5 shadow-[var(--shadow-sm)]">
|
||||
<div className="flex items-start gap-4">
|
||||
<Skeleton shape="avatar" />
|
||||
<div className="min-w-0 flex-1 space-y-3">
|
||||
<Skeleton className="w-1/3" />
|
||||
<Skeleton className="w-full" />
|
||||
<Skeleton className="w-5/6" tone="muted" />
|
||||
<Skeleton shape="pill" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Spinner } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Spinner",
|
||||
component: Spinner,
|
||||
args: {
|
||||
size: "md",
|
||||
tone: "primary"
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
size: {
|
||||
control: "radio",
|
||||
options: ["sm", "md", "lg"]
|
||||
},
|
||||
tone: {
|
||||
control: "radio",
|
||||
options: ["default", "current", "primary"]
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Spinner>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const InButtons: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="inline-flex items-center gap-2 rounded-[var(--radius-full)] bg-[var(--color-primary)] px-4 py-2 text-sm font-medium text-[var(--color-primary-foreground)]">
|
||||
<Spinner size="sm" />
|
||||
Saving
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2 rounded-[var(--radius-full)] border border-[var(--color-border-strong)] px-4 py-2 text-sm font-medium">
|
||||
<Spinner tone="default" />
|
||||
Syncing
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Label, Switch } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Switch",
|
||||
component: Switch,
|
||||
args: {
|
||||
defaultChecked: true
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
defaultChecked: {
|
||||
control: "boolean"
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean"
|
||||
},
|
||||
invalid: {
|
||||
control: "boolean"
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Switch>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked id="switch-live" />
|
||||
<Label htmlFor="switch-live">Live publishing</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch id="switch-draft" />
|
||||
<Label htmlFor="switch-draft">Draft only</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch disabled id="switch-disabled" />
|
||||
<Label htmlFor="switch-disabled">Disabled</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch id="switch-invalid" invalid />
|
||||
<Label htmlFor="switch-invalid">Invalid state</Label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Tabs",
|
||||
component: Tabs,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Tabs>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<Tabs className="w-[720px]" defaultValue="overview">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
||||
<TabsTrigger value="audience">Audience</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="overview">
|
||||
High-level release summary, current risk score, and owners.
|
||||
</TabsContent>
|
||||
<TabsContent value="timeline">
|
||||
Rollout checkpoints, notification windows, and approval milestones.
|
||||
</TabsContent>
|
||||
<TabsContent value="audience">
|
||||
Impacted customer groups, internal reviewers, and communication channels.
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Field, FieldControl, FieldDescription, FieldError, Label, Textarea } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Textarea",
|
||||
component: Textarea,
|
||||
args: {
|
||||
placeholder: "Add release notes or supporting context",
|
||||
size: "md"
|
||||
},
|
||||
argTypes: {
|
||||
className: {
|
||||
control: false
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean"
|
||||
},
|
||||
invalid: {
|
||||
control: "boolean"
|
||||
},
|
||||
placeholder: {
|
||||
control: "text"
|
||||
},
|
||||
readOnly: {
|
||||
control: "boolean"
|
||||
},
|
||||
required: {
|
||||
control: "boolean"
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["sm", "md", "lg"]
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Textarea>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: (args) => <Textarea {...args} className="w-[420px]" />
|
||||
};
|
||||
|
||||
export const States: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-[760px] gap-4 sm:grid-cols-2">
|
||||
<Textarea defaultValue="Default note with enough content to show the surface." />
|
||||
<Textarea disabled defaultValue="Disabled note content." />
|
||||
<Textarea invalid defaultValue="This content has a validation error." />
|
||||
<Textarea readOnly defaultValue="Read-only generated notes stay muted." />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithField: Story = {
|
||||
render: () => (
|
||||
<Field invalid className="w-[480px]">
|
||||
<Label requiredIndicator>Launch summary</Label>
|
||||
<FieldControl>
|
||||
<Textarea required />
|
||||
<FieldDescription>Keep it short enough for the changelog card.</FieldDescription>
|
||||
<FieldError>Summary needs at least 24 characters.</FieldError>
|
||||
</FieldControl>
|
||||
</Field>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Button,
|
||||
Toast,
|
||||
ToastAction,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport
|
||||
} from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
|
||||
function ToastDemo() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<ToastProvider swipeDirection="right">
|
||||
<Button onClick={() => setOpen(true)}>Show toast</Button>
|
||||
<Toast onOpenChange={setOpen} open={open} variant="success">
|
||||
<ToastTitle>Release queued</ToastTitle>
|
||||
<ToastDescription>
|
||||
The rollout has been scheduled and reviewers were notified.
|
||||
</ToastDescription>
|
||||
<ToastAction altText="Open rollout">Open rollout</ToastAction>
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Components/Toast",
|
||||
component: ToastDemo,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof ToastDemo>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Button, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipTrigger } from "@ai-ui/ui";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Tooltip",
|
||||
component: Tooltip,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"]
|
||||
} satisfies Meta<typeof Tooltip>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost">Hover for note</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Inline notes stay terse and avoid blocking the main flow.
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
};
|
||||
Reference in New Issue
Block a user