import type { Meta, StoryObj } from 'storybook-solidjs-vite'; import { createSignal, onMount, type JSX } from 'solid-js'; import './form'; import { argTypesFor, specDescription } from '../stories/docs/element-controls'; import type { FormDefinition } from '../components/form'; import type { CardEvent } from '../primitives/card-contract'; declare module 'solid-js' { // eslint-disable-next-line @typescript-eslint/no-namespace namespace JSX { interface IntrinsicElements { 'kc-form': JSX.HTMLAttributes & { heading?: string; 'card-id'?: string; ref?: (el: HTMLElement) => void; }; } } } type FormEl = HTMLElement & { data?: FormDefinition; resolution?: Record }; /** A bordered box the form sits inside. */ function Frame(props: { children: JSX.Element }) { return
{props.children}
; } /** Mounts a , sets `.data`, logs the emitted CardEvent under the render. */ function FormDemo(props: { def: FormDefinition; cardId: string }) { const [log, setLog] = createSignal([]); let el: FormEl | undefined; onMount(() => { if (!el) return; el.data = props.def; el.addEventListener('kc-card', (e) => { const detail = (e as CustomEvent).detail; setLog((prev) => [...prev, detail]); }); }); return (
(el = e as FormEl)} card-id={props.cardId} />
          {log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
        
); } const FEEDBACK: FormDefinition = { type: 'object', title: 'How did we do?', description: 'Two quick questions.', required: ['rating', 'contactOk'], 'x-kc-order': ['rating', 'comments', 'plan', 'contactOk'], 'x-kc-submitLabel': 'Send feedback', 'x-kc-actions': [{ id: 'skip', label: 'Skip', variant: 'ghost' }], properties: { rating: { type: 'integer', title: 'Overall rating', minimum: 1, maximum: 5, 'x-kc-widget': 'rating' }, comments: { type: 'string', title: 'Comments', maxLength: 500, 'x-kc-widget': 'textarea', 'x-kc-placeholder': "What worked, what didn't…", }, plan: { type: 'string', title: 'Your plan', enum: ['free', 'pro', 'team'], default: 'free' }, contactOk: { type: 'boolean', title: 'OK to contact me about this', default: false }, }, }; const ALL_WIDGETS: FormDefinition = { type: 'object', title: 'Every widget', 'x-kc-submitLabel': 'Submit all', properties: { name: { type: 'string', title: 'Name' }, bio: { type: 'string', title: 'Bio', maxLength: 300 }, email: { type: 'string', title: 'Email', format: 'email' }, website: { type: 'string', title: 'Website', format: 'uri' }, birthday: { type: 'string', title: 'Birthday', format: 'date' }, secret: { type: 'string', title: 'Password', 'x-kc-widget': 'password' }, size: { type: 'string', title: 'Size', enum: ['S', 'M', 'L'] }, country: { type: 'string', title: 'Country', enum: ['US', 'UK', 'DE', 'FR', 'JP'] }, age: { type: 'integer', title: 'Age', minimum: 0, maximum: 120 }, volume: { type: 'integer', title: 'Volume', minimum: 0, maximum: 11, 'x-kc-widget': 'slider' }, stars: { type: 'integer', title: 'Stars', minimum: 1, maximum: 5, 'x-kc-widget': 'rating' }, notify: { type: 'boolean', title: 'Email me updates' }, agree: { type: 'boolean', title: 'I agree', 'x-kc-widget': 'checkbox' }, tags: { type: 'array', title: 'Tags', items: { type: 'string' } }, topics: { type: 'array', title: 'Topics', items: { enum: ['news', 'sports', 'tech'] } }, contacts: { type: 'array', title: 'Contacts', items: { type: 'object', properties: { label: { type: 'string', title: 'Label' }, phone: { type: 'string', title: 'Phone' } }, }, }, address: { type: 'object', title: 'Address', properties: { street: { type: 'string', title: 'Street' }, city: { type: 'string', title: 'City' } }, }, }, }; const VALIDATION: FormDefinition = { type: 'object', title: 'Create account', required: ['username', 'email', 'age'], properties: { username: { type: 'string', title: 'Username', minLength: 3, maxLength: 12 }, email: { type: 'string', title: 'Email', format: 'email' }, age: { type: 'integer', title: 'Age', minimum: 13, maximum: 120 }, }, }; const HTML_SNIPPET = (def: FormDefinition) => ` `; const meta = { title: 'Generative UI/Cards/kc-form', tags: ['autodocs'], argTypes: argTypesFor('kc-form'), parameters: { layout: 'padded', docs: { description: specDescription('kc-form', [ '`` turns an agent\'s **JSON Schema** "shape" (set via the `data` **property**) into a themed, accessible, validated form inside `` chrome. A valid submission is emitted **up the Card contract** as a bubbling **`kc-card`** CustomEvent of `{ kind: \'submit\', cardId, data }`.', '**Anatomy:** `` chrome (optional heading from `data.title` or `heading` attr) → **field rows** (one per schema property in `x-kc-order` / key order: label + widget; `object` properties become a `
`, `array` items become a repeater/checkbox-group/tag-list) → **card footer** (`x-kc-submitLabel` submit `