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/storybook/.gitignore | 3 + packages/storybook/.storybook/main.ts | 37 +++ packages/storybook/.storybook/manager.ts | 11 + packages/storybook/.storybook/preview.tsx | 106 +++++++ packages/storybook/.storybook/theme-tool.ts | 21 ++ packages/storybook/debug-storybook.log | 307 +++++++++++++++++++++ packages/storybook/package.json | 28 ++ packages/storybook/tsconfig.json | 16 ++ packages/ui/package.json | 1 + 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 +++++ packages/ui/tsconfig.json | 3 +- 84 files changed, 5212 insertions(+), 1 deletion(-) create mode 100644 packages/storybook/.gitignore create mode 100644 packages/storybook/.storybook/main.ts create mode 100644 packages/storybook/.storybook/manager.ts create mode 100644 packages/storybook/.storybook/preview.tsx create mode 100644 packages/storybook/.storybook/theme-tool.ts create mode 100644 packages/storybook/debug-storybook.log create mode 100644 packages/storybook/package.json create mode 100644 packages/storybook/tsconfig.json 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') diff --git a/packages/storybook/.gitignore b/packages/storybook/.gitignore new file mode 100644 index 000000000..b122737ad --- /dev/null +++ b/packages/storybook/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +storybook-static/ +.storybook-cache/ diff --git a/packages/storybook/.storybook/main.ts b/packages/storybook/.storybook/main.ts new file mode 100644 index 000000000..6c850858a --- /dev/null +++ b/packages/storybook/.storybook/main.ts @@ -0,0 +1,37 @@ +import { defineMain } from "storybook-solidjs-vite" +import path from "node:path" +import { fileURLToPath } from "node:url" + +const here = path.dirname(fileURLToPath(import.meta.url)) +const ui = path.resolve(here, "../../ui") + +export default defineMain({ + framework: { + name: "storybook-solidjs-vite", + options: {}, + }, + addons: [ + "@storybook/addon-onboarding", + "@storybook/addon-docs", + "@storybook/addon-links", + "@storybook/addon-a11y", + "@storybook/addon-vitest", + ], + stories: ["../../ui/src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + async viteFinal(config) { + const { mergeConfig, searchForWorkspaceRoot } = await import("vite") + return mergeConfig(config, { + resolve: { + dedupe: ["solid-js", "solid-js/web", "@solidjs/meta"], + }, + worker: { + format: "es", + }, + server: { + fs: { + allow: [searchForWorkspaceRoot(process.cwd()), ui], + }, + }, + }) + }, +}) diff --git a/packages/storybook/.storybook/manager.ts b/packages/storybook/.storybook/manager.ts new file mode 100644 index 000000000..9af9ba0a8 --- /dev/null +++ b/packages/storybook/.storybook/manager.ts @@ -0,0 +1,11 @@ +import { addons, types } from "storybook/manager-api" +import { ThemeTool } from "./theme-tool" + +addons.register("opencode/theme-toggle", () => { + addons.add("opencode/theme-toggle/tool", { + type: types.TOOL, + title: "Theme", + match: ({ viewMode }) => viewMode === "story" || viewMode === "docs", + render: ThemeTool, + }) +}) diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx new file mode 100644 index 000000000..cb5ee4329 --- /dev/null +++ b/packages/storybook/.storybook/preview.tsx @@ -0,0 +1,106 @@ +import "@opencode-ai/ui/styles" + +import { createEffect, onCleanup, onMount } from "solid-js" +import addonA11y from "@storybook/addon-a11y" +import addonDocs from "@storybook/addon-docs" +import { MetaProvider } from "@solidjs/meta" +import { addons } from "storybook/preview-api" +import { GLOBALS_UPDATED } from "storybook/internal/core-events" +import { createJSXDecorator, definePreview } from "storybook-solidjs-vite" +import { Code } from "@opencode-ai/ui/code" +import { CodeComponentProvider } from "@opencode-ai/ui/context/code" +import { DialogProvider } from "@opencode-ai/ui/context/dialog" +import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" +import { MarkedProvider } from "@opencode-ai/ui/context/marked" +import { Diff } from "@opencode-ai/ui/diff" +import { ThemeProvider, useTheme, type ColorScheme } from "@opencode-ai/ui/theme" +import { Font } from "@opencode-ai/ui/font" + +function resolveScheme(value: unknown): ColorScheme { + if (value === "light" || value === "dark" || value === "system") return value + return "system" +} + +const channel = addons.getChannel() + +const Scheme = (props: { value?: unknown }) => { + const theme = useTheme() + const apply = (value?: unknown) => { + theme.setColorScheme(resolveScheme(value)) + } + createEffect(() => { + apply(props.value) + }) + createEffect(() => { + const root = document.documentElement + root.classList.remove("light", "dark") + root.classList.add(theme.mode()) + }) + onMount(() => { + const handler = (event: { globals?: Record }) => { + apply(event.globals?.theme) + } + channel.on(GLOBALS_UPDATED, handler) + onCleanup(() => channel.off(GLOBALS_UPDATED, handler)) + }) + return null +} + +const frame = createJSXDecorator((Story, context) => { + const override = context.parameters?.themes?.themeOverride + const selected = context.globals?.theme + const pick = override === "light" || override === "dark" ? override : selected + const scheme = resolveScheme(pick) + return ( + + + + + + + + +
+ +
+
+
+
+
+
+
+ ) +}) + +export default definePreview({ + addons: [addonDocs(), addonA11y()], + decorators: [frame], + globalTypes: { + theme: { + name: "Theme", + description: "Global theme", + defaultValue: "light", + }, + }, + parameters: { + actions: { + argTypesRegex: "^on.*", + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + a11y: { + test: "todo", + }, + }, +}) diff --git a/packages/storybook/.storybook/theme-tool.ts b/packages/storybook/.storybook/theme-tool.ts new file mode 100644 index 000000000..3dac777cd --- /dev/null +++ b/packages/storybook/.storybook/theme-tool.ts @@ -0,0 +1,21 @@ +import { createElement } from "react" +import { useGlobals } from "storybook/manager-api" +import { ToggleButton } from "storybook/internal/components" + +export function ThemeTool() { + const [globals, updateGlobals] = useGlobals() + const mode = globals.theme === "dark" ? "dark" : "light" + const toggle = () => { + const next = mode === "dark" ? "light" : "dark" + updateGlobals({ theme: next }) + } + return createElement( + ToggleButton, + { + title: "Toggle theme", + active: mode === "dark", + onClick: toggle, + }, + mode === "dark" ? "Dark" : "Light", + ) +} diff --git a/packages/storybook/debug-storybook.log b/packages/storybook/debug-storybook.log new file mode 100644 index 000000000..e13d40c8e --- /dev/null +++ b/packages/storybook/debug-storybook.log @@ -0,0 +1,307 @@ +[14:25:48.462] [INFO] storybook v10.2.10 +[14:25:48.749] [DEBUG] Getting package.json info for /Users/davidhill/Documents/Local/opencode/packages/storybook/package.json... +[14:25:48.997] [INFO] Starting... +[14:25:49.095] [DEBUG] Starting preview.. +[14:25:49.098] [WARN] 🚨 Unable to index files: +- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +[14:25:49.109] [ERROR] Failed to build the preview +[14:25:49.110] [ERROR] Error: Unable to index files: +- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export +- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0) + +More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export + at _StoryIndexGenerator.getIndexAndStats (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6085:15) + at async _StoryIndexGenerator.getIndex (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6074:13) + at async getOptimizeDeps (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1862:15) + at async createViteServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1888:19) + at async Module.start (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@storybook+builder-vite@10.2.10+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1923:17) + at async storybookDevServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7241:83) + at async buildOrThrow (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:4504:12) + at async buildDevStandalone (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7611:66) + at async withTelemetry (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/_node-chunks/chunk-S3MWHNYJ.js:218:12) + at async dev (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/storybook@10.2.10+4edd68b244e756bb/node_modules/storybook/dist/bin/core.js:2734:3) +[14:25:49.118] [WARN] Broken build, fix the error above. +You may need to refresh the browser. \ No newline at end of file diff --git a/packages/storybook/package.json b/packages/storybook/package.json new file mode 100644 index 000000000..2ab92bd5f --- /dev/null +++ b/packages/storybook/package.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@opencode-ai/storybook", + "private": true, + "type": "module", + "scripts": { + "storybook": "storybook dev -p 6006", + "build": "storybook build" + }, + "devDependencies": { + "@opencode-ai/ui": "workspace:*", + "@solidjs/meta": "catalog:", + "@storybook/addon-a11y": "^10.2.10", + "@storybook/addon-docs": "^10.2.10", + "@storybook/addon-links": "^10.2.10", + "@storybook/addon-onboarding": "^10.2.10", + "@storybook/addon-vitest": "^10.2.10", + "@tsconfig/node22": "catalog:", + "@types/node": "catalog:", + "@types/react": "18.0.25", + "react": "18.2.0", + "solid-js": "catalog:", + "storybook": "^10.2.10", + "storybook-solidjs-vite": "^10.0.9", + "typescript": "catalog:", + "vite": "catalog:" + } +} diff --git a/packages/storybook/tsconfig.json b/packages/storybook/tsconfig.json new file mode 100644 index 000000000..68ae315d2 --- /dev/null +++ b/packages/storybook/tsconfig.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js", + "target": "ESNext", + "lib": ["es2023", "dom", "dom.iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "strict": true, + "types": ["vite/client", "node"] + }, + "include": [".storybook/**/*.ts", ".storybook/**/*.tsx"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 0cdccdc54..b2f9bb401 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,6 +4,7 @@ "type": "module", "license": "MIT", "exports": { + "./package.json": "./package.json", "./*": "./src/components/*.tsx", "./i18n/*": "./src/i18n/*.ts", "./pierre": "./src/pierre/index.ts", 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)}
+                
+ ) + }} + > + +
+ ) + }, + }, + } +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 832f7cbf8..25f6a3dcc 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -17,5 +17,6 @@ // Type Checking & Safety "strict": true, "types": ["vite/client", "bun"] - } + }, + "exclude": ["**/*.stories.*", "**/*.mdx"] } -- cgit v1.2.3