import type { Meta, StoryObj } from "storybook-solidjs-vite"; import { fn } from "storybook/test"; import { createSignal } from "solid-js"; import { SlashCommand, type SlashCommandItem } from "./slash-command"; import { PromptInput, PromptInputTextarea } from "./prompt-input"; import { ChatConfig } from "../primitives/chat-config"; import { componentDescription } from '../stories/docs/element-controls'; const skillCommands: SlashCommandItem[] = [ { id: "caveman", label: "Caveman", description: "Ultra-compressed terse responses", category: "Skills" }, { id: "detailed", label: "Detailed", description: "Thorough, comprehensive responses", category: "Skills" }, { id: "eli5", label: "ELI5", description: "Explain simply, avoid jargon", category: "Skills" }, { id: "socratic", label: "Socratic", description: "Ask guiding questions", category: "Skills" }, { id: "concise", label: "Concise", description: "Short, direct answers", category: "Skills" }, ]; const mixedCommands: SlashCommandItem[] = [ ...skillCommands, { id: "clear", label: "Clear", description: "Clear conversation history", category: "Actions" }, { id: "export", label: "Export", description: "Export conversation as markdown", category: "Actions" }, ]; /** * `SlashCommand` reads the input value from the enclosing `PromptInput` * context, so every story mounts it inside a `PromptInput`. The popup opens * when the input starts with `/`. */ function SlashDemo(props: { commands: SlashCommandItem[]; activeIds?: string[]; compact?: boolean; onSelect: (cmd: SlashCommandItem) => void }) { const [value, setValue] = createSignal("/"); return (
setValue("")}> { props.onSelect(cmd); setValue(""); }} />

Type / to see commands. Arrow keys + Tab/Enter to select.

); } const meta = { title: "Solid (Advanced)/Elements/SlashCommand", component: SlashCommand, tags: ["autodocs"], parameters: { layout: "centered", docs: { description: componentDescription([ "A keyboard-navigable command palette that pops above a `PromptInput` when the user types `/`. Filters and groups commands by category, with arrow-key navigation and Tab/Enter to select.", "**When to use:** to offer slash commands or toggleable skills/modes directly from the prompt input as the user types `/name`.", "**How to use:** render it as a child of `PromptInput` (it consumes that context). Pass `commands`, handle `onSelect`, and optionally pass `activeIds` to mark active toggles. Set `compact={false}` for two-line rows.", "**Placement:** inside the relative-positioned `PromptInput`, absolutely anchored to the top of the textarea.", ]), controls: { exclude: ["use:eventListener"] }, }, }, argTypes: { commands: { control: "object", description: "List of selectable commands ({ id, label, description?, category? }).", }, activeIds: { control: "object", description: "IDs currently marked active — selecting an active command toggles it off.", }, compact: { control: "boolean", description: "Single-line rows (label + description side by side). When false, two-line rows.", table: { defaultValue: { summary: "true" } }, }, onSelect: { action: "select", description: "Fired with the chosen command when an item is selected.", table: { category: "Events" }, }, class: { control: "text", description: "Additional classes on the popup container." }, }, args: { commands: skillCommands, compact: true, activeIds: [], onSelect: fn(), }, render: (args) => ( ), } satisfies Meta; export default meta; type Story = StoryObj; const IMPORT = `import { SlashCommand, PromptInput, PromptInputTextarea } from '@kitn.ai/chat';`; const src = (code: string) => ({ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } }, }); /** Interactive playground — type `/` in the input; tweak `commands`/`compact`/`activeIds`. */ export const Playground: Story = { ...src(` setValue('')}> { /* apply */ }} /> `), }; /** Compact single-line rows (default). */ export const Compact: Story = { args: { compact: true }, ...src(``), }; /** Two-line rows with label above description. */ export const Expanded: Story = { args: { compact: false }, ...src(``), }; /** Commands grouped by category (Skills + Actions). */ export const WithCategories: Story = { args: { commands: mixedCommands }, ...src(`const commands = [ { id: 'eli5', label: 'ELI5', description: 'Explain simply', category: 'Skills' }, { id: 'clear', label: 'Clear', description: 'Clear conversation', category: 'Actions' }, ]; `), }; /** Toggleable skills — active IDs are marked and toggle off when reselected (showcase). */ export const ActiveToggles: Story = { render: () => { const [selected, setSelected] = createSignal(["eli5"]); return ( setSelected((prev) => prev.includes(cmd.id) ? prev.filter((id) => id !== cmd.id) : [...prev, cmd.id], ) } /> ); }, ...src(` toggle(cmd.id)} />`), };