chore: storybook (#15285)

Co-authored-by: David Hill <iamdavidhill@gmail.com>
This commit is contained in:
Adam
2026-02-26 16:05:04 -06:00
committed by GitHub
parent 8c484a05b8
commit 05d77b7d47
85 changed files with 5407 additions and 9 deletions

View File

@@ -0,0 +1,149 @@
// @ts-nocheck
import { createEffect, createSignal } from "solid-js"
import * as mod from "./accordion"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Accordion for collapsible content sections with optional multi-open behavior.
Use one trigger per item; keep content concise.
### API
- Root supports Kobalte Accordion props: \`value\`, \`multiple\`, \`collapsible\`, \`onChange\`.
- Compose with \`Accordion.Item\`, \`Header\`, \`Trigger\`, \`Content\`.
### Variants and states
- Single or multiple open items.
- Collapsible or fixed-open behavior.
### Behavior
- Controlled via \`value\`/\`onChange\` when provided.
### Accessibility
- TODO: confirm keyboard navigation from Kobalte Accordion.
### Theming/tokens
- Uses \`data-component="accordion"\` and slot data attributes.
`
const story = create({ title: "UI/Accordion", mod })
export default {
title: "UI/Accordion",
id: "components-accordion",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
args: {
collapsible: true,
multiple: false,
value: "first",
},
argTypes: {
collapsible: { control: "boolean" },
multiple: { control: "boolean" },
value: {
control: "select",
options: ["first", "second", "none"],
mapping: {
none: undefined,
},
},
},
render: (props) => {
const [value, setValue] = createSignal(props.value)
createEffect(() => {
setValue(props.value)
})
const current = () => {
if (props.multiple) {
if (Array.isArray(value())) return value()
if (value()) return [value()]
return []
}
if (Array.isArray(value())) return value()[0]
return value()
}
return (
<div style={{ display: "grid", gap: "8px", width: "420px" }}>
<mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={current()} onChange={setValue}>
<mod.Accordion.Item value="first">
<mod.Accordion.Header>
<mod.Accordion.Trigger>First</mod.Accordion.Trigger>
</mod.Accordion.Header>
<mod.Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
</mod.Accordion.Content>
</mod.Accordion.Item>
<mod.Accordion.Item value="second">
<mod.Accordion.Header>
<mod.Accordion.Trigger>Second</mod.Accordion.Trigger>
</mod.Accordion.Header>
<mod.Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>More content.</div>
</mod.Accordion.Content>
</mod.Accordion.Item>
</mod.Accordion>
</div>
)
},
}
export const Multiple = {
args: {
collapsible: true,
multiple: true,
value: ["first", "second"],
},
render: (props) => (
<mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={props.value}>
<mod.Accordion.Item value="first">
<mod.Accordion.Header>
<mod.Accordion.Trigger>First</mod.Accordion.Trigger>
</mod.Accordion.Header>
<mod.Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
</mod.Accordion.Content>
</mod.Accordion.Item>
<mod.Accordion.Item value="second">
<mod.Accordion.Header>
<mod.Accordion.Trigger>Second</mod.Accordion.Trigger>
</mod.Accordion.Header>
<mod.Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>More content.</div>
</mod.Accordion.Content>
</mod.Accordion.Item>
</mod.Accordion>
),
}
export const NonCollapsible = {
args: {
collapsible: false,
multiple: false,
value: "first",
},
render: (props) => (
<mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={props.value}>
<mod.Accordion.Item value="first">
<mod.Accordion.Header>
<mod.Accordion.Trigger>First</mod.Accordion.Trigger>
</mod.Accordion.Header>
<mod.Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
</mod.Accordion.Content>
</mod.Accordion.Item>
</mod.Accordion>
),
}

View File

@@ -0,0 +1,69 @@
// @ts-nocheck
import { iconNames } from "./app-icons/types"
import * as mod from "./app-icon"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Application icon renderer for known editor/terminal apps.
Use in provider or app selection lists.
### API
- Required: \`id\` (app icon name).
- Accepts standard img props except \`src\`.
### Variants and states
- Auto-switches themed icons when available.
### Behavior
- Watches color scheme changes to swap themed assets.
### Accessibility
- Provide \`alt\` text when the icon conveys meaning.
### Theming/tokens
- Uses \`data-component="app-icon"\`.
`
const story = create({ title: "UI/AppIcon", mod, args: { id: "vscode" } })
export default {
title: "UI/AppIcon",
id: "components-app-icon",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
id: {
control: "select",
options: iconNames,
},
},
}
export const Basic = story.Basic
export const AllIcons = {
render: () => (
<div
style={{
display: "grid",
gap: "12px",
"grid-template-columns": "repeat(auto-fill, minmax(72px, 1fr))",
}}
>
{iconNames.map((id) => (
<div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
<mod.AppIcon id={id} alt={id} />
<div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{id}</div>
</div>
))}
</div>
),
}

View File

@@ -0,0 +1,76 @@
// @ts-nocheck
import * as mod from "./avatar"
import { create } from "../storybook/scaffold"
const docs = `### Overview
User avatar with image fallback to initials.
Use in user lists and headers.
### API
- Required: \`fallback\` string.
- Optional: \`src\`, \`background\`, \`foreground\`, \`size\`.
### Variants and states
- Sizes: small, normal, large.
- Image vs fallback state.
### Behavior
- Uses grapheme-aware fallback rendering.
### Accessibility
- TODO: provide alt text when using images; currently image is decorative.
### Theming/tokens
- Uses \`data-component="avatar"\` with size and image state attributes.
`
const story = create({ title: "UI/Avatar", mod, args: { fallback: "A" } })
export default {
title: "UI/Avatar",
id: "components-avatar",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
size: {
control: "select",
options: ["small", "normal", "large"],
},
},
}
export const Basic = story.Basic
export const WithImage = {
args: {
src: "https://placehold.co/80x80/png",
fallback: "J",
},
}
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<mod.Avatar size="small" fallback="S" />
<mod.Avatar size="normal" fallback="N" />
<mod.Avatar size="large" fallback="L" />
</div>
),
}
export const CustomColors = {
args: {
fallback: "C",
background: "#1f2a44",
foreground: "#f2f5ff",
},
}

View File

@@ -0,0 +1,133 @@
// @ts-nocheck
import { createSignal } from "solid-js"
import * as mod from "./basic-tool"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Expandable tool panel with a structured trigger and optional details.
Use structured triggers for consistent layout; custom triggers allowed.
### API
- Required: \`icon\` and \`trigger\` (structured or custom JSX).
- Optional: \`status\`, \`defaultOpen\`, \`forceOpen\`, \`defer\`, \`locked\`.
### Variants and states
- Pending/running status animates the title via TextShimmer.
### Behavior
- Uses Collapsible; can defer content rendering until open.
- Locked state prevents closing.
### Accessibility
- TODO: confirm trigger semantics and aria labeling.
### Theming/tokens
- Uses \`data-component="tool-trigger"\` and related slots.
`
const story = create({
title: "UI/Basic Tool",
mod,
args: {
icon: "mcp",
defaultOpen: true,
trigger: {
title: "Basic Tool",
subtitle: "Example subtitle",
args: ["--flag", "value"],
},
children: "Details content",
},
})
export default {
title: "UI/Basic Tool",
id: "components-basic-tool",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Pending = {
args: {
status: "pending",
trigger: {
title: "Running tool",
subtitle: "Working...",
},
children: "Progress details",
},
}
export const Locked = {
args: {
locked: true,
trigger: {
title: "Locked tool",
subtitle: "Cannot close",
},
children: "Locked details",
},
}
export const Deferred = {
args: {
defer: true,
defaultOpen: false,
trigger: {
title: "Deferred tool",
subtitle: "Content mounts on open",
},
children: "Deferred content",
},
}
export const ForceOpen = {
args: {
forceOpen: true,
trigger: {
title: "Forced open",
subtitle: "Cannot close",
},
children: "Forced content",
},
}
export const HideDetails = {
args: {
hideDetails: true,
trigger: {
title: "Summary only",
subtitle: "Details hidden",
},
children: "Hidden content",
},
}
export const SubtitleAction = {
render: () => {
const [message, setMessage] = createSignal("Subtitle not clicked")
return (
<div style={{ display: "grid", gap: "8px" }}>
<div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{message()}</div>
<mod.BasicTool
icon="mcp"
trigger={{ title: "Clickable subtitle", subtitle: "Click me" }}
onSubtitleClick={() => setMessage("Subtitle clicked")}
>
Subtitle action details
</mod.BasicTool>
</div>
)
},
}

View File

@@ -0,0 +1,108 @@
// @ts-nocheck
import { Button } from "./button"
const docs = `### Overview
Primary action button with size, variant, and optional icon support.
Use \`IconButton\` for icon-only actions.
### API
- \`variant\`: "primary" | "secondary" | "ghost".
- \`size\`: "small" | "normal" | "large".
- \`icon\`: Icon name for a leading icon.
- Inherits Kobalte Button props and native button attributes.
### Variants and states
- Variants: primary, secondary, ghost.
- States: disabled.
### Behavior
- Renders an Icon when \`icon\` is set.
### Accessibility
- Provide clear label text; use \`aria-label\` for icon-only buttons.
### Theming/tokens
- Uses \`data-component="button"\` with size/variant data attributes.
`
export default {
title: "UI/Button",
id: "components-button",
component: Button,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
args: {
children: "Button",
variant: "secondary",
size: "normal",
},
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary", "ghost"],
},
size: {
control: "select",
options: ["small", "normal", "large"],
},
icon: {
control: "select",
options: ["none", "check", "plus", "arrow-right"],
mapping: {
none: undefined,
},
},
},
}
export const Primary = {
args: {
variant: "primary",
},
}
export const Secondary = {}
export const Ghost = {
args: {
variant: "ghost",
},
}
export const WithIcon = {
args: {
children: "Continue",
icon: "arrow-right",
},
}
export const Disabled = {
args: {
variant: "primary",
disabled: true,
},
}
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<Button size="small" variant="secondary">
Small
</Button>
<Button size="normal" variant="secondary">
Normal
</Button>
<Button size="large" variant="secondary">
Large
</Button>
</div>
),
}

View File

@@ -0,0 +1,90 @@
// @ts-nocheck
import { Card } from "./card"
import { Button } from "./button"
const docs = `### Overview
Surface container for grouping related content and actions.
Pair with \`Button\` or \`Tag\` for quick actions.
### API
- Optional: \`variant\` (normal, error, warning, success, info).
- Accepts standard div props.
### Variants and states
- Semantic variants for status-driven messaging.
### Behavior
- Pure presentational container.
### Accessibility
- Provide headings or aria labels when used in isolation.
### Theming/tokens
- Uses \`data-component="card"\` with variant data attributes.
`
export default {
title: "UI/Card",
id: "components-card",
component: Card,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
args: {
variant: "normal",
},
argTypes: {
variant: {
control: "select",
options: ["normal", "error", "warning", "success", "info"],
},
},
render: (props: { variant?: "normal" | "error" | "warning" | "success" | "info" }) => {
return (
<Card variant={props.variant}>
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 500 }}>Card title</div>
<div style={{ color: "var(--text-weak)", fontSize: "13px" }}>Small supporting text.</div>
</div>
<Button size="small" variant="ghost">
Action
</Button>
</div>
</Card>
)
},
}
export const Normal = {}
export const Error = {
args: {
variant: "error",
},
}
export const Warning = {
args: {
variant: "warning",
},
}
export const Success = {
args: {
variant: "success",
},
}
export const Info = {
args: {
variant: "info",
},
}

View File

@@ -0,0 +1,71 @@
// @ts-nocheck
import { Icon } from "./icon"
import * as mod from "./checkbox"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Checkbox control for multi-select or agreement inputs.
Use in forms and multi-select lists.
### API
- Uses Kobalte Checkbox props (\`checked\`, \`defaultChecked\`, \`onChange\`).
- Optional: \`hideLabel\`, \`description\`, \`icon\`.
- Children render as the label.
### Variants and states
- Checked/unchecked, indeterminate, disabled (via Kobalte).
### Behavior
- Controlled or uncontrolled usage.
### Accessibility
- TODO: confirm aria attributes from Kobalte.
### Theming/tokens
- Uses \`data-component="checkbox"\` and related slots.
`
const story = create({ title: "UI/Checkbox", mod, args: { children: "Checkbox", defaultChecked: true } })
export default {
title: "UI/Checkbox",
id: "components-checkbox",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const States = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Checkbox defaultChecked>Checked</mod.Checkbox>
<mod.Checkbox>Unchecked</mod.Checkbox>
<mod.Checkbox disabled>Disabled</mod.Checkbox>
<mod.Checkbox description="Helper text">With description</mod.Checkbox>
</div>
),
}
export const CustomIcon = {
render: () => (
<mod.Checkbox icon={<Icon name="check" size="small" />} defaultChecked>
Custom icon
</mod.Checkbox>
),
}
export const HiddenLabel = {
args: {
children: "Hidden label",
hideLabel: true,
},
}

View File

@@ -0,0 +1,70 @@
// @ts-nocheck
import * as mod from "./code"
import { create } from "../storybook/scaffold"
import { code } from "../storybook/fixtures"
const docs = `### Overview
Syntax-highlighted code viewer with selection support and large-file virtualization.
Use alongside \`LineComment\` and \`Diff\` in review workflows.
### API
- Required: \`file\` with file name + contents.
- Optional: \`language\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`.
- Optional callbacks: \`onRendered\`, \`onLineSelectionEnd\`.
### Variants and states
- Supports large-file virtualization automatically.
### Behavior
- Re-renders when \`file\` or rendering options change.
- Optional line selection integrates with selection callbacks.
### Accessibility
- TODO: confirm keyboard find and selection behavior.
### Theming/tokens
- Uses \`data-component="code"\` and Pierre CSS variables from \`styleVariables\`.
`
const story = create({
title: "UI/Code",
mod,
args: {
file: code,
language: "ts",
},
})
export default {
title: "UI/Code",
id: "components-code",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const SelectedLines = {
args: {
enableLineSelection: true,
selectedLines: { start: 2, end: 4 },
},
}
export const CommentedLines = {
args: {
commentedLines: [
{ start: 1, end: 1 },
{ start: 5, end: 6 },
],
},
}

View File

@@ -0,0 +1,86 @@
// @ts-nocheck
import * as mod from "./collapsible"
const docs = `### Overview
Toggleable content region with optional arrow indicator.
Compose \`Collapsible.Trigger\`, \`Collapsible.Content\`, and \`Collapsible.Arrow\`.
### API
- Root accepts Kobalte Collapsible props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
- \`variant\` controls styling ("normal" | "ghost").
### Variants and states
- Normal and ghost variants.
- Open/closed states.
### Behavior
- Trigger toggles the content visibility.
### Accessibility
- TODO: confirm ARIA attributes provided by Kobalte.
### Theming/tokens
- Uses \`data-component="collapsible"\` and slots for trigger/content/arrow.
`
export default {
title: "UI/Collapsible",
id: "components-collapsible",
component: mod.Collapsible,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
variant: {
control: "select",
options: ["normal", "ghost"],
},
},
}
export const Basic = {
args: {
variant: "normal",
defaultOpen: true,
},
render: (props) => (
<mod.Collapsible {...props}>
<mod.Collapsible.Trigger data-slot="collapsible-trigger">
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
<span>Details</span>
<mod.Collapsible.Arrow />
</div>
</mod.Collapsible.Trigger>
<mod.Collapsible.Content data-slot="collapsible-content">
<div style={{ color: "var(--text-weak)", "padding-top": "8px" }}>Optional details sit here.</div>
</mod.Collapsible.Content>
</mod.Collapsible>
),
}
export const Ghost = {
args: {
variant: "ghost",
defaultOpen: false,
},
render: (props) => (
<mod.Collapsible {...props}>
<mod.Collapsible.Trigger data-slot="collapsible-trigger">
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
<span>Ghost trigger</span>
<mod.Collapsible.Arrow />
</div>
</mod.Collapsible.Trigger>
<mod.Collapsible.Content data-slot="collapsible-content">
<div style={{ color: "var(--text-weak)", "padding-top": "8px" }}>Ghost content.</div>
</mod.Collapsible.Content>
</mod.Collapsible>
),
}

View File

@@ -0,0 +1,113 @@
// @ts-nocheck
import * as mod from "./context-menu"
const docs = `### Overview
Context menu for right-click interactions with composable items and submenus.
Use \`ItemLabel\` and \`ItemDescription\` for rich items.
### API
- Root accepts Kobalte ContextMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
- Compose \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections.
### Variants and states
- Supports grouped sections and nested submenus.
### Behavior
- Opens on context menu gesture over the trigger element.
### Accessibility
- TODO: confirm keyboard and focus behavior from Kobalte.
### Theming/tokens
- Uses \`data-component="context-menu"\` and slot attributes for styling.
`
export default {
title: "UI/ContextMenu",
id: "components-context-menu",
component: mod.ContextMenu,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<mod.ContextMenu defaultOpen>
<mod.ContextMenu.Trigger>
<div
style={{
padding: "20px",
border: "1px dashed var(--border-weak)",
"border-radius": "8px",
color: "var(--text-weak)",
}}
>
Right click (or open) here
</div>
</mod.ContextMenu.Trigger>
<mod.ContextMenu.Portal>
<mod.ContextMenu.Content>
<mod.ContextMenu.Group>
<mod.ContextMenu.GroupLabel>Actions</mod.ContextMenu.GroupLabel>
<mod.ContextMenu.Item>
<mod.ContextMenu.ItemLabel>Copy</mod.ContextMenu.ItemLabel>
</mod.ContextMenu.Item>
<mod.ContextMenu.Item>
<mod.ContextMenu.ItemLabel>Paste</mod.ContextMenu.ItemLabel>
</mod.ContextMenu.Item>
</mod.ContextMenu.Group>
<mod.ContextMenu.Separator />
<mod.ContextMenu.Sub>
<mod.ContextMenu.SubTrigger>More</mod.ContextMenu.SubTrigger>
<mod.ContextMenu.SubContent>
<mod.ContextMenu.Item>
<mod.ContextMenu.ItemLabel>Duplicate</mod.ContextMenu.ItemLabel>
</mod.ContextMenu.Item>
<mod.ContextMenu.Item>
<mod.ContextMenu.ItemLabel>Move</mod.ContextMenu.ItemLabel>
</mod.ContextMenu.Item>
</mod.ContextMenu.SubContent>
</mod.ContextMenu.Sub>
</mod.ContextMenu.Content>
</mod.ContextMenu.Portal>
</mod.ContextMenu>
),
}
export const CheckboxRadio = {
render: () => (
<mod.ContextMenu defaultOpen>
<mod.ContextMenu.Trigger>
<div
style={{
padding: "20px",
border: "1px dashed var(--border-weak)",
"border-radius": "8px",
color: "var(--text-weak)",
}}
>
Right click (or open) here
</div>
</mod.ContextMenu.Trigger>
<mod.ContextMenu.Portal>
<mod.ContextMenu.Content>
<mod.ContextMenu.CheckboxItem checked>Show line numbers</mod.ContextMenu.CheckboxItem>
<mod.ContextMenu.CheckboxItem>Wrap lines</mod.ContextMenu.CheckboxItem>
<mod.ContextMenu.Separator />
<mod.ContextMenu.RadioGroup value="compact">
<mod.ContextMenu.RadioItem value="compact">Compact</mod.ContextMenu.RadioItem>
<mod.ContextMenu.RadioItem value="comfortable">Comfortable</mod.ContextMenu.RadioItem>
</mod.ContextMenu.RadioGroup>
</mod.ContextMenu.Content>
</mod.ContextMenu.Portal>
</mod.ContextMenu>
),
}

View File

@@ -0,0 +1,173 @@
// @ts-nocheck
import { onMount } from "solid-js"
import * as mod from "./dialog"
import { Button } from "./button"
import { useDialog } from "../context/dialog"
const docs = `### Overview
Dialog content wrapper used with the DialogProvider for modal flows.
Provide concise title/description and keep body focused.
### API
- Optional: \`title\`, \`description\`, \`action\`.
- \`size\`: normal | large | x-large.
- \`fit\` and \`transition\` control layout and animation.
### Variants and states
- Sizes and optional header/action controls.
### Behavior
- Intended to be rendered via \`useDialog().show\`.
### Accessibility
- TODO: confirm focus trapping and aria attributes from Kobalte Dialog.
### Theming/tokens
- Uses \`data-component="dialog"\` and slot attributes.
`
export default {
title: "UI/Dialog",
id: "components-dialog",
component: mod.Dialog,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => {
const dialog = useDialog()
const open = () =>
dialog.show(() => (
<mod.Dialog title="Dialog" description="Description">
Dialog body content.
</mod.Dialog>
))
onMount(open)
return (
<Button variant="secondary" onClick={open}>
Open dialog
</Button>
)
},
}
export const Sizes = {
render: () => {
const dialog = useDialog()
return (
<div style={{ display: "flex", gap: "12px" }}>
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog title="Normal" description="Normal size">
Normal dialog content.
</mod.Dialog>
))
}
>
Normal
</Button>
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog size="large" title="Large" description="Large size">
Large dialog content.
</mod.Dialog>
))
}
>
Large
</Button>
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog size="x-large" title="Extra large" description="X-large size">
X-large dialog content.
</mod.Dialog>
))
}
>
X-Large
</Button>
</div>
)
},
}
export const Transition = {
render: () => {
const dialog = useDialog()
return (
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog title="Transition" description="Animated" transition>
Transition enabled.
</mod.Dialog>
))
}
>
Open transition dialog
</Button>
)
},
}
export const CustomAction = {
render: () => {
const dialog = useDialog()
return (
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog
title="Custom action"
description="Dialog with a custom header action"
action={<Button variant="ghost">Help</Button>}
>
Dialog body content.
</mod.Dialog>
))
}
>
Open action dialog
</Button>
)
},
}
export const Fit = {
render: () => {
const dialog = useDialog()
return (
<Button
variant="secondary"
onClick={() =>
dialog.show(() => (
<mod.Dialog title="Fit content" fit>
Dialog fits its content.
</mod.Dialog>
))
}
>
Open fit dialog
</Button>
)
},
}

View File

@@ -0,0 +1,81 @@
// @ts-nocheck
import * as mod from "./diff-changes"
import { create } from "../storybook/scaffold"
import { changes } from "../storybook/fixtures"
const docs = `### Overview
Summarize additions/deletions as text or compact bars.
Pair with \`Diff\`/\`DiffSSR\` to contextualize a change set.
### API
- Required: \`changes\` as { additions, deletions } or an array of those objects.
- Optional: \`variant\` ("default" | "bars").
### Variants and states
- Default text summary or bar visualization.
- Handles zero-change state (renders nothing in default variant).
### Behavior
- Aggregates arrays into total additions/deletions.
### Accessibility
- Ensure surrounding context conveys meaning of the counts/bars.
### Theming/tokens
- Uses \`data-component="diff-changes"\` and diff color tokens.
`
const story = create({
title: "UI/DiffChanges",
mod,
args: {
changes,
variant: "default",
},
})
export default {
title: "UI/DiffChanges",
id: "components-diff-changes",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
variant: {
control: "select",
options: ["default", "bars"],
},
},
}
export const Default = story.Basic
export const Bars = {
args: {
variant: "bars",
},
}
export const MultipleFiles = {
args: {
changes: [
{ additions: 4, deletions: 1 },
{ additions: 8, deletions: 3 },
{ additions: 2, deletions: 0 },
],
},
}
export const Zero = {
args: {
changes: { additions: 0, deletions: 0 },
},
}

View File

@@ -0,0 +1,97 @@
// @ts-nocheck
import { preloadMultiFileDiff } from "@pierre/diffs/ssr"
import { createResource, Show } from "solid-js"
import * as mod from "./diff-ssr"
import { createDefaultOptions } from "../pierre"
import { WorkerPoolProvider } from "../context/worker-pool"
import { getWorkerPools } from "../pierre/worker"
import { diff } from "../storybook/fixtures"
const docs = `### Overview
Server-rendered diff hydration component for preloaded Pierre diff output.
Use alongside server routes that preload diffs.
Pair with \`DiffChanges\` for summaries.
### API
- Required: \`before\`, \`after\`, and \`preloadedDiff\` from \`preloadMultiFileDiff\`.
- Optional: \`diffStyle\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`.
### Variants and states
- Unified/split styles (preloaded must match the style used during preload).
### Behavior
- Hydrates pre-rendered diff HTML into a live diff instance.
- Requires a worker pool provider for syntax highlighting.
### Accessibility
- TODO: confirm keyboard behavior from the Pierre diff engine.
### Theming/tokens
- Uses \`data-component="diff"\` with Pierre CSS variables and theme tokens.
`
const load = async () => {
return preloadMultiFileDiff({
oldFile: diff.before,
newFile: diff.after,
options: createDefaultOptions("unified"),
})
}
const loadSplit = async () => {
return preloadMultiFileDiff({
oldFile: diff.before,
newFile: diff.after,
options: createDefaultOptions("split"),
})
}
export default {
title: "UI/DiffSSR",
id: "components-diff-ssr",
component: mod.Diff,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => {
const [data] = createResource(load)
return (
<WorkerPoolProvider pools={getWorkerPools()}>
<Show when={data()} fallback={<div>Loading pre-rendered diff...</div>}>
{(preloaded) => (
<div style={{ "max-width": "960px" }}>
<mod.Diff before={diff.before} after={diff.after} diffStyle="unified" preloadedDiff={preloaded()} />
</div>
)}
</Show>
</WorkerPoolProvider>
)
},
}
export const Split = {
render: () => {
const [data] = createResource(loadSplit)
return (
<WorkerPoolProvider pools={getWorkerPools()}>
<Show when={data()} fallback={<div>Loading pre-rendered diff...</div>}>
{(preloaded) => (
<div style={{ "max-width": "960px" }}>
<mod.Diff before={diff.before} after={diff.after} diffStyle="split" preloadedDiff={preloaded()} />
</div>
)}
</Show>
</WorkerPoolProvider>
)
},
}

View File

@@ -0,0 +1,96 @@
// @ts-nocheck
import * as mod from "./diff"
import { create } from "../storybook/scaffold"
import { diff } from "../storybook/fixtures"
const docs = `### Overview
Render a code diff with OpenCode styling using the Pierre diff engine.
Pair with \`DiffChanges\` for summary counts.
Use \`LineComment\` or external UI for annotation workflows.
### API
- Required: \`before\` and \`after\` file contents (name + contents).
- Optional: \`diffStyle\` ("unified" | "split"), \`annotations\`, \`selectedLines\`, \`commentedLines\`.
- Optional interaction: \`enableLineSelection\`, \`onLineSelectionEnd\`.
- Passes through Pierre FileDiff options (see component source).
### Variants and states
- Unified and split diff styles.
- Optional line selection + commented line highlighting.
### Behavior
- Re-renders when \`before\`/\`after\` or diff options change.
- Line selection uses mouse drag/selection when enabled.
### Accessibility
- TODO: confirm keyboard behavior from the Pierre diff engine.
- Provide surrounding labels or headings when used as a standalone view.
### Theming/tokens
- Uses \`data-component="diff"\` and Pierre CSS variables from \`styleVariables\`.
- Colors derive from theme tokens (diff add/delete, background, text).
`
const story = create({
title: "UI/Diff",
mod,
args: {
before: diff.before,
after: diff.after,
diffStyle: "unified",
},
})
export default {
title: "UI/Diff",
id: "components-diff",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
diffStyle: {
control: "select",
options: ["unified", "split"],
},
enableLineSelection: {
control: "boolean",
},
},
}
export const Unified = story.Basic
export const Split = {
args: {
diffStyle: "split",
},
}
export const Selectable = {
args: {
enableLineSelection: true,
},
}
export const SelectedLines = {
args: {
selectedLines: { start: 2, end: 4 },
},
}
export const CommentedLines = {
args: {
commentedLines: [
{ start: 1, end: 1 },
{ start: 4, end: 4 },
],
},
}

View File

@@ -0,0 +1,62 @@
// @ts-nocheck
import * as mod from "./dock-prompt"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Docked prompt layout for questions and permission requests.
Use with form controls or confirmation buttons in the footer.
### API
- Required: \`kind\` (question | permission), \`header\`, \`children\`, \`footer\`.
- Optional: \`ref\` for measuring or focus management.
### Variants and states
- Question and permission layouts (data attributes).
### Behavior
- Pure layout component; behavior handled by parent.
### Accessibility
- Ensure header and footer content provide clear context and actions.
### Theming/tokens
- Uses \`data-component="dock-prompt"\` with kind data attribute.
`
const story = create({
title: "UI/DockPrompt",
mod,
args: {
kind: "question",
header: "Header",
children: "Prompt content",
footer: "Footer",
},
})
export default {
title: "UI/DockPrompt",
id: "components-dock-prompt",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Permission = {
args: {
kind: "permission",
header: "Allow access?",
children: "This action needs permission to proceed.",
footer: "Approve or deny",
},
}

View File

@@ -0,0 +1,97 @@
// @ts-nocheck
import * as mod from "./dropdown-menu"
import { Button } from "./button"
const docs = `### Overview
Dropdown menu built on Kobalte with composable items, groups, and submenus.
Use \`DropdownMenu.ItemLabel\`/\`ItemDescription\` for richer rows.
### API
- Root accepts Kobalte DropdownMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
- Compose with \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections.
### Variants and states
- Supports item groups, separators, and nested submenus.
### Behavior
- Menu opens from trigger and renders in a portal by default.
### Accessibility
- TODO: confirm keyboard navigation from Kobalte.
### Theming/tokens
- Uses \`data-component="dropdown-menu"\` and slot attributes for styling.
`
export default {
title: "UI/DropdownMenu",
id: "components-dropdown-menu",
component: mod.DropdownMenu,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<mod.DropdownMenu defaultOpen>
<mod.DropdownMenu.Trigger as={Button} variant="secondary" size="small">
Open menu
</mod.DropdownMenu.Trigger>
<mod.DropdownMenu.Portal>
<mod.DropdownMenu.Content>
<mod.DropdownMenu.Group>
<mod.DropdownMenu.GroupLabel>Actions</mod.DropdownMenu.GroupLabel>
<mod.DropdownMenu.Item>
<mod.DropdownMenu.ItemLabel>New file</mod.DropdownMenu.ItemLabel>
</mod.DropdownMenu.Item>
<mod.DropdownMenu.Item>
<mod.DropdownMenu.ItemLabel>Rename</mod.DropdownMenu.ItemLabel>
<mod.DropdownMenu.ItemDescription>Shift+R</mod.DropdownMenu.ItemDescription>
</mod.DropdownMenu.Item>
</mod.DropdownMenu.Group>
<mod.DropdownMenu.Separator />
<mod.DropdownMenu.Sub>
<mod.DropdownMenu.SubTrigger>More options</mod.DropdownMenu.SubTrigger>
<mod.DropdownMenu.SubContent>
<mod.DropdownMenu.Item>
<mod.DropdownMenu.ItemLabel>Duplicate</mod.DropdownMenu.ItemLabel>
</mod.DropdownMenu.Item>
<mod.DropdownMenu.Item>
<mod.DropdownMenu.ItemLabel>Move</mod.DropdownMenu.ItemLabel>
</mod.DropdownMenu.Item>
</mod.DropdownMenu.SubContent>
</mod.DropdownMenu.Sub>
</mod.DropdownMenu.Content>
</mod.DropdownMenu.Portal>
</mod.DropdownMenu>
),
}
export const CheckboxRadio = {
render: () => (
<mod.DropdownMenu defaultOpen>
<mod.DropdownMenu.Trigger as={Button} variant="secondary" size="small">
Open menu
</mod.DropdownMenu.Trigger>
<mod.DropdownMenu.Portal>
<mod.DropdownMenu.Content>
<mod.DropdownMenu.CheckboxItem checked>Show line numbers</mod.DropdownMenu.CheckboxItem>
<mod.DropdownMenu.CheckboxItem>Wrap lines</mod.DropdownMenu.CheckboxItem>
<mod.DropdownMenu.Separator />
<mod.DropdownMenu.RadioGroup value="compact">
<mod.DropdownMenu.RadioItem value="compact">Compact</mod.DropdownMenu.RadioItem>
<mod.DropdownMenu.RadioItem value="comfortable">Comfortable</mod.DropdownMenu.RadioItem>
</mod.DropdownMenu.RadioGroup>
</mod.DropdownMenu.Content>
</mod.DropdownMenu.Portal>
</mod.DropdownMenu>
),
}

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
import * as mod from "./favicon"
const docs = `### Overview
Injects favicon and app icon meta tags for the document head.
Render once near the app root (head management).
### API
- No props.
### Variants and states
- Single configuration.
### Behavior
- Registers link and meta tags via Solid Meta components.
### Accessibility
- Not applicable.
### Theming/tokens
- Not applicable.
`
export default {
title: "UI/Favicon",
id: "components-favicon",
component: mod.Favicon,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<div style={{ display: "grid", gap: "8px" }}>
<mod.Favicon />
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
Head tags are injected for favicon and app icons.
</div>
</div>
),
}

View File

@@ -0,0 +1,94 @@
// @ts-nocheck
import * as mod from "./file-icon"
import { create } from "../storybook/scaffold"
const docs = `### Overview
File and folder icon renderer based on file name and extension.
Use in file trees and lists.
### API
- Required: \`node\` with \`path\` and \`type\`.
- Optional: \`expanded\` (for folders), \`mono\` for monochrome rendering.
### Variants and states
- Folder vs file icons; expanded folder variant.
### Behavior
- Maps file names and extensions to sprite icons.
### Accessibility
- Provide adjacent text labels for filenames; icons are decorative.
### Theming/tokens
- Uses \`data-component="file-icon"\` and sprite-based styling.
`
const story = create({
title: "UI/FileIcon",
mod,
args: {
node: { path: "package.json", type: "file" },
mono: true,
},
})
export default {
title: "UI/FileIcon",
id: "components-file-icon",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Folder = {
args: {
node: { path: "src", type: "directory" },
expanded: true,
mono: false,
},
}
export const Samples = {
render: () => {
const items = [
{ path: "README.md", type: "file" },
{ path: "package.json", type: "file" },
{ path: "tsconfig.json", type: "file" },
{ path: "index.ts", type: "file" },
{ path: "styles.css", type: "file" },
{ path: "logo.svg", type: "file" },
{ path: "photo.png", type: "file" },
{ path: "Dockerfile", type: "file" },
{ path: ".env", type: "file" },
{ path: "src", type: "directory" },
{ path: "public", type: "directory" },
] as const
return (
<div
style={{
display: "grid",
gap: "12px",
"grid-template-columns": "repeat(auto-fill, minmax(120px, 1fr))",
}}
>
{items.map((node) => (
<div style={{ display: "flex", gap: "8px", "align-items": "center" }}>
<mod.FileIcon node={{ path: node.path, type: node.type }} mono={false} />
<div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{node.path}</div>
</div>
))}
</div>
)
},
}

View File

@@ -0,0 +1,48 @@
// @ts-nocheck
import * as mod from "./font"
const docs = `### Overview
Loads OpenCode typography assets and mono nerd fonts.
Render once at the app root or Storybook preview.
### API
- No props.
### Variants and states
- Fonts include sans and multiple mono families.
### Behavior
- Injects @font-face rules and preload links into the document head.
### Accessibility
- Not applicable.
### Theming/tokens
- Provides font families used by theme tokens.
`
export default {
title: "UI/Font",
id: "components-font",
component: mod.Font,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<div style={{ display: "grid", gap: "8px" }}>
<mod.Font />
<div style={{ "font-family": "var(--font-family-sans)" }}>OpenCode Sans Sample</div>
<div style={{ "font-family": "var(--font-family-mono)" }}>OpenCode Mono Sample</div>
</div>
),
}

View File

@@ -0,0 +1,70 @@
// @ts-nocheck
import { createSignal } from "solid-js"
import * as mod from "./hover-card"
const docs = `### Overview
Hover-triggered card for lightweight previews and metadata.
Use for short summaries; avoid dense interactive controls.
### API
- Required: \`trigger\` element.
- Children render inside the hover card body.
### Variants and states
- None; content and trigger are fully composable.
### Behavior
- Opens on hover/focus over the trigger.
### Accessibility
- TODO: confirm focus and hover intent behavior from Kobalte.
### Theming/tokens
- Uses \`data-component="hover-card-content"\` and slots for styling.
`
export default {
title: "UI/HoverCard",
id: "components-hover-card",
component: mod.HoverCard,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<mod.HoverCard trigger={<span style={{ "text-decoration": "underline", cursor: "default" }}>Hover me</span>}>
<div style={{ display: "grid", gap: "6px" }}>
<div style={{ "font-weight": 600 }}>Preview</div>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Short supporting text.</div>
</div>
</mod.HoverCard>
),
}
export const InlineMount = {
render: () => {
const [mount, setMount] = createSignal<HTMLDivElement | undefined>(undefined)
return (
<div ref={setMount} style={{ padding: "16px", border: "1px dashed var(--border-weak)" }}>
<mod.HoverCard
mount={mount()}
trigger={<span style={{ "text-decoration": "underline", cursor: "default" }}>Hover me</span>}
>
<div style={{ display: "grid", gap: "6px" }}>
<div style={{ "font-weight": 600 }}>Mounted inside</div>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Uses custom mount node.</div>
</div>
</mod.HoverCard>
</div>
)
},
}

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import * as mod from "./icon-button"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Compact icon-only button with size and variant control.
Use \`Button\` for text labels and primary actions.
### API
- Required: \`icon\` icon name.
- Optional: \`size\`, \`iconSize\`, \`variant\`.
- Inherits Kobalte Button props and native button attributes.
### Variants and states
- Variants: primary, secondary, ghost.
- Sizes: small, normal, large.
### Behavior
- Icon size adapts to button size unless overridden.
### Accessibility
- Provide \`aria-label\` when there is no visible text.
### Theming/tokens
- Uses \`data-component="icon-button"\` and size/variant data attributes.
`
const story = create({ title: "UI/IconButton", mod, args: { icon: "check", "aria-label": "Icon" } })
export default {
title: "UI/IconButton",
id: "components-icon-button",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<mod.IconButton icon="check" size="small" aria-label="Small" />
<mod.IconButton icon="check" size="normal" aria-label="Normal" />
<mod.IconButton icon="check" size="large" aria-label="Large" />
</div>
),
}
export const Variants = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<mod.IconButton icon="check" variant="primary" aria-label="Primary" />
<mod.IconButton icon="check" variant="secondary" aria-label="Secondary" />
<mod.IconButton icon="check" variant="ghost" aria-label="Ghost" />
</div>
),
}
export const IconSizeOverride = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<mod.IconButton icon="check" size="small" iconSize="large" aria-label="Small with large icon" />
<mod.IconButton icon="check" size="large" iconSize="small" aria-label="Large with small icon" />
</div>
),
}

View File

@@ -0,0 +1,170 @@
// @ts-nocheck
import * as mod from "./icon"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Inline icon renderer using the built-in OpenCode icon set.
Use with \`Button\`, \`IconButton\`, and menu items.
### API
- Required: \`name\` (icon key).
- Optional: \`size\` (small | normal | medium | large).
- Accepts standard SVG props.
### Variants and states
- Size variants only.
### Behavior
- Uses an internal SVG path map.
### Accessibility
- Icons are aria-hidden by default; wrap with accessible text when needed.
### Theming/tokens
- Uses \`data-component="icon"\` with size data attributes.
`
const names = [
"align-right",
"arrow-up",
"arrow-left",
"arrow-right",
"archive",
"bubble-5",
"prompt",
"brain",
"bullet-list",
"check-small",
"chevron-down",
"chevron-left",
"chevron-right",
"chevron-grabber-vertical",
"chevron-double-right",
"circle-x",
"close",
"close-small",
"checklist",
"console",
"expand",
"collapse",
"code",
"code-lines",
"circle-ban-sign",
"edit-small-2",
"eye",
"enter",
"folder",
"file-tree",
"file-tree-active",
"magnifying-glass",
"plus-small",
"plus",
"new-session",
"pencil-line",
"mcp",
"glasses",
"magnifying-glass-menu",
"window-cursor",
"task",
"stop",
"layout-left",
"layout-left-partial",
"layout-left-full",
"layout-right",
"layout-right-partial",
"layout-right-full",
"square-arrow-top-right",
"open-file",
"speech-bubble",
"comment",
"folder-add-left",
"github",
"discord",
"layout-bottom",
"layout-bottom-partial",
"layout-bottom-full",
"dot-grid",
"circle-check",
"copy",
"check",
"photo",
"share",
"download",
"menu",
"server",
"branch",
"edit",
"help",
"settings-gear",
"dash",
"cloud-upload",
"trash",
"sliders",
"keyboard",
"selector",
"arrow-down-to-line",
"warning",
"link",
"providers",
"models",
]
const story = create({ title: "UI/Icon", mod, args: { name: "check" } })
export default {
title: "UI/Icon",
id: "components-icon",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
name: {
control: "select",
options: names,
},
size: {
control: "select",
options: ["small", "normal", "medium", "large"],
},
},
}
export const Basic = story.Basic
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
<mod.Icon name="check" size="small" />
<mod.Icon name="check" size="normal" />
<mod.Icon name="check" size="medium" />
<mod.Icon name="check" size="large" />
</div>
),
}
export const Gallery = {
render: () => (
<div
style={{
display: "grid",
gap: "12px",
"grid-template-columns": "repeat(auto-fill, minmax(88px, 1fr))",
}}
>
{names.map((name) => (
<div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
<mod.Icon name={name} />
<div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{name}</div>
</div>
))}
</div>
),
}

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import { onMount } from "solid-js"
import * as mod from "./image-preview"
import { Button } from "./button"
import { useDialog } from "../context/dialog"
const docs = `### Overview
Image preview content intended to render inside the dialog stack.
Use for full-size image inspection; keep images optimized.
### API
- Required: \`src\`.
- Optional: \`alt\` text.
### Variants and states
- Single layout with close action.
### Behavior
- Intended to be used via \`useDialog().show\`.
### Accessibility
- Uses localized aria-label for close button.
### Theming/tokens
- Uses \`data-component="image-preview"\` and slot attributes.
`
export default {
title: "UI/ImagePreview",
id: "components-image-preview",
component: mod.ImagePreview,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => {
const dialog = useDialog()
const src = "https://placehold.co/640x360/png"
const open = () => dialog.show(() => <mod.ImagePreview src={src} alt="Preview" />)
onMount(open)
return (
<Button variant="secondary" onClick={open}>
Open image preview
</Button>
)
},
}

View File

@@ -0,0 +1,50 @@
// @ts-nocheck
import * as mod from "./inline-input"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Compact inline input for short values.
Use inside text or table rows for quick edits.
### API
- Optional: \`width\` to set a fixed width.
- Accepts standard input props.
### Variants and states
- No built-in variants; style via class or width.
### Behavior
- Uses inline width when provided.
### Accessibility
- Provide a label or aria-label when used standalone.
### Theming/tokens
- Uses \`data-component="inline-input"\`.
`
const story = create({ title: "UI/InlineInput", mod, args: { placeholder: "Type...", value: "Inline" } })
export default {
title: "UI/InlineInput",
id: "components-inline-input",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const FixedWidth = {
args: {
value: "80px",
width: "80px",
},
}

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
import * as mod from "./keybind"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Keyboard shortcut pill for displaying keybindings.
Pair with menu items or command palettes.
### API
- Children render the key sequence text.
- Accepts standard span props.
### Variants and states
- Single visual style.
### Behavior
- Presentational only.
### Accessibility
- Ensure text conveys the shortcut (e.g., "Cmd+K").
### Theming/tokens
- Uses \`data-component="keybind"\`.
`
const story = create({ title: "UI/Keybind", mod, args: { children: "Cmd+K" } })
export default {
title: "UI/Keybind",
id: "components-keybind",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic

View File

@@ -0,0 +1,115 @@
// @ts-nocheck
import { createSignal } from "solid-js"
import * as mod from "./line-comment"
const docs = `### Overview
Inline comment anchor and editor for code review or annotation flows.
Pair with \`Diff\` or \`Code\` to align comments to lines.
### API
- \`LineCommentAnchor\`: position with \`top\`, control \`open\`, render custom children.
- \`LineComment\`: convenience wrapper for displaying comment + selection label.
- \`LineCommentEditor\`: controlled textarea with submit/cancel handlers.
### Variants and states
- Default display and editor display variants.
### Behavior
- Anchor positions relative to a containing element.
- Editor submits on Enter (Shift+Enter for newline).
### Accessibility
- TODO: confirm ARIA labeling for comment button and editor textarea.
### Theming/tokens
- Uses \`data-component="line-comment"\` and related slots.
`
export default {
title: "UI/LineComment",
id: "components-line-comment",
component: mod.LineComment,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Default = {
render: () => (
<div
style={{
position: "relative",
height: "160px",
padding: "16px 16px 16px 40px",
border: "1px solid var(--border-weak)",
"border-radius": "8px",
"font-family": "var(--font-family-mono)",
"font-size": "12px",
color: "var(--text-weak)",
}}
>
<div>12 | const total = sum(values)</div>
<div>13 | return total / values.length</div>
<mod.LineComment open top={18} comment="Consider guarding against empty arrays." selection="L12-L13" />
</div>
),
}
export const Editor = {
render: () => {
const [value, setValue] = createSignal("Add context for this change.")
return (
<div
style={{
position: "relative",
height: "220px",
padding: "16px 16px 16px 40px",
border: "1px solid var(--border-weak)",
"border-radius": "8px",
"font-family": "var(--font-family-mono)",
"font-size": "12px",
color: "var(--text-weak)",
}}
>
<div>40 | if (values.length === 0) return 0</div>
<mod.LineCommentEditor
top={24}
value={value()}
selection="L40"
onInput={setValue}
onCancel={() => setValue("")}
onSubmit={(next) => setValue(next)}
/>
</div>
)
},
}
export const AnchorOnly = {
render: () => (
<div
style={{
position: "relative",
height: "120px",
padding: "16px 16px 16px 40px",
border: "1px solid var(--border-weak)",
"border-radius": "8px",
"font-family": "var(--font-family-mono)",
"font-size": "12px",
color: "var(--text-weak)",
}}
>
<div>20 | const ready = true</div>
<mod.LineCommentAnchor top={18} open={false}>
<div data-slot="line-comment-content">Anchor content</div>
</mod.LineCommentAnchor>
</div>
),
}

View File

@@ -0,0 +1,170 @@
// @ts-nocheck
import * as mod from "./list"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Filterable list with keyboard navigation and optional search input.
Use within panels or popovers where keyboard navigation is expected.
### API
- Required: \`items\` and \`key\`.
- Required: \`children\` render function for items.
- Optional: \`search\`, \`filterKeys\`, \`groupBy\`, \`onSelect\`, \`onKeyEvent\`.
### Variants and states
- Optional search bar and group headers.
### Behavior
- Uses fuzzy search when \`search\` is enabled.
- Keyboard navigation via arrow keys; Enter selects.
### Accessibility
- TODO: confirm ARIA roles for list items and search input.
### Theming/tokens
- Uses \`data-component="list"\` and data slots for structure.
`
const story = create({
title: "UI/List",
mod,
args: {
items: ["One", "Two", "Three", "Four"],
key: (x: string) => x,
children: (x: string) => x,
search: true,
},
})
export default {
title: "UI/List",
id: "components-list",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Grouped = {
render: () => {
const items = [
{ id: "a1", title: "Alpha", group: "Group A" },
{ id: "a2", title: "Bravo", group: "Group A" },
{ id: "b1", title: "Delta", group: "Group B" },
]
return (
<mod.List items={items} key={(item) => item.id} groupBy={(item) => item.group} search={true}>
{(item) => item.title}
</mod.List>
)
},
}
export const Empty = {
render: () => (
<mod.List items={[]} key={(item) => item} search={true}>
{(item) => item}
</mod.List>
),
}
export const WithAdd = {
render: () => (
<mod.List
items={["One", "Two"]}
key={(item) => item}
search={true}
add={{
render: () => (
<button type="button" data-slot="list-item">
Add item
</button>
),
}}
>
{(item) => item}
</mod.List>
),
}
export const Divider = {
render: () => (
<mod.List items={["One", "Two", "Three"]} key={(item) => item} divider={true}>
{(item) => item}
</mod.List>
),
}
export const ActiveIcon = {
render: () => (
<mod.List items={["Alpha", "Beta", "Gamma"]} key={(item) => item} activeIcon="chevron-right">
{(item) => item}
</mod.List>
),
}
export const NoSearch = {
render: () => (
<mod.List items={["One", "Two", "Three"]} key={(item) => item} search={false}>
{(item) => item}
</mod.List>
),
}
export const SearchOptions = {
render: () => (
<mod.List
items={["Apple", "Banana", "Cherry"]}
key={(item) => item}
search={{
placeholder: "Filter...",
hideIcon: true,
action: <button type="button">Action</button>,
}}
>
{(item) => item}
</mod.List>
),
}
export const ItemWrapper = {
render: () => (
<mod.List
items={["One", "Two", "Three"]}
key={(item) => item}
itemWrapper={(item, node) => (
<div style={{ border: "1px solid var(--border-weak)", "border-radius": "6px", margin: "4px 0" }}>{node}</div>
)}
>
{(item) => item}
</mod.List>
),
}
export const GroupHeader = {
render: () => {
const items = [
{ id: "a1", title: "Alpha", group: "Group A" },
{ id: "b1", title: "Beta", group: "Group B" },
]
return (
<mod.List
items={items}
key={(item) => item.id}
groupBy={(item) => item.group}
groupHeader={(group) => <strong>{group.category}</strong>}
>
{(item) => item.title}
</mod.List>
)
},
}

View File

@@ -0,0 +1,57 @@
// @ts-nocheck
import * as mod from "./logo"
const docs = `### Overview
OpenCode logo assets: mark, splash, and wordmark.
Use Mark for compact spaces, Logo for headers, Splash for hero sections.
### API
- \`Mark\`, \`Splash\`, and \`Logo\` components accept standard SVG props.
### Variants and states
- Multiple logo variants for different contexts.
### Behavior
- Pure SVG rendering.
### Accessibility
- Provide title/aria-label when logos convey meaning.
### Theming/tokens
- Uses theme color tokens via CSS variables.
`
export default {
title: "UI/Logo",
id: "components-logo",
component: mod.Logo,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<div style={{ display: "grid", gap: "16px", "align-items": "start" }}>
<div>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Mark</div>
<mod.Mark />
</div>
<div>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Splash</div>
<mod.Splash style={{ width: "80px", height: "100px" }} />
</div>
<div>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Logo</div>
<mod.Logo style={{ width: "200px" }} />
</div>
</div>
),
}

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
import * as mod from "./markdown"
import { create } from "../storybook/scaffold"
import { markdown } from "../storybook/fixtures"
const docs = `### Overview
Render sanitized Markdown with code blocks, inline code, and safe links.
Pair with \`Code\` for standalone code views.
### API
- Required: \`text\` Markdown string.
- Uses the Marked context provider for parsing and sanitization.
### Variants and states
- Code blocks include copy buttons when rendered.
### Behavior
- Sanitizes HTML and auto-converts inline URL code to links.
- Adds copy buttons to code blocks.
### Accessibility
- Copy buttons include aria-labels from i18n.
- TODO: confirm link target behavior in sanitized output.
### Theming/tokens
- Uses \`data-component="markdown"\` and related slots for styling.
`
const story = create({
title: "UI/Markdown",
mod,
args: {
text: markdown,
},
})
export default {
title: "UI/Markdown",
id: "components-markdown",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
import * as mod from "./message-nav"
import { create } from "../storybook/scaffold"
const story = create({ title: "UI/MessageNav", mod })
export default { title: "UI/MessageNav", id: "components-message-nav", component: story.meta.component }
export const Basic = story.Basic

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
import * as mod from "./message-part"
import { create } from "../storybook/scaffold"
const story = create({ title: "UI/MessagePart", mod })
export default { title: "UI/MessagePart", id: "components-message-part", component: story.meta.component }
export const Basic = story.Basic

View File

@@ -0,0 +1,87 @@
// @ts-nocheck
import { createSignal } from "solid-js"
import * as mod from "./popover"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Composable popover with optional title, description, and close button.
Use for small contextual details; avoid long forms.
### API
- \`trigger\` and \`children\` define the anchor and content.
- Optional: \`title\`, \`description\`, \`portal\`, \`open\`, \`defaultOpen\`.
### Variants and states
- Supports controlled and uncontrolled open state.
### Behavior
- Closes on outside click or Escape by default.
### Accessibility
- TODO: confirm focus management from Kobalte.
### Theming/tokens
- Uses \`data-component="popover-content"\` and related slots.
`
const story = create({
title: "UI/Popover",
mod,
args: {
trigger: "Open popover",
title: "Popover",
description: "Optional description",
defaultOpen: true,
children: "Popover content",
},
})
export default {
title: "UI/Popover",
id: "components-popover",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const NoHeader = {
args: {
title: undefined,
description: undefined,
children: "Popover body only",
},
}
export const Inline = {
args: {
portal: false,
defaultOpen: true,
},
}
export const Controlled = {
render: () => {
const [open, setOpen] = createSignal(true)
return (
<mod.Popover
open={open()}
onOpenChange={setOpen}
trigger="Toggle popover"
title="Controlled"
description="Open state is controlled"
>
Controlled content
</mod.Popover>
)
},
}

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import * as mod from "./progress-circle"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Circular progress indicator for compact loading states.
Pair with labels for clarity in dashboards.
### API
- Required: \`percentage\` (0-100).
- Optional: \`size\`, \`strokeWidth\`.
### Variants and states
- Single visual style; size and stroke width adjust appearance.
### Behavior
- Percentage is clamped between 0 and 100.
### Accessibility
- Use alongside text or aria-live messaging for progress context.
### Theming/tokens
- Uses \`data-component="progress-circle"\` with background/progress slots.
`
const story = create({ title: "UI/ProgressCircle", mod, args: { percentage: 65, size: 48 } })
export default {
title: "UI/ProgressCircle",
id: "components-progress-circle",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
percentage: {
control: { type: "range", min: 0, max: 100, step: 1 },
},
},
}
export const Basic = story.Basic
export const States = {
render: () => (
<div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
<mod.ProgressCircle percentage={0} size={32} />
<mod.ProgressCircle percentage={50} size={32} />
<mod.ProgressCircle percentage={100} size={32} />
</div>
),
}

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
import * as mod from "./progress"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Linear progress indicator with optional label and value display.
Use in forms, uploads, or background tasks.
### API
- \`value\` and \`maxValue\` control progress.
- Optional: \`showValueLabel\`, \`hideLabel\`.
- Children provide the label text.
### Variants and states
- Supports indeterminate state via Kobalte props (if provided).
### Behavior
- Uses Kobalte Progress for value calculation.
### Accessibility
- TODO: confirm ARIA attributes from Kobalte.
### Theming/tokens
- Uses \`data-component="progress"\` with track/fill slots.
`
const story = create({
title: "UI/Progress",
mod,
args: {
value: 60,
maxValue: 100,
children: "Progress",
showValueLabel: true,
},
})
export default {
title: "UI/Progress",
id: "components-progress",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const NoLabel = {
args: {
children: "",
hideLabel: true,
showValueLabel: false,
value: 30,
},
}
export const Indeterminate = {
render: () => <mod.Progress>Loading</mod.Progress>,
}

View File

@@ -0,0 +1,69 @@
// @ts-nocheck
import { iconNames } from "./provider-icons/types"
import * as mod from "./provider-icon"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Provider icon sprite renderer for model/provider badges.
Use in model pickers or provider lists.
### API
- Required: \`id\` (provider icon name).
- Accepts standard SVG props.
### Variants and states
- Single visual style; size via CSS.
### Behavior
- Renders from the provider SVG sprite sheet.
### Accessibility
- Provide accessible text nearby when the icon conveys meaning.
### Theming/tokens
- Uses \`data-component="provider-icon"\`.
`
const story = create({ title: "UI/ProviderIcon", mod, args: { id: "openai" } })
export default {
title: "UI/ProviderIcon",
id: "components-provider-icon",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
id: {
control: "select",
options: iconNames,
},
},
}
export const Basic = story.Basic
export const AllIcons = {
render: () => (
<div
style={{
display: "grid",
gap: "12px",
"grid-template-columns": "repeat(auto-fill, minmax(80px, 1fr))",
}}
>
{iconNames.map((id) => (
<div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
<mod.ProviderIcon id={id} width="28" height="28" aria-label={id} />
<div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{id}</div>
</div>
))}
</div>
),
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -10,6 +10,7 @@ export const iconNames = [
"xai",
"wandb",
"vultr",
"vivgrid",
"vercel",
"venice",
"v0",
@@ -17,11 +18,16 @@ export const iconNames = [
"togetherai",
"synthetic",
"submodel",
"stepfun",
"stackit",
"siliconflow",
"siliconflow-cn",
"scaleway",
"sap-ai-core",
"requesty",
"qiniu-ai",
"qihang-ai",
"privatemode-ai",
"poe",
"perplexity",
"ovhcloud",
@@ -30,19 +36,28 @@ export const iconNames = [
"openai",
"ollama-cloud",
"nvidia",
"novita-ai",
"nova",
"nebius",
"nano-gpt",
"morph",
"moonshotai",
"moonshotai-cn",
"modelscope",
"moark",
"mistral",
"minimax",
"minimax-coding-plan",
"minimax-cn",
"minimax-cn-coding-plan",
"meganova",
"lucidquery",
"lmstudio",
"llama",
"kuae-cloud-coding-plan",
"kimi-for-coding",
"kilo",
"jiekou",
"io-net",
"inference",
"inception",
@@ -53,9 +68,11 @@ export const iconNames = [
"google",
"google-vertex",
"google-vertex-anthropic",
"gitlab",
"github-models",
"github-copilot",
"friendli",
"firmware",
"fireworks-ai",
"fastrouter",
"deepseek",
@@ -64,8 +81,10 @@ export const iconNames = [
"cohere",
"cloudflare-workers-ai",
"cloudflare-ai-gateway",
"cloudferro-sherlock",
"chutes",
"cerebras",
"berget",
"baseten",
"bailing",
"azure",
@@ -76,6 +95,7 @@ export const iconNames = [
"alibaba-cn",
"aihubmix",
"abacus",
"302ai",
] as const
export type IconName = (typeof iconNames)[number]

View File

@@ -0,0 +1,92 @@
// @ts-nocheck
import * as mod from "./radio-group"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Segmented radio group for choosing a single option.
Use for view toggles or mode selection.
### API
- Required: \`options\`.
- Optional: \`current\`, \`defaultValue\`, \`value\`, \`label\`, \`onSelect\`.
- Optional layout: \`size\`, \`fill\`, \`pad\`.
### Variants and states
- Size variants: small, medium.
- Optional fill and padding behavior.
### Behavior
- Maps options to segmented items and manages selection.
### Accessibility
- TODO: confirm role/aria attributes from Kobalte SegmentedControl.
### Theming/tokens
- Uses \`data-component="radio-group"\` with size/pad data attributes.
`
const story = create({
title: "UI/RadioGroup",
mod,
args: {
options: ["One", "Two", "Three"],
defaultValue: "One",
},
})
export default {
title: "UI/RadioGroup",
id: "components-radio-group",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
size: {
control: "select",
options: ["small", "medium"],
},
pad: {
control: "select",
options: ["none", "normal"],
},
fill: {
control: "boolean",
},
},
}
export const Basic = story.Basic
export const Sizes = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.RadioGroup options={["One", "Two"]} defaultValue="One" size="small" />
<mod.RadioGroup options={["One", "Two"]} defaultValue="One" size="medium" />
</div>
),
}
export const Filled = {
args: {
fill: true,
pad: "none",
},
}
export const CustomLabels = {
render: () => (
<mod.RadioGroup
options={["list", "grid"]}
defaultValue="list"
label={(value) => (value === "list" ? "List view" : "Grid view")}
/>
),
}

View File

@@ -0,0 +1,156 @@
// @ts-nocheck
import { createSignal } from "solid-js"
import * as mod from "./resize-handle"
const docs = `### Overview
Drag handle for resizing panels or split views.
Use alongside resizable panels and split layouts.
### API
- Required: \`direction\`, \`size\`, \`min\`, \`max\`, \`onResize\`.
- Optional: \`edge\`, \`onCollapse\`, \`collapseThreshold\`.
### Variants and states
- Horizontal and vertical directions.
### Behavior
- Drag updates size and calls \`onResize\` with clamped values.
### Accessibility
- TODO: provide keyboard resizing guidance if needed.
### Theming/tokens
- Uses \`data-component="resize-handle"\` with direction/edge data attributes.
`
export default {
title: "UI/ResizeHandle",
id: "components-resize-handle",
component: mod.ResizeHandle,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => {
const [size, setSize] = createSignal(240)
return (
<div style={{ display: "grid", gap: "8px" }}>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
<div
style={{
width: `${size()}px`,
height: "48px",
"background-color": "var(--background-stronger)",
"border-radius": "6px",
}}
/>
<mod.ResizeHandle
direction="horizontal"
size={size()}
min={120}
max={480}
onResize={setSize}
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
/>
</div>
)
},
}
export const Vertical = {
render: () => {
const [size, setSize] = createSignal(180)
return (
<div style={{ display: "grid", gap: "8px", width: "220px" }}>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
<div
style={{
height: `${size()}px`,
"background-color": "var(--background-stronger)",
"border-radius": "6px",
}}
/>
<mod.ResizeHandle
direction="vertical"
size={size()}
min={120}
max={320}
onResize={setSize}
style="width:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
/>
</div>
)
},
}
export const Collapse = {
render: () => {
const [size, setSize] = createSignal(200)
const [collapsed, setCollapsed] = createSignal(false)
return (
<div style={{ display: "grid", gap: "8px" }}>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
{collapsed() ? "Collapsed" : `Size: ${size()}px`}
</div>
<div
style={{
width: `${collapsed() ? 0 : size()}px`,
height: "48px",
"background-color": "var(--background-stronger)",
"border-radius": "6px",
}}
/>
<mod.ResizeHandle
direction="horizontal"
size={size()}
min={80}
max={360}
collapseThreshold={100}
onResize={(next) => {
setCollapsed(false)
setSize(next)
}}
onCollapse={() => setCollapsed(true)}
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
/>
</div>
)
},
}
export const EdgeStart = {
render: () => {
const [size, setSize] = createSignal(240)
return (
<div style={{ display: "grid", gap: "8px" }}>
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
<div
style={{
width: `${size()}px`,
height: "48px",
"background-color": "var(--background-stronger)",
"border-radius": "6px",
}}
/>
<mod.ResizeHandle
direction="horizontal"
edge="start"
size={size()}
min={120}
max={480}
onResize={setSize}
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
/>
</div>
)
},
}

View File

@@ -0,0 +1,113 @@
// @ts-nocheck
import * as mod from "./select"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Select menu for choosing a single option with optional grouping.
Use \`children\` to customize option rendering.
### API
- Required: \`options\`.
- Optional: \`current\`, \`placeholder\`, \`value\`, \`label\`, \`groupBy\`.
- Accepts Button props for the trigger (\`variant\`, \`size\`).
### Variants and states
- Trigger supports "settings" style via \`triggerVariant\`.
### Behavior
- Uses Kobalte Select with optional item highlight callbacks.
### Accessibility
- TODO: confirm keyboard navigation and aria attributes from Kobalte.
### Theming/tokens
- Uses \`data-component="select"\` with slot attributes.
`
const story = create({
title: "UI/Select",
mod,
args: {
options: ["One", "Two", "Three"],
current: "One",
placeholder: "Choose...",
variant: "secondary",
size: "normal",
},
})
export default {
title: "UI/Select",
id: "components-select",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
triggerVariant: {
control: "select",
options: ["settings", undefined],
},
},
}
export const Basic = story.Basic
export const Grouped = {
render: () => {
const options = [
{ id: "alpha", label: "Alpha", group: "Group A" },
{ id: "bravo", label: "Bravo", group: "Group A" },
{ id: "delta", label: "Delta", group: "Group B" },
]
return (
<mod.Select
options={options}
current={options[0]}
value={(item) => item.id}
label={(item) => item.label}
groupBy={(item) => item.group}
placeholder="Choose..."
variant="secondary"
/>
)
},
}
export const SettingsTrigger = {
args: {
triggerVariant: "settings",
},
}
export const CustomRender = {
render: () => (
<mod.Select
options={["Primary", "Secondary", "Ghost"]}
current="Primary"
placeholder="Choose..."
variant="secondary"
>
{(item) => <span style={{ "text-transform": "uppercase" }}>{item}</span>}
</mod.Select>
),
}
export const CustomTriggerStyle = {
args: {
triggerStyle: { "min-width": "180px", "justify-content": "space-between" },
},
}
export const Disabled = {
args: {
disabled: true,
},
}

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
import * as mod from "./session-review"
import { create } from "../storybook/scaffold"
const story = create({ title: "UI/SessionReview", mod })
export default { title: "UI/SessionReview", id: "components-session-review", component: story.meta.component }
export const Basic = story.Basic

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
import * as mod from "./session-turn"
import { create } from "../storybook/scaffold"
const story = create({ title: "UI/SessionTurn", mod })
export default { title: "UI/SessionTurn", id: "components-session-turn", component: story.meta.component }
export const Basic = story.Basic

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
import * as mod from "./spinner"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Animated loading indicator for inline or page-level loading states.
Use with \`Button\` or in empty states.
### API
- Accepts standard SVG props (class, style).
### Variants and states
- Single default animation style.
### Behavior
- Animation is CSS-driven via data attributes.
### Accessibility
- Use alongside text or aria-live regions to convey loading state.
### Theming/tokens
- Uses \`data-component="spinner"\` for styling hooks.
`
const story = create({ title: "UI/Spinner", mod })
export default {
title: "UI/Spinner",
id: "components-spinner",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
<mod.Spinner style={{ width: "12px", height: "12px" }} />
<mod.Spinner style={{ width: "20px", height: "20px" }} />
<mod.Spinner style={{ width: "28px", height: "28px" }} />
</div>
),
}

View File

@@ -0,0 +1,54 @@
// @ts-nocheck
import { Accordion } from "./accordion"
import * as mod from "./sticky-accordion-header"
const docs = `### Overview
Sticky accordion header wrapper for persistent section labels.
Use only inside \`Accordion.Item\` with \`Accordion.Trigger\`.
### API
- Accepts standard header props and children.
### Variants and states
- Inherits accordion states.
### Behavior
- Renders inside an Accordion item header.
### Accessibility
- TODO: confirm semantics from Accordion.Header usage.
### Theming/tokens
- Uses \`data-component="sticky-accordion-header"\`.
`
export default {
title: "UI/StickyAccordionHeader",
id: "components-sticky-accordion-header",
component: mod.StickyAccordionHeader,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<Accordion value="first">
<Accordion.Item value="first">
<mod.StickyAccordionHeader>
<Accordion.Trigger>Sticky header</Accordion.Trigger>
</mod.StickyAccordionHeader>
<Accordion.Content>
<div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
</Accordion.Content>
</Accordion.Item>
</Accordion>
),
}

View File

@@ -0,0 +1,68 @@
// @ts-nocheck
import * as mod from "./switch"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Toggle control for binary settings.
Use in settings panels or forms.
### API
- Uses Kobalte Switch props (\`checked\`, \`defaultChecked\`, \`onChange\`).
- Optional: \`hideLabel\`, \`description\`.
- Children render as the label.
### Variants and states
- Checked/unchecked, disabled states.
### Behavior
- Controlled or uncontrolled usage via Kobalte props.
### Accessibility
- TODO: confirm aria attributes from Kobalte.
### Theming/tokens
- Uses \`data-component="switch"\` and slot attributes.
`
const story = create({
title: "UI/Switch",
mod,
args: { defaultChecked: true, children: "Enable notifications" },
})
export default {
title: "UI/Switch",
id: "components-switch",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const States = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Switch defaultChecked>Enabled</mod.Switch>
<mod.Switch>Disabled</mod.Switch>
<mod.Switch disabled>Disabled switch</mod.Switch>
<mod.Switch description="Optional description">With description</mod.Switch>
</div>
),
}
export const HiddenLabel = {
args: {
children: "Hidden label",
hideLabel: true,
defaultChecked: true,
},
}

View File

@@ -0,0 +1,179 @@
// @ts-nocheck
import { IconButton } from "./icon-button"
import { createSignal } from "solid-js"
import * as mod from "./tabs"
const docs = `### Overview
Tabbed navigation for switching between related panels.
Compose \`Tabs.List\` + \`Tabs.Trigger\` + \`Tabs.Content\`.
### API
- Root accepts Kobalte Tabs props (\`value\`, \`defaultValue\`, \`onChange\`).
- \`variant\` sets visual style: normal, alt, pill, settings.
- \`orientation\` supports horizontal or vertical layouts.
- Trigger supports \`closeButton\`, \`hideCloseButton\`, and \`onMiddleClick\`.
### Variants and states
- Normal, alt, pill, settings variants.
- Horizontal and vertical orientations.
### Behavior
- Uses Kobalte Tabs for roving focus and selection management.
### Accessibility
- TODO: confirm keyboard interactions from Kobalte Tabs.
### Theming/tokens
- Uses \`data-component="tabs"\` with variant/orientation data attributes.
`
export default {
title: "UI/Tabs",
id: "components-tabs",
component: mod.Tabs,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
variant: {
control: "select",
options: ["normal", "alt", "pill", "settings"],
},
orientation: {
control: "select",
options: ["horizontal", "vertical"],
},
},
}
export const Basic = {
args: {
variant: "normal",
orientation: "horizontal",
defaultValue: "overview",
},
render: (props) => (
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger value="overview">Overview</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="details">Details</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="activity">Activity</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="overview">Overview content</mod.Tabs.Content>
<mod.Tabs.Content value="details">Details content</mod.Tabs.Content>
<mod.Tabs.Content value="activity">Activity content</mod.Tabs.Content>
</mod.Tabs>
),
}
export const Settings = {
args: {
variant: "settings",
orientation: "horizontal",
defaultValue: "general",
},
render: (props) => (
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger value="general">General</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="appearance">Appearance</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="general">General settings</mod.Tabs.Content>
<mod.Tabs.Content value="appearance">Appearance settings</mod.Tabs.Content>
</mod.Tabs>
),
}
export const Alt = {
args: {
variant: "alt",
orientation: "horizontal",
defaultValue: "first",
},
render: (props) => (
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger value="first">First</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="second">Second</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="first">Alt content</mod.Tabs.Content>
<mod.Tabs.Content value="second">Alt content 2</mod.Tabs.Content>
</mod.Tabs>
),
}
export const Vertical = {
args: {
variant: "pill",
orientation: "vertical",
defaultValue: "alpha",
},
render: (props) => (
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger value="alpha">Alpha</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="beta">Beta</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="alpha">Alpha content</mod.Tabs.Content>
<mod.Tabs.Content value="beta">Beta content</mod.Tabs.Content>
</mod.Tabs>
),
}
export const Closable = {
args: {
variant: "normal",
orientation: "horizontal",
defaultValue: "tab-1",
},
render: (props) => (
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger
value="tab-1"
closeButton={<IconButton icon="close" size="small" variant="ghost" aria-label="Close tab" />}
>
Tab 1
</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="tab-2">Tab 2</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="tab-1">Closable content</mod.Tabs.Content>
<mod.Tabs.Content value="tab-2">Standard content</mod.Tabs.Content>
</mod.Tabs>
),
}
export const MiddleClick = {
args: {
variant: "normal",
orientation: "horizontal",
defaultValue: "tab-1",
},
render: (props) => {
const [message, setMessage] = createSignal("Middle click a tab")
return (
<div style={{ display: "grid", gap: "8px" }}>
<div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{message()}</div>
<mod.Tabs {...props}>
<mod.Tabs.List>
<mod.Tabs.Trigger value="tab-1" onMiddleClick={() => setMessage("Middle clicked tab-1")}>
Tab 1
</mod.Tabs.Trigger>
<mod.Tabs.Trigger value="tab-2" onMiddleClick={() => setMessage("Middle clicked tab-2")}>
Tab 2
</mod.Tabs.Trigger>
</mod.Tabs.List>
<mod.Tabs.Content value="tab-1">Tab 1 content</mod.Tabs.Content>
<mod.Tabs.Content value="tab-2">Tab 2 content</mod.Tabs.Content>
</mod.Tabs>
</div>
)
},
}

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import * as mod from "./tag"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Small label tag for metadata and status chips.
Use alongside headings or lists for quick metadata.
### API
- Optional: \`size\` (normal | large).
- Accepts standard span props.
### Variants and states
- Size variants only.
### Behavior
- Inline element; size controls padding and font size via CSS.
### Accessibility
- Ensure text conveys meaning; avoid color-only distinction.
### Theming/tokens
- Uses \`data-component="tag"\` with size data attributes.
`
const story = create({ title: "UI/Tag", mod, args: { children: "Tag" } })
export default {
title: "UI/Tag",
id: "components-tag",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
argTypes: {
size: {
control: "select",
options: ["normal", "large"],
},
},
}
export const Basic = story.Basic
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "8px", "align-items": "center" }}>
<mod.Tag size="normal">Normal</mod.Tag>
<mod.Tag size="large">Large</mod.Tag>
</div>
),
}

View File

@@ -0,0 +1,111 @@
// @ts-nocheck
import * as mod from "./text-field"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Text input with label, description, and optional copy-to-clipboard action.
Pair with \`Tooltip\` and \`IconButton\` for copy affordance (built in).
### API
- Supports Kobalte TextField props: \`value\`, \`defaultValue\`, \`onChange\`, \`disabled\`, \`readOnly\`.
- Optional: \`label\`, \`description\`, \`error\`, \`variant\`, \`copyable\`, \`multiline\`.
### Variants and states
- Normal and ghost variants.
- Supports multiline textarea.
### Behavior
- When \`copyable\` is true, clicking copies the current value.
### Accessibility
- Label is hidden when \`hideLabel\` is true (sr-only).
### Theming/tokens
- Uses \`data-component="input"\` with slot attributes for styling.
`
const story = create({
title: "UI/TextField",
mod,
args: {
label: "Label",
placeholder: "Type here...",
defaultValue: "Hello",
},
})
export default {
title: "UI/TextField",
id: "components-text-field",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Variants = {
render: () => (
<div style={{ display: "grid", gap: "12px", width: "320px" }}>
<mod.TextField label="Normal" placeholder="Type here..." defaultValue="Value" />
<mod.TextField label="Ghost" variant="ghost" placeholder="Type here..." defaultValue="Value" />
</div>
),
}
export const Multiline = {
args: {
label: "Description",
multiline: true,
defaultValue: "Line one\nLine two",
},
}
export const Copyable = {
args: {
label: "Invite link",
defaultValue: "https://example.com/invite/abc",
copyable: true,
copyKind: "link",
},
}
export const Error = {
args: {
label: "Email",
defaultValue: "invalid@",
error: "Enter a valid email address",
},
}
export const Disabled = {
args: {
label: "Disabled",
defaultValue: "Readonly",
disabled: true,
},
}
export const ReadOnly = {
args: {
label: "Read only",
defaultValue: "Read only value",
readOnly: true,
},
}
export const HiddenLabel = {
args: {
label: "Hidden label",
hideLabel: true,
placeholder: "Hidden label",
},
}

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import * as mod from "./text-shimmer"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Animated shimmer effect for loading text placeholders.
Use for pending states inside buttons or list rows.
### API
- Required: \`text\` string.
- Optional: \`as\`, \`active\`, \`stepMs\`, \`durationMs\`.
### Variants and states
- Active/inactive state via \`active\`.
### Behavior
- Characters animate with staggered delays.
### Accessibility
- Uses \`aria-label\` with the full text.
### Theming/tokens
- Uses \`data-component="text-shimmer"\` and CSS custom properties for timing.
`
const story = create({ title: "UI/TextShimmer", mod, args: { text: "Loading..." } })
export default {
title: "UI/TextShimmer",
id: "components-text-shimmer",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Inactive = {
args: {
text: "Static text",
active: false,
},
}
export const CustomTiming = {
args: {
text: "Custom timing",
stepMs: 80,
durationMs: 1800,
},
}

View File

@@ -0,0 +1,138 @@
// @ts-nocheck
import * as mod from "./toast"
import { Button } from "./button"
const docs = `### Overview
Toast notifications with optional icons, actions, and progress.
Use brief titles/descriptions; limit actions to 1-2.
### API
- Use \`showToast\` or \`showPromiseToast\` to trigger toasts.
- Render \`Toast.Region\` once per page.
- \`Toast\` subcomponents compose the structure.
### Variants and states
- Variants: default, success, error, loading.
- Optional actions and persistent toasts.
### Behavior
- Toasts render in a portal and auto-dismiss unless persistent.
### Accessibility
- TODO: confirm aria-live behavior from Kobalte Toast.
### Theming/tokens
- Uses \`data-component="toast"\` and slot data attributes.
`
export default {
title: "UI/Toast",
id: "components-toast",
component: mod.Toast,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Toast.Region />
<Button
variant="primary"
onClick={() =>
mod.showToast({
title: "Saved",
description: "Your changes are stored.",
variant: "success",
icon: "check",
})
}
>
Show success toast
</Button>
<Button
variant="secondary"
onClick={() =>
mod.showToast({
description: "This action needs attention.",
variant: "error",
icon: "warning",
})
}
>
Show error toast
</Button>
</div>
),
}
export const Actions = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Toast.Region />
<Button
variant="secondary"
onClick={() =>
mod.showToast({
title: "Update available",
description: "Restart to apply the update.",
actions: [
{ label: "Restart", onClick: "dismiss" },
{ label: "Later", onClick: "dismiss" },
],
})
}
>
Show action toast
</Button>
</div>
),
}
export const Promise = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Toast.Region />
<Button
variant="secondary"
onClick={() =>
mod.showPromiseToast(() => new Promise((resolve) => setTimeout(() => resolve(true), 800)), {
loading: "Saving...",
success: () => "Saved",
error: () => "Failed",
})
}
>
Show promise toast
</Button>
</div>
),
}
export const Loading = {
render: () => (
<div style={{ display: "grid", gap: "12px" }}>
<mod.Toast.Region />
<Button
variant="secondary"
onClick={() =>
mod.showToast({
description: "Syncing...",
variant: "loading",
persistent: true,
})
}
>
Show loading toast
</Button>
</div>
),
}

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
import * as mod from "./tooltip"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Tooltip for contextual hints and keybind callouts.
Use for short hints; avoid long descriptions.
### API
- Required: \`value\` (tooltip content).
- Optional: \`inactive\`, \`forceOpen\`, placement props from Kobalte.
### Variants and states
- Supports keybind-style tooltip via \`TooltipKeybind\`.
### Behavior
- Opens on hover/focus; can be forced open.
### Accessibility
- TODO: confirm trigger semantics and focus behavior.
### Theming/tokens
- Uses \`data-component="tooltip"\` and related slots.
`
const story = create({ title: "UI/Tooltip", mod, args: { value: "Tooltip", children: "Hover me" } })
export default {
title: "UI/Tooltip",
id: "components-tooltip",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Keybind = {
render: () => (
<mod.TooltipKeybind title="Search" keybind="Cmd+K">
<span style={{ "text-decoration": "underline" }}>Hover for keybind</span>
</mod.TooltipKeybind>
),
}
export const ForcedOpen = {
args: {
forceOpen: true,
},
}
export const Inactive = {
args: {
inactive: true,
},
}

View File

@@ -0,0 +1,51 @@
// @ts-nocheck
import * as mod from "./typewriter"
import { create } from "../storybook/scaffold"
const docs = `### Overview
Animated typewriter text effect for short inline messages.
Use for short status lines; avoid long paragraphs.
### API
- Optional: \`text\` string; if absent, nothing is rendered.
- Optional: \`as\` to change the rendered element.
### Variants and states
- Single animation style with cursor blink.
### Behavior
- Types one character at a time with randomized delays.
### Accessibility
- TODO: confirm if cursor should be aria-hidden in all contexts.
### Theming/tokens
- Uses \`blinking-cursor\` class for cursor styling.
`
const story = create({ title: "UI/Typewriter", mod, args: { text: "Typewriter text" } })
export default {
title: "UI/Typewriter",
id: "components-typewriter",
component: story.meta.component,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = story.Basic
export const Inline = {
args: {
text: "Inline typewriter",
as: "span",
},
}