From 05d77b7d473d8828671ede90fec1f4da75106738 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:05:04 -0600 Subject: chore: storybook (#15285) Co-authored-by: David Hill --- packages/ui/src/assets/icons/provider/302ai.svg | 7 + packages/ui/src/assets/icons/provider/berget.svg | 3 + .../assets/icons/provider/cloudferro-sherlock.svg | 5 + packages/ui/src/assets/icons/provider/firmware.svg | 18 ++ packages/ui/src/assets/icons/provider/gitlab.svg | 3 + packages/ui/src/assets/icons/provider/jiekou.svg | 4 + packages/ui/src/assets/icons/provider/kilo.svg | 4 + .../icons/provider/kuae-cloud-coding-plan.svg | 3 + packages/ui/src/assets/icons/provider/meganova.svg | 7 + .../icons/provider/minimax-cn-coding-plan.svg | 24 ++ .../assets/icons/provider/minimax-coding-plan.svg | 24 ++ packages/ui/src/assets/icons/provider/moark.svg | 3 + packages/ui/src/assets/icons/provider/nova.svg | 3 + .../ui/src/assets/icons/provider/novita-ai.svg | 10 + .../src/assets/icons/provider/privatemode-ai.svg | 5 + .../ui/src/assets/icons/provider/qihang-ai.svg | 9 + packages/ui/src/assets/icons/provider/qiniu-ai.svg | 7 + packages/ui/src/assets/icons/provider/stackit.svg | 4 + packages/ui/src/assets/icons/provider/stepfun.svg | 24 ++ packages/ui/src/assets/icons/provider/vivgrid.svg | 4 + packages/ui/src/components/accordion.stories.tsx | 149 +++++++++++++ packages/ui/src/components/app-icon.stories.tsx | 69 ++++++ packages/ui/src/components/avatar.stories.tsx | 76 +++++++ packages/ui/src/components/basic-tool.stories.tsx | 133 +++++++++++ packages/ui/src/components/button.stories.tsx | 108 +++++++++ packages/ui/src/components/card.stories.tsx | 90 ++++++++ packages/ui/src/components/checkbox.stories.tsx | 71 ++++++ packages/ui/src/components/code.stories.tsx | 70 ++++++ packages/ui/src/components/collapsible.stories.tsx | 86 +++++++ .../ui/src/components/context-menu.stories.tsx | 113 ++++++++++ packages/ui/src/components/dialog.stories.tsx | 173 +++++++++++++++ .../ui/src/components/diff-changes.stories.tsx | 81 +++++++ packages/ui/src/components/diff-ssr.stories.tsx | 97 ++++++++ packages/ui/src/components/diff.stories.tsx | 96 ++++++++ packages/ui/src/components/dock-prompt.stories.tsx | 62 ++++++ .../ui/src/components/dropdown-menu.stories.tsx | 97 ++++++++ packages/ui/src/components/favicon.stories.tsx | 49 ++++ packages/ui/src/components/file-icon.stories.tsx | 94 ++++++++ packages/ui/src/components/font.stories.tsx | 48 ++++ packages/ui/src/components/hover-card.stories.tsx | 70 ++++++ packages/ui/src/components/icon-button.stories.tsx | 74 ++++++ packages/ui/src/components/icon.stories.tsx | 170 ++++++++++++++ .../ui/src/components/image-preview.stories.tsx | 59 +++++ .../ui/src/components/inline-input.stories.tsx | 50 +++++ packages/ui/src/components/keybind.stories.tsx | 43 ++++ .../ui/src/components/line-comment.stories.tsx | 115 ++++++++++ packages/ui/src/components/list.stories.tsx | 170 ++++++++++++++ packages/ui/src/components/logo.stories.tsx | 57 +++++ packages/ui/src/components/markdown.stories.tsx | 53 +++++ packages/ui/src/components/message-nav.stories.tsx | 7 + .../ui/src/components/message-part.stories.tsx | 7 + packages/ui/src/components/popover.stories.tsx | 87 ++++++++ .../ui/src/components/progress-circle.stories.tsx | 59 +++++ packages/ui/src/components/progress.stories.tsx | 67 ++++++ .../ui/src/components/provider-icon.stories.tsx | 69 ++++++ .../ui/src/components/provider-icons/sprite.svg | 247 +++++++++++++++++++++ packages/ui/src/components/provider-icons/types.ts | 20 ++ packages/ui/src/components/radio-group.stories.tsx | 92 ++++++++ .../ui/src/components/resize-handle.stories.tsx | 156 +++++++++++++ packages/ui/src/components/select.stories.tsx | 113 ++++++++++ .../ui/src/components/session-review.stories.tsx | 7 + .../ui/src/components/session-turn.stories.tsx | 7 + packages/ui/src/components/spinner.stories.tsx | 53 +++++ .../components/sticky-accordion-header.stories.tsx | 54 +++++ packages/ui/src/components/switch.stories.tsx | 68 ++++++ packages/ui/src/components/tabs.stories.tsx | 179 +++++++++++++++ packages/ui/src/components/tag.stories.tsx | 58 +++++ packages/ui/src/components/text-field.stories.tsx | 111 +++++++++ .../ui/src/components/text-shimmer.stories.tsx | 59 +++++ packages/ui/src/components/toast.stories.tsx | 138 ++++++++++++ packages/ui/src/components/tooltip.stories.tsx | 64 ++++++ packages/ui/src/components/typewriter.stories.tsx | 51 +++++ packages/ui/src/storybook/fixtures.ts | 51 +++++ packages/ui/src/storybook/scaffold.tsx | 62 ++++++ 74 files changed, 4680 insertions(+) create mode 100644 packages/ui/src/assets/icons/provider/302ai.svg create mode 100644 packages/ui/src/assets/icons/provider/berget.svg create mode 100644 packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg create mode 100644 packages/ui/src/assets/icons/provider/firmware.svg create mode 100644 packages/ui/src/assets/icons/provider/gitlab.svg create mode 100644 packages/ui/src/assets/icons/provider/jiekou.svg create mode 100644 packages/ui/src/assets/icons/provider/kilo.svg create mode 100644 packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg create mode 100644 packages/ui/src/assets/icons/provider/meganova.svg create mode 100644 packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg create mode 100644 packages/ui/src/assets/icons/provider/minimax-coding-plan.svg create mode 100644 packages/ui/src/assets/icons/provider/moark.svg create mode 100644 packages/ui/src/assets/icons/provider/nova.svg create mode 100644 packages/ui/src/assets/icons/provider/novita-ai.svg create mode 100644 packages/ui/src/assets/icons/provider/privatemode-ai.svg create mode 100644 packages/ui/src/assets/icons/provider/qihang-ai.svg create mode 100644 packages/ui/src/assets/icons/provider/qiniu-ai.svg create mode 100644 packages/ui/src/assets/icons/provider/stackit.svg create mode 100644 packages/ui/src/assets/icons/provider/stepfun.svg create mode 100644 packages/ui/src/assets/icons/provider/vivgrid.svg create mode 100644 packages/ui/src/components/accordion.stories.tsx create mode 100644 packages/ui/src/components/app-icon.stories.tsx create mode 100644 packages/ui/src/components/avatar.stories.tsx create mode 100644 packages/ui/src/components/basic-tool.stories.tsx create mode 100644 packages/ui/src/components/button.stories.tsx create mode 100644 packages/ui/src/components/card.stories.tsx create mode 100644 packages/ui/src/components/checkbox.stories.tsx create mode 100644 packages/ui/src/components/code.stories.tsx create mode 100644 packages/ui/src/components/collapsible.stories.tsx create mode 100644 packages/ui/src/components/context-menu.stories.tsx create mode 100644 packages/ui/src/components/dialog.stories.tsx create mode 100644 packages/ui/src/components/diff-changes.stories.tsx create mode 100644 packages/ui/src/components/diff-ssr.stories.tsx create mode 100644 packages/ui/src/components/diff.stories.tsx create mode 100644 packages/ui/src/components/dock-prompt.stories.tsx create mode 100644 packages/ui/src/components/dropdown-menu.stories.tsx create mode 100644 packages/ui/src/components/favicon.stories.tsx create mode 100644 packages/ui/src/components/file-icon.stories.tsx create mode 100644 packages/ui/src/components/font.stories.tsx create mode 100644 packages/ui/src/components/hover-card.stories.tsx create mode 100644 packages/ui/src/components/icon-button.stories.tsx create mode 100644 packages/ui/src/components/icon.stories.tsx create mode 100644 packages/ui/src/components/image-preview.stories.tsx create mode 100644 packages/ui/src/components/inline-input.stories.tsx create mode 100644 packages/ui/src/components/keybind.stories.tsx create mode 100644 packages/ui/src/components/line-comment.stories.tsx create mode 100644 packages/ui/src/components/list.stories.tsx create mode 100644 packages/ui/src/components/logo.stories.tsx create mode 100644 packages/ui/src/components/markdown.stories.tsx create mode 100644 packages/ui/src/components/message-nav.stories.tsx create mode 100644 packages/ui/src/components/message-part.stories.tsx create mode 100644 packages/ui/src/components/popover.stories.tsx create mode 100644 packages/ui/src/components/progress-circle.stories.tsx create mode 100644 packages/ui/src/components/progress.stories.tsx create mode 100644 packages/ui/src/components/provider-icon.stories.tsx create mode 100644 packages/ui/src/components/radio-group.stories.tsx create mode 100644 packages/ui/src/components/resize-handle.stories.tsx create mode 100644 packages/ui/src/components/select.stories.tsx create mode 100644 packages/ui/src/components/session-review.stories.tsx create mode 100644 packages/ui/src/components/session-turn.stories.tsx create mode 100644 packages/ui/src/components/spinner.stories.tsx create mode 100644 packages/ui/src/components/sticky-accordion-header.stories.tsx create mode 100644 packages/ui/src/components/switch.stories.tsx create mode 100644 packages/ui/src/components/tabs.stories.tsx create mode 100644 packages/ui/src/components/tag.stories.tsx create mode 100644 packages/ui/src/components/text-field.stories.tsx create mode 100644 packages/ui/src/components/text-shimmer.stories.tsx create mode 100644 packages/ui/src/components/toast.stories.tsx create mode 100644 packages/ui/src/components/tooltip.stories.tsx create mode 100644 packages/ui/src/components/typewriter.stories.tsx create mode 100644 packages/ui/src/storybook/fixtures.ts create mode 100644 packages/ui/src/storybook/scaffold.tsx (limited to 'packages/ui/src') diff --git a/packages/ui/src/assets/icons/provider/302ai.svg b/packages/ui/src/assets/icons/provider/302ai.svg new file mode 100644 index 000000000..46f2e4315 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/302ai.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/berget.svg b/packages/ui/src/assets/icons/provider/berget.svg new file mode 100644 index 000000000..831547a59 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/berget.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg b/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg new file mode 100644 index 000000000..6f09a794e --- /dev/null +++ b/packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/firmware.svg b/packages/ui/src/assets/icons/provider/firmware.svg new file mode 100644 index 000000000..baa524ba2 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/firmware.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/packages/ui/src/assets/icons/provider/gitlab.svg b/packages/ui/src/assets/icons/provider/gitlab.svg new file mode 100644 index 000000000..eef04ace2 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/gitlab.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/jiekou.svg b/packages/ui/src/assets/icons/provider/jiekou.svg new file mode 100644 index 000000000..7fe6378e5 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/jiekou.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/provider/kilo.svg b/packages/ui/src/assets/icons/provider/kilo.svg new file mode 100644 index 000000000..0a761347a --- /dev/null +++ b/packages/ui/src/assets/icons/provider/kilo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg b/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg new file mode 100644 index 000000000..3d0d0c455 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/meganova.svg b/packages/ui/src/assets/icons/provider/meganova.svg new file mode 100644 index 000000000..ab294f1e1 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/meganova.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg b/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg new file mode 100644 index 000000000..086e9aa1f --- /dev/null +++ b/packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg b/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg new file mode 100644 index 000000000..086e9aa1f --- /dev/null +++ b/packages/ui/src/assets/icons/provider/minimax-coding-plan.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/moark.svg b/packages/ui/src/assets/icons/provider/moark.svg new file mode 100644 index 000000000..dc84a9191 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/moark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/nova.svg b/packages/ui/src/assets/icons/provider/nova.svg new file mode 100644 index 000000000..9fcae228c --- /dev/null +++ b/packages/ui/src/assets/icons/provider/nova.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/provider/novita-ai.svg b/packages/ui/src/assets/icons/provider/novita-ai.svg new file mode 100644 index 000000000..ac537b8dd --- /dev/null +++ b/packages/ui/src/assets/icons/provider/novita-ai.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/privatemode-ai.svg b/packages/ui/src/assets/icons/provider/privatemode-ai.svg new file mode 100644 index 000000000..edb5a6d76 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/privatemode-ai.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/qihang-ai.svg b/packages/ui/src/assets/icons/provider/qihang-ai.svg new file mode 100644 index 000000000..3b356637a --- /dev/null +++ b/packages/ui/src/assets/icons/provider/qihang-ai.svg @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/provider/qiniu-ai.svg b/packages/ui/src/assets/icons/provider/qiniu-ai.svg new file mode 100644 index 000000000..858560f9f --- /dev/null +++ b/packages/ui/src/assets/icons/provider/qiniu-ai.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ui/src/assets/icons/provider/stackit.svg b/packages/ui/src/assets/icons/provider/stackit.svg new file mode 100644 index 000000000..0d78b781a --- /dev/null +++ b/packages/ui/src/assets/icons/provider/stackit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/assets/icons/provider/stepfun.svg b/packages/ui/src/assets/icons/provider/stepfun.svg new file mode 100644 index 000000000..086e9aa1f --- /dev/null +++ b/packages/ui/src/assets/icons/provider/stepfun.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/ui/src/assets/icons/provider/vivgrid.svg b/packages/ui/src/assets/icons/provider/vivgrid.svg new file mode 100644 index 000000000..928fa3ff1 --- /dev/null +++ b/packages/ui/src/assets/icons/provider/vivgrid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/src/components/accordion.stories.tsx b/packages/ui/src/components/accordion.stories.tsx new file mode 100644 index 000000000..c53b6d3da --- /dev/null +++ b/packages/ui/src/components/accordion.stories.tsx @@ -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 ( +
+ + + + First + + +
Accordion content.
+
+
+ + + Second + + +
More content.
+
+
+
+
+ ) + }, +} + +export const Multiple = { + args: { + collapsible: true, + multiple: true, + value: ["first", "second"], + }, + render: (props) => ( + + + + First + + +
Accordion content.
+
+
+ + + Second + + +
More content.
+
+
+
+ ), +} + +export const NonCollapsible = { + args: { + collapsible: false, + multiple: false, + value: "first", + }, + render: (props) => ( + + + + First + + +
Accordion content.
+
+
+
+ ), +} diff --git a/packages/ui/src/components/app-icon.stories.tsx b/packages/ui/src/components/app-icon.stories.tsx new file mode 100644 index 000000000..24460b6da --- /dev/null +++ b/packages/ui/src/components/app-icon.stories.tsx @@ -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: () => ( +
+ {iconNames.map((id) => ( +
+ +
{id}
+
+ ))} +
+ ), +} diff --git a/packages/ui/src/components/avatar.stories.tsx b/packages/ui/src/components/avatar.stories.tsx new file mode 100644 index 000000000..044224ae8 --- /dev/null +++ b/packages/ui/src/components/avatar.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} + +export const CustomColors = { + args: { + fallback: "C", + background: "#1f2a44", + foreground: "#f2f5ff", + }, +} diff --git a/packages/ui/src/components/basic-tool.stories.tsx b/packages/ui/src/components/basic-tool.stories.tsx new file mode 100644 index 000000000..9d9d97acf --- /dev/null +++ b/packages/ui/src/components/basic-tool.stories.tsx @@ -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 ( +
+
{message()}
+ setMessage("Subtitle clicked")} + > + Subtitle action details + +
+ ) + }, +} diff --git a/packages/ui/src/components/button.stories.tsx b/packages/ui/src/components/button.stories.tsx new file mode 100644 index 000000000..24fad5c8a --- /dev/null +++ b/packages/ui/src/components/button.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/components/card.stories.tsx b/packages/ui/src/components/card.stories.tsx new file mode 100644 index 000000000..befb2d34f --- /dev/null +++ b/packages/ui/src/components/card.stories.tsx @@ -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 title
+
Small supporting text.
+
+ +
+
+ ) + }, +} + +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", + }, +} diff --git a/packages/ui/src/components/checkbox.stories.tsx b/packages/ui/src/components/checkbox.stories.tsx new file mode 100644 index 000000000..ceb09f103 --- /dev/null +++ b/packages/ui/src/components/checkbox.stories.tsx @@ -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: () => ( +
+ Checked + Unchecked + Disabled + With description +
+ ), +} + +export const CustomIcon = { + render: () => ( + } defaultChecked> + Custom icon + + ), +} + +export const HiddenLabel = { + args: { + children: "Hidden label", + hideLabel: true, + }, +} diff --git a/packages/ui/src/components/code.stories.tsx b/packages/ui/src/components/code.stories.tsx new file mode 100644 index 000000000..992fa6302 --- /dev/null +++ b/packages/ui/src/components/code.stories.tsx @@ -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 }, + ], + }, +} diff --git a/packages/ui/src/components/collapsible.stories.tsx b/packages/ui/src/components/collapsible.stories.tsx new file mode 100644 index 000000000..67883b229 --- /dev/null +++ b/packages/ui/src/components/collapsible.stories.tsx @@ -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) => ( + + +
+ Details + +
+
+ +
Optional details sit here.
+
+
+ ), +} + +export const Ghost = { + args: { + variant: "ghost", + defaultOpen: false, + }, + render: (props) => ( + + +
+ Ghost trigger + +
+
+ +
Ghost content.
+
+
+ ), +} diff --git a/packages/ui/src/components/context-menu.stories.tsx b/packages/ui/src/components/context-menu.stories.tsx new file mode 100644 index 000000000..bee5a5596 --- /dev/null +++ b/packages/ui/src/components/context-menu.stories.tsx @@ -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: () => ( + + +
+ Right click (or open) here +
+
+ + + + Actions + + Copy + + + Paste + + + + + More + + + Duplicate + + + Move + + + + + +
+ ), +} + +export const CheckboxRadio = { + render: () => ( + + +
+ Right click (or open) here +
+
+ + + Show line numbers + Wrap lines + + + Compact + Comfortable + + + +
+ ), +} diff --git a/packages/ui/src/components/dialog.stories.tsx b/packages/ui/src/components/dialog.stories.tsx new file mode 100644 index 000000000..60cd0a1c1 --- /dev/null +++ b/packages/ui/src/components/dialog.stories.tsx @@ -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(() => ( + + Dialog body content. + + )) + + onMount(open) + + return ( + + ) + }, +} + +export const Sizes = { + render: () => { + const dialog = useDialog() + return ( +
+ + + +
+ ) + }, +} + +export const Transition = { + render: () => { + const dialog = useDialog() + return ( + + ) + }, +} + +export const CustomAction = { + render: () => { + const dialog = useDialog() + return ( + } + > + Dialog body content. + + )) + } + > + Open action dialog + + ) + }, +} + +export const Fit = { + render: () => { + const dialog = useDialog() + return ( + + ) + }, +} diff --git a/packages/ui/src/components/diff-changes.stories.tsx b/packages/ui/src/components/diff-changes.stories.tsx new file mode 100644 index 000000000..fe0ba6eb4 --- /dev/null +++ b/packages/ui/src/components/diff-changes.stories.tsx @@ -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 }, + }, +} diff --git a/packages/ui/src/components/diff-ssr.stories.tsx b/packages/ui/src/components/diff-ssr.stories.tsx new file mode 100644 index 000000000..d1adce280 --- /dev/null +++ b/packages/ui/src/components/diff-ssr.stories.tsx @@ -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 ( + + Loading pre-rendered diff...}> + {(preloaded) => ( +
+ +
+ )} +
+
+ ) + }, +} + +export const Split = { + render: () => { + const [data] = createResource(loadSplit) + return ( + + Loading pre-rendered diff...}> + {(preloaded) => ( +
+ +
+ )} +
+
+ ) + }, +} diff --git a/packages/ui/src/components/diff.stories.tsx b/packages/ui/src/components/diff.stories.tsx new file mode 100644 index 000000000..03bf4a0e0 --- /dev/null +++ b/packages/ui/src/components/diff.stories.tsx @@ -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 }, + ], + }, +} diff --git a/packages/ui/src/components/dock-prompt.stories.tsx b/packages/ui/src/components/dock-prompt.stories.tsx new file mode 100644 index 000000000..de017a1ba --- /dev/null +++ b/packages/ui/src/components/dock-prompt.stories.tsx @@ -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", + }, +} diff --git a/packages/ui/src/components/dropdown-menu.stories.tsx b/packages/ui/src/components/dropdown-menu.stories.tsx new file mode 100644 index 000000000..7a2d644fa --- /dev/null +++ b/packages/ui/src/components/dropdown-menu.stories.tsx @@ -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: () => ( + + + Open menu + + + + + Actions + + New file + + + Rename + Shift+R + + + + + More options + + + Duplicate + + + Move + + + + + + + ), +} + +export const CheckboxRadio = { + render: () => ( + + + Open menu + + + + Show line numbers + Wrap lines + + + Compact + Comfortable + + + + + ), +} diff --git a/packages/ui/src/components/favicon.stories.tsx b/packages/ui/src/components/favicon.stories.tsx new file mode 100644 index 000000000..a693c0f46 --- /dev/null +++ b/packages/ui/src/components/favicon.stories.tsx @@ -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: () => ( +
+ +
+ Head tags are injected for favicon and app icons. +
+
+ ), +} diff --git a/packages/ui/src/components/file-icon.stories.tsx b/packages/ui/src/components/file-icon.stories.tsx new file mode 100644 index 000000000..937328502 --- /dev/null +++ b/packages/ui/src/components/file-icon.stories.tsx @@ -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 ( +
+ {items.map((node) => ( +
+ +
{node.path}
+
+ ))} +
+ ) + }, +} diff --git a/packages/ui/src/components/font.stories.tsx b/packages/ui/src/components/font.stories.tsx new file mode 100644 index 000000000..153a2c8dc --- /dev/null +++ b/packages/ui/src/components/font.stories.tsx @@ -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: () => ( +
+ +
OpenCode Sans Sample
+
OpenCode Mono Sample
+
+ ), +} diff --git a/packages/ui/src/components/hover-card.stories.tsx b/packages/ui/src/components/hover-card.stories.tsx new file mode 100644 index 000000000..3f5cf1028 --- /dev/null +++ b/packages/ui/src/components/hover-card.stories.tsx @@ -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: () => ( + Hover me}> +
+
Preview
+
Short supporting text.
+
+
+ ), +} + +export const InlineMount = { + render: () => { + const [mount, setMount] = createSignal(undefined) + return ( +
+ Hover me} + > +
+
Mounted inside
+
Uses custom mount node.
+
+
+
+ ) + }, +} diff --git a/packages/ui/src/components/icon-button.stories.tsx b/packages/ui/src/components/icon-button.stories.tsx new file mode 100644 index 000000000..9782759f8 --- /dev/null +++ b/packages/ui/src/components/icon-button.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} + +export const Variants = { + render: () => ( +
+ + + +
+ ), +} + +export const IconSizeOverride = { + render: () => ( +
+ + +
+ ), +} diff --git a/packages/ui/src/components/icon.stories.tsx b/packages/ui/src/components/icon.stories.tsx new file mode 100644 index 000000000..1986d7477 --- /dev/null +++ b/packages/ui/src/components/icon.stories.tsx @@ -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: () => ( +
+ + + + +
+ ), +} + +export const Gallery = { + render: () => ( +
+ {names.map((name) => ( +
+ +
{name}
+
+ ))} +
+ ), +} diff --git a/packages/ui/src/components/image-preview.stories.tsx b/packages/ui/src/components/image-preview.stories.tsx new file mode 100644 index 000000000..f0a00c782 --- /dev/null +++ b/packages/ui/src/components/image-preview.stories.tsx @@ -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(() => ) + + onMount(open) + + return ( + + ) + }, +} diff --git a/packages/ui/src/components/inline-input.stories.tsx b/packages/ui/src/components/inline-input.stories.tsx new file mode 100644 index 000000000..e364c8963 --- /dev/null +++ b/packages/ui/src/components/inline-input.stories.tsx @@ -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", + }, +} diff --git a/packages/ui/src/components/keybind.stories.tsx b/packages/ui/src/components/keybind.stories.tsx new file mode 100644 index 000000000..a458a53a7 --- /dev/null +++ b/packages/ui/src/components/keybind.stories.tsx @@ -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 diff --git a/packages/ui/src/components/line-comment.stories.tsx b/packages/ui/src/components/line-comment.stories.tsx new file mode 100644 index 000000000..c48674e3c --- /dev/null +++ b/packages/ui/src/components/line-comment.stories.tsx @@ -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: () => ( +
+
12 | const total = sum(values)
+
13 | return total / values.length
+ +
+ ), +} + +export const Editor = { + render: () => { + const [value, setValue] = createSignal("Add context for this change.") + return ( +
+
40 | if (values.length === 0) return 0
+ setValue("")} + onSubmit={(next) => setValue(next)} + /> +
+ ) + }, +} + +export const AnchorOnly = { + render: () => ( +
+
20 | const ready = true
+ +
Anchor content
+
+
+ ), +} diff --git a/packages/ui/src/components/list.stories.tsx b/packages/ui/src/components/list.stories.tsx new file mode 100644 index 000000000..280f323c0 --- /dev/null +++ b/packages/ui/src/components/list.stories.tsx @@ -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 ( + item.id} groupBy={(item) => item.group} search={true}> + {(item) => item.title} + + ) + }, +} + +export const Empty = { + render: () => ( + item} search={true}> + {(item) => item} + + ), +} + +export const WithAdd = { + render: () => ( + item} + search={true} + add={{ + render: () => ( + + ), + }} + > + {(item) => item} + + ), +} + +export const Divider = { + render: () => ( + item} divider={true}> + {(item) => item} + + ), +} + +export const ActiveIcon = { + render: () => ( + item} activeIcon="chevron-right"> + {(item) => item} + + ), +} + +export const NoSearch = { + render: () => ( + item} search={false}> + {(item) => item} + + ), +} + +export const SearchOptions = { + render: () => ( + item} + search={{ + placeholder: "Filter...", + hideIcon: true, + action: , + }} + > + {(item) => item} + + ), +} + +export const ItemWrapper = { + render: () => ( + item} + itemWrapper={(item, node) => ( +
{node}
+ )} + > + {(item) => item} +
+ ), +} + +export const GroupHeader = { + render: () => { + const items = [ + { id: "a1", title: "Alpha", group: "Group A" }, + { id: "b1", title: "Beta", group: "Group B" }, + ] + return ( + item.id} + groupBy={(item) => item.group} + groupHeader={(group) => {group.category}} + > + {(item) => item.title} + + ) + }, +} diff --git a/packages/ui/src/components/logo.stories.tsx b/packages/ui/src/components/logo.stories.tsx new file mode 100644 index 000000000..3f5dd9cef --- /dev/null +++ b/packages/ui/src/components/logo.stories.tsx @@ -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: () => ( +
+
+
Mark
+ +
+
+
Splash
+ +
+
+
Logo
+ +
+
+ ), +} diff --git a/packages/ui/src/components/markdown.stories.tsx b/packages/ui/src/components/markdown.stories.tsx new file mode 100644 index 000000000..cae429486 --- /dev/null +++ b/packages/ui/src/components/markdown.stories.tsx @@ -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 diff --git a/packages/ui/src/components/message-nav.stories.tsx b/packages/ui/src/components/message-nav.stories.tsx new file mode 100644 index 000000000..7ce31a7be --- /dev/null +++ b/packages/ui/src/components/message-nav.stories.tsx @@ -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 diff --git a/packages/ui/src/components/message-part.stories.tsx b/packages/ui/src/components/message-part.stories.tsx new file mode 100644 index 000000000..28489dc7b --- /dev/null +++ b/packages/ui/src/components/message-part.stories.tsx @@ -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 diff --git a/packages/ui/src/components/popover.stories.tsx b/packages/ui/src/components/popover.stories.tsx new file mode 100644 index 000000000..e5117b451 --- /dev/null +++ b/packages/ui/src/components/popover.stories.tsx @@ -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 ( + + Controlled content + + ) + }, +} diff --git a/packages/ui/src/components/progress-circle.stories.tsx b/packages/ui/src/components/progress-circle.stories.tsx new file mode 100644 index 000000000..5bc23c310 --- /dev/null +++ b/packages/ui/src/components/progress-circle.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/components/progress.stories.tsx b/packages/ui/src/components/progress.stories.tsx new file mode 100644 index 000000000..2ee322343 --- /dev/null +++ b/packages/ui/src/components/progress.stories.tsx @@ -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: () => Loading, +} diff --git a/packages/ui/src/components/provider-icon.stories.tsx b/packages/ui/src/components/provider-icon.stories.tsx new file mode 100644 index 000000000..e7fc39967 --- /dev/null +++ b/packages/ui/src/components/provider-icon.stories.tsx @@ -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: () => ( +
+ {iconNames.map((id) => ( +
+ +
{id}
+
+ ))} +
+ ), +} diff --git a/packages/ui/src/components/provider-icons/sprite.svg b/packages/ui/src/components/provider-icons/sprite.svg index 88406fa8c..29d22461d 100644 --- a/packages/ui/src/components/provider-icons/sprite.svg +++ b/packages/ui/src/components/provider-icons/sprite.svg @@ -87,6 +87,18 @@ fill="currentColor" > + + + + @@ -175,6 +187,36 @@ fill="currentColor" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/provider-icons/types.ts b/packages/ui/src/components/provider-icons/types.ts index 89fbc0625..bafa7ffaf 100644 --- a/packages/ui/src/components/provider-icons/types.ts +++ b/packages/ui/src/components/provider-icons/types.ts @@ -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] diff --git a/packages/ui/src/components/radio-group.stories.tsx b/packages/ui/src/components/radio-group.stories.tsx new file mode 100644 index 000000000..4900ead84 --- /dev/null +++ b/packages/ui/src/components/radio-group.stories.tsx @@ -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: () => ( +
+ + +
+ ), +} + +export const Filled = { + args: { + fill: true, + pad: "none", + }, +} + +export const CustomLabels = { + render: () => ( + (value === "list" ? "List view" : "Grid view")} + /> + ), +} diff --git a/packages/ui/src/components/resize-handle.stories.tsx b/packages/ui/src/components/resize-handle.stories.tsx new file mode 100644 index 000000000..474cf71e2 --- /dev/null +++ b/packages/ui/src/components/resize-handle.stories.tsx @@ -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 ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} + +export const Vertical = { + render: () => { + const [size, setSize] = createSignal(180) + return ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} + +export const Collapse = { + render: () => { + const [size, setSize] = createSignal(200) + const [collapsed, setCollapsed] = createSignal(false) + return ( +
+
+ {collapsed() ? "Collapsed" : `Size: ${size()}px`} +
+
+ { + setCollapsed(false) + setSize(next) + }} + onCollapse={() => setCollapsed(true)} + style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)" + /> +
+ ) + }, +} + +export const EdgeStart = { + render: () => { + const [size, setSize] = createSignal(240) + return ( +
+
Size: {size()}px
+
+ +
+ ) + }, +} diff --git a/packages/ui/src/components/select.stories.tsx b/packages/ui/src/components/select.stories.tsx new file mode 100644 index 000000000..1ee00ab85 --- /dev/null +++ b/packages/ui/src/components/select.stories.tsx @@ -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 ( + item.id} + label={(item) => item.label} + groupBy={(item) => item.group} + placeholder="Choose..." + variant="secondary" + /> + ) + }, +} + +export const SettingsTrigger = { + args: { + triggerVariant: "settings", + }, +} + +export const CustomRender = { + render: () => ( + + {(item) => {item}} + + ), +} + +export const CustomTriggerStyle = { + args: { + triggerStyle: { "min-width": "180px", "justify-content": "space-between" }, + }, +} + +export const Disabled = { + args: { + disabled: true, + }, +} diff --git a/packages/ui/src/components/session-review.stories.tsx b/packages/ui/src/components/session-review.stories.tsx new file mode 100644 index 000000000..7ab1eb203 --- /dev/null +++ b/packages/ui/src/components/session-review.stories.tsx @@ -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 diff --git a/packages/ui/src/components/session-turn.stories.tsx b/packages/ui/src/components/session-turn.stories.tsx new file mode 100644 index 000000000..927402c8d --- /dev/null +++ b/packages/ui/src/components/session-turn.stories.tsx @@ -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 diff --git a/packages/ui/src/components/spinner.stories.tsx b/packages/ui/src/components/spinner.stories.tsx new file mode 100644 index 000000000..be6106d14 --- /dev/null +++ b/packages/ui/src/components/spinner.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} diff --git a/packages/ui/src/components/sticky-accordion-header.stories.tsx b/packages/ui/src/components/sticky-accordion-header.stories.tsx new file mode 100644 index 000000000..3f0335626 --- /dev/null +++ b/packages/ui/src/components/sticky-accordion-header.stories.tsx @@ -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: () => ( + + + + Sticky header + + +
Accordion content.
+
+
+
+ ), +} diff --git a/packages/ui/src/components/switch.stories.tsx b/packages/ui/src/components/switch.stories.tsx new file mode 100644 index 000000000..540e91e36 --- /dev/null +++ b/packages/ui/src/components/switch.stories.tsx @@ -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: () => ( +
+ Enabled + Disabled + Disabled switch + With description +
+ ), +} + +export const HiddenLabel = { + args: { + children: "Hidden label", + hideLabel: true, + defaultChecked: true, + }, +} diff --git a/packages/ui/src/components/tabs.stories.tsx b/packages/ui/src/components/tabs.stories.tsx new file mode 100644 index 000000000..8e09764dc --- /dev/null +++ b/packages/ui/src/components/tabs.stories.tsx @@ -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) => ( + + + Overview + Details + Activity + + Overview content + Details content + Activity content + + ), +} + +export const Settings = { + args: { + variant: "settings", + orientation: "horizontal", + defaultValue: "general", + }, + render: (props) => ( + + + General + Appearance + + General settings + Appearance settings + + ), +} + +export const Alt = { + args: { + variant: "alt", + orientation: "horizontal", + defaultValue: "first", + }, + render: (props) => ( + + + First + Second + + Alt content + Alt content 2 + + ), +} + +export const Vertical = { + args: { + variant: "pill", + orientation: "vertical", + defaultValue: "alpha", + }, + render: (props) => ( + + + Alpha + Beta + + Alpha content + Beta content + + ), +} + +export const Closable = { + args: { + variant: "normal", + orientation: "horizontal", + defaultValue: "tab-1", + }, + render: (props) => ( + + + } + > + Tab 1 + + Tab 2 + + Closable content + Standard content + + ), +} + +export const MiddleClick = { + args: { + variant: "normal", + orientation: "horizontal", + defaultValue: "tab-1", + }, + render: (props) => { + const [message, setMessage] = createSignal("Middle click a tab") + return ( +
+
{message()}
+ + + setMessage("Middle clicked tab-1")}> + Tab 1 + + setMessage("Middle clicked tab-2")}> + Tab 2 + + + Tab 1 content + Tab 2 content + +
+ ) + }, +} diff --git a/packages/ui/src/components/tag.stories.tsx b/packages/ui/src/components/tag.stories.tsx new file mode 100644 index 000000000..73ae880ba --- /dev/null +++ b/packages/ui/src/components/tag.stories.tsx @@ -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: () => ( +
+ Normal + Large +
+ ), +} diff --git a/packages/ui/src/components/text-field.stories.tsx b/packages/ui/src/components/text-field.stories.tsx new file mode 100644 index 000000000..73f900660 --- /dev/null +++ b/packages/ui/src/components/text-field.stories.tsx @@ -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: () => ( +
+ + +
+ ), +} + +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", + }, +} diff --git a/packages/ui/src/components/text-shimmer.stories.tsx b/packages/ui/src/components/text-shimmer.stories.tsx new file mode 100644 index 000000000..4b6de34c2 --- /dev/null +++ b/packages/ui/src/components/text-shimmer.stories.tsx @@ -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, + }, +} diff --git a/packages/ui/src/components/toast.stories.tsx b/packages/ui/src/components/toast.stories.tsx new file mode 100644 index 000000000..ef9cbb68e --- /dev/null +++ b/packages/ui/src/components/toast.stories.tsx @@ -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: () => ( +
+ + + +
+ ), +} + +export const Actions = { + render: () => ( +
+ + +
+ ), +} + +export const Promise = { + render: () => ( +
+ + +
+ ), +} + +export const Loading = { + render: () => ( +
+ + +
+ ), +} diff --git a/packages/ui/src/components/tooltip.stories.tsx b/packages/ui/src/components/tooltip.stories.tsx new file mode 100644 index 000000000..efe11d92e --- /dev/null +++ b/packages/ui/src/components/tooltip.stories.tsx @@ -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: () => ( + + Hover for keybind + + ), +} + +export const ForcedOpen = { + args: { + forceOpen: true, + }, +} + +export const Inactive = { + args: { + inactive: true, + }, +} diff --git a/packages/ui/src/components/typewriter.stories.tsx b/packages/ui/src/components/typewriter.stories.tsx new file mode 100644 index 000000000..880ca7489 --- /dev/null +++ b/packages/ui/src/components/typewriter.stories.tsx @@ -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", + }, +} diff --git a/packages/ui/src/storybook/fixtures.ts b/packages/ui/src/storybook/fixtures.ts new file mode 100644 index 000000000..59d412970 --- /dev/null +++ b/packages/ui/src/storybook/fixtures.ts @@ -0,0 +1,51 @@ +export const diff = { + before: { + name: "src/greet.ts", + contents: `export function greet(name: string) { + return \`Hello, \${name}!\` +} +`, + }, + after: { + name: "src/greet.ts", + contents: `export function greet(name: string, excited = false) { + const message = \`Hello, \${name}!\` + return excited ? \`\${message}!!\` : message +} +`, + }, +} + +export const code = { + name: "src/calc.ts", + contents: `export function sum(values: number[]) { + return values.reduce((total, value) => total + value, 0) +} + +export function average(values: number[]) { + if (values.length === 0) return 0 + return sum(values) / values.length +} +`, +} + +export const markdown = [ + "# Markdown", + "", + "Use **Markdown** for rich text.", + "", + "## Highlights", + "- Headings, lists, and code blocks", + "- Inline `code` and links", + "", + "```ts", + "export const value = 42", + "```", + "", + "More at https://example.com/docs", +].join("\n") + +export const changes = { + additions: 18, + deletions: 6, +} diff --git a/packages/ui/src/storybook/scaffold.tsx b/packages/ui/src/storybook/scaffold.tsx new file mode 100644 index 000000000..2512aa09b --- /dev/null +++ b/packages/ui/src/storybook/scaffold.tsx @@ -0,0 +1,62 @@ +import { ErrorBoundary, type ValidComponent } from "solid-js" +import { Dynamic } from "solid-js/web" + +function fn(value: unknown): value is (...args: never[]) => unknown { + return typeof value === "function" +} + +function pick(mod: Record, name?: string) { + if (name && fn(mod[name])) return mod[name] + if (fn(mod.default)) return mod.default + + const preferred = Object.keys(mod) + .filter((k) => k[0] && k[0] === k[0].toUpperCase()) + .find((k) => fn(mod[k])) + if (preferred) return mod[preferred] + + const first = Object.keys(mod).find((k) => fn(mod[k])) + if (first) return mod[first] + + return () => { + return ( +
+
Missing component export.
+
Exports: {Object.keys(mod).join(", ") || "(none)"}
+
+ ) + } +} + +export function create(input: { + title: string + mod: Record + name?: string + args?: Record +}) { + const component = pick(input.mod, input.name) as unknown as ValidComponent + + return { + meta: { + title: input.title, + component, + }, + Basic: { + args: input.args ?? {}, + render: (args: Record) => { + return ( + { + return ( +
+                  {String(err)}
+                
+ ) + }} + > + +
+ ) + }, + }, + } +} -- cgit v1.2.3