UNPKG

@kitn.ai/chat

Version:

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

122 lines (108 loc) 4.62 kB
import meta from '../../elements/element-meta.json'; type Prop = { name: string; type: string; default?: string; scalar: boolean }; type ElementMeta = { tag: string; props: Prop[] }; const all = meta as unknown as ElementMeta[]; // A small, consistent pointer at the top of every Web Component's Docs tab, // directing readers to the dedicated API tab (which holds the full generated // spec). Keeps the Docs tab focused on examples while keeping the spec one click // away — and discoverable, since everyone reads the description first. const API_POINTER = '> **Full API reference** — every property, event, default and token is on the **API** tab above.'; /** * Build a Storybook `docs.description` from an element's intro paragraphs, with * the API-tab pointer prepended. The generated spec itself lives in the **API** * tab (see `.storybook/api-tab.tsx`), not inline here. * (`tag` is kept in the signature so callers don't churn if we re-inline later.) */ export function specDescription(_tag: string, paragraphs: string[]): { component: string } { return { component: [API_POINTER, ...paragraphs].join('\n\n') }; } // The Components/UI sibling of API_POINTER — same idea, component vocabulary // (props/callbacks/slots rather than properties/events/attributes). const COMPONENT_POINTER = '> **Full API reference** — every prop, callback, slot and token is on the **API** tab above.'; /** * Build a Storybook `docs.description` for a SolidJS/UI component story, with the * API-tab pointer prepended. The generated spec (props/callbacks/slots/tokens) * lives on the **API** tab (see `.storybook/api-tab.tsx`), from * `src/components/component-meta.json`. */ export function componentDescription(paragraphs: string[]): { component: string } { return { component: [COMPONENT_POINTER, ...paragraphs].join('\n\n') }; } /** * Parse a type string from element-meta.json into meaningful parts. * * The meta generator writes types like: * - `"undefined | false | true"` for booleans (NOT "boolean") * - `"undefined | \"preview\" | \"code\""` for enums (double-quoted, with leading undefined) * * We normalise by splitting on `|`, trimming, and dropping `undefined` / `null`. */ const normaliseParts = (type: string): string[] => type .split('|') .map((s) => s.trim()) .filter((s) => s !== 'undefined' && s !== 'null'); const enumValues = (parts: string[]): string[] | null => { // Accept both single-quoted ('foo') and double-quoted ("foo") string literals. if (parts.length > 0 && parts.every((p) => /^(['"])[^'"]*\1$/.test(p))) return parts.map((p) => p.slice(1, -1)); return null; }; const isBooleanParts = (parts: string[], rawType: string): boolean => { // Explicit boolean keyword (future-proof) if (/\bboolean\b/.test(rawType)) return true; // element-meta encodes booleans as "undefined | false | true" const sorted = [...parts].sort().join(','); return sorted === 'false,true'; }; /** * Storybook argTypes for an element's props (theme select, booleans, text, * number, object/JSON for complex types, and no control for functions). * * Classification order: * 1. enum → select * 2. boolean → boolean * 3. number → number * 4. contains `=>` (function / function-bearing object) → control: false * 5. not all-string after normalisation (arrays, records, generics…) → object * 6. all-string → text */ export function argTypesFor(tag: string): Record<string, unknown> { const el = all.find((e) => e.tag === tag); if (!el) return {}; const out: Record<string, unknown> = {}; for (const p of el.props) { const parts = normaliseParts(p.type); // 1–3: scalar classifications (enums, booleans, numbers only appear in scalar props) if (p.scalar) { const values = enumValues(parts); if (values) { out[p.name] = { control: { type: 'select' }, options: values }; continue; } if (isBooleanParts(parts, p.type)) { out[p.name] = { control: 'boolean' }; continue; } if (/\bnumber\b/.test(p.type)) { out[p.name] = { control: 'number' }; continue; } } // 4. Function / function-bearing object: contains `=>` in raw type string if (p.type.includes('=>')) { out[p.name] = { control: false }; continue; } // 5. Complex/object: parts are not all exactly the string "string" if (!parts.every((part) => part === 'string')) { out[p.name] = { control: 'object' }; continue; } // 6. Pure string out[p.name] = { control: 'text' }; } return out; }