@kitn.ai/ui 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/kitn-chat.es.js +1 -1
  2. package/package.json +6 -1
  3. package/src/elements/compiled.css +1 -1
  4. package/src/components/artifact.stories.tsx +0 -138
  5. package/src/components/attachments.stories.tsx +0 -303
  6. package/src/components/attachments.test.tsx +0 -64
  7. package/src/components/chain-of-thought.stories.tsx +0 -211
  8. package/src/components/chat-container.stories.tsx +0 -187
  9. package/src/components/chat-thread.test.tsx +0 -58
  10. package/src/components/checkpoint.stories.tsx +0 -102
  11. package/src/components/code-block.stories.tsx +0 -150
  12. package/src/components/confirm-card.stories.tsx +0 -74
  13. package/src/components/context.stories.tsx +0 -229
  14. package/src/components/context.test.tsx +0 -133
  15. package/src/components/conversation-item.stories.tsx +0 -125
  16. package/src/components/conversation-list.stories.tsx +0 -133
  17. package/src/components/default-input.test.tsx +0 -111
  18. package/src/components/empty.stories.tsx +0 -434
  19. package/src/components/feedback-bar.stories.tsx +0 -138
  20. package/src/components/feedback-bar.test.tsx +0 -103
  21. package/src/components/file-tree.stories.tsx +0 -73
  22. package/src/components/file-upload.stories.tsx +0 -156
  23. package/src/components/image.stories.tsx +0 -89
  24. package/src/components/loader.stories.tsx +0 -181
  25. package/src/components/markdown.stories.tsx +0 -180
  26. package/src/components/message-action-bar.test.tsx +0 -128
  27. package/src/components/message-narrow.stories.tsx +0 -329
  28. package/src/components/message-skills.stories.tsx +0 -211
  29. package/src/components/message.stories.tsx +0 -281
  30. package/src/components/model-switcher.stories.tsx +0 -123
  31. package/src/components/model-switcher.test.tsx +0 -65
  32. package/src/components/prompt-input.stories.tsx +0 -222
  33. package/src/components/prompt-suggestion.stories.tsx +0 -142
  34. package/src/components/reasoning.stories.tsx +0 -140
  35. package/src/components/resizable.test.tsx +0 -226
  36. package/src/components/scroll-button.stories.tsx +0 -100
  37. package/src/components/slash-command.stories.tsx +0 -163
  38. package/src/components/source-list.test.tsx +0 -152
  39. package/src/components/source.stories.tsx +0 -124
  40. package/src/components/tasks-card.stories.tsx +0 -78
  41. package/src/components/text-shimmer.stories.tsx +0 -87
  42. package/src/components/thinking-bar.stories.tsx +0 -87
  43. package/src/components/tool.stories.tsx +0 -153
  44. package/src/components/voice-input.stories.tsx +0 -83
  45. package/src/elements/artifact.stories.tsx +0 -506
  46. package/src/elements/attachments.stories.tsx +0 -190
  47. package/src/elements/card.stories.tsx +0 -113
  48. package/src/elements/cards.stories.tsx +0 -59
  49. package/src/elements/catalog.stories.tsx +0 -491
  50. package/src/elements/chain-of-thought.declarative.test.tsx +0 -153
  51. package/src/elements/chain-of-thought.stories.tsx +0 -113
  52. package/src/elements/chat-scope-picker.stories.tsx +0 -73
  53. package/src/elements/chat-workspace.stories.tsx +0 -288
  54. package/src/elements/chat.stories.tsx +0 -151
  55. package/src/elements/checkpoint.stories.tsx +0 -72
  56. package/src/elements/choice.stories.tsx +0 -240
  57. package/src/elements/code-block.stories.tsx +0 -83
  58. package/src/elements/composed-shell.stories.tsx +0 -316
  59. package/src/elements/confirm-card.stories.tsx +0 -227
  60. package/src/elements/context-meter.stories.tsx +0 -151
  61. package/src/elements/conversation-list.declarative.test.tsx +0 -137
  62. package/src/elements/conversation-list.stories.tsx +0 -243
  63. package/src/elements/embed.stories.tsx +0 -213
  64. package/src/elements/empty.stories.tsx +0 -111
  65. package/src/elements/feedback-bar.stories.tsx +0 -114
  66. package/src/elements/file-tree.stories.tsx +0 -134
  67. package/src/elements/file-upload.stories.tsx +0 -82
  68. package/src/elements/form.stories.tsx +0 -248
  69. package/src/elements/image.stories.tsx +0 -71
  70. package/src/elements/link-preview.stories.tsx +0 -209
  71. package/src/elements/loader.stories.tsx +0 -88
  72. package/src/elements/markdown.stories.tsx +0 -76
  73. package/src/elements/message-skills.declarative.test.tsx +0 -104
  74. package/src/elements/message-skills.stories.tsx +0 -114
  75. package/src/elements/message.stories.tsx +0 -218
  76. package/src/elements/model-switcher.declarative.test.tsx +0 -130
  77. package/src/elements/model-switcher.stories.tsx +0 -198
  78. package/src/elements/popover.stories.tsx +0 -80
  79. package/src/elements/prompt-input-slash-command.test.tsx +0 -128
  80. package/src/elements/prompt-input-stoppable.test.tsx +0 -88
  81. package/src/elements/prompt-input-toolbar-actions.test.tsx +0 -120
  82. package/src/elements/prompt-input.stories.tsx +0 -331
  83. package/src/elements/prompt-suggestions.declarative.test.tsx +0 -164
  84. package/src/elements/prompt-suggestions.stories.tsx +0 -213
  85. package/src/elements/reasoning.stories.tsx +0 -77
  86. package/src/elements/remote.stories.tsx +0 -314
  87. package/src/elements/resizable.stories.tsx +0 -426
  88. package/src/elements/response-stream.stories.tsx +0 -79
  89. package/src/elements/scroll-button.stories.tsx +0 -202
  90. package/src/elements/scroll-button.test.tsx +0 -148
  91. package/src/elements/source-list.stories.tsx +0 -143
  92. package/src/elements/source.stories.tsx +0 -88
  93. package/src/elements/switch.stories.tsx +0 -56
  94. package/src/elements/tasks.stories.tsx +0 -237
  95. package/src/elements/text-shimmer.stories.tsx +0 -64
  96. package/src/elements/thinking-bar.stories.tsx +0 -72
  97. package/src/elements/tool.stories.tsx +0 -89
  98. package/src/elements/voice-input.stories.tsx +0 -88
  99. package/src/primitives/controllable.test.ts +0 -47
  100. package/src/stories/chat-panel-layout.stories.tsx +0 -145
  101. package/src/stories/chat-scene.tsx +0 -571
  102. package/src/stories/checkpoint-restore.stories.tsx +0 -256
  103. package/src/stories/context-usage.stories.tsx +0 -212
  104. package/src/stories/conversation-with-reasoning.stories.tsx +0 -182
  105. package/src/stories/conversation-with-sources.stories.tsx +0 -471
  106. package/src/stories/docs/Accessibility.mdx +0 -119
  107. package/src/stories/docs/ChildElements.mdx +0 -269
  108. package/src/stories/docs/ForAIAgents.mdx +0 -162
  109. package/src/stories/docs/GettingStarted.mdx +0 -65
  110. package/src/stories/docs/Installation.mdx +0 -83
  111. package/src/stories/docs/Introduction.mdx +0 -50
  112. package/src/stories/docs/SolidJsAdvanced.mdx +0 -17
  113. package/src/stories/docs/Theming.mdx +0 -85
  114. package/src/stories/docs/element-controls.ts +0 -121
  115. package/src/stories/docs/frameworks/Angular.mdx +0 -245
  116. package/src/stories/docs/frameworks/Html.mdx +0 -227
  117. package/src/stories/docs/frameworks/Overview.mdx +0 -47
  118. package/src/stories/docs/frameworks/React.mdx +0 -227
  119. package/src/stories/docs/frameworks/Solid.mdx +0 -173
  120. package/src/stories/docs/frameworks/Svelte.mdx +0 -194
  121. package/src/stories/docs/frameworks/Vue.mdx +0 -238
  122. package/src/stories/docs/generative-ui-overview.mdx +0 -186
  123. package/src/stories/docs/recipes/SpeechToText.mdx +0 -7
  124. package/src/stories/docs/recipes/Streaming.mdx +0 -68
  125. package/src/stories/docs/recipes/TextToSpeech.mdx +0 -35
  126. package/src/stories/docs/theme-editor/canvas.tsx +0 -32
  127. package/src/stories/docs/theme-editor/inspector.tsx +0 -66
  128. package/src/stories/docs/theme-editor/presets.test.ts +0 -32
  129. package/src/stories/docs/theme-editor/presets.ts +0 -64
  130. package/src/stories/docs/theme-editor/theme-css.test.ts +0 -19
  131. package/src/stories/docs/theme-editor/theme-css.ts +0 -15
  132. package/src/stories/docs/theme-editor/theme-editor.tsx +0 -146
  133. package/src/stories/docs/theme-tokens.tsx +0 -174
  134. package/src/stories/examples/ChoosingComponents.mdx +0 -106
  135. package/src/stories/examples/sample-data.ts +0 -79
  136. package/src/stories/examples/usage/checkpoint-restore.ts +0 -156
  137. package/src/stories/examples/usage/context-usage.ts +0 -591
  138. package/src/stories/examples/usage/conversation-with-reasoning.ts +0 -224
  139. package/src/stories/examples/usage/conversation-with-sources.ts +0 -589
  140. package/src/stories/examples/usage/empty-state.ts +0 -144
  141. package/src/stories/examples/usage/full-chat-app.ts +0 -277
  142. package/src/stories/examples/usage/index.ts +0 -63
  143. package/src/stories/examples/usage/message-actions.ts +0 -704
  144. package/src/stories/examples/usage/pattern-centered-conversation.ts +0 -446
  145. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +0 -442
  146. package/src/stories/examples/usage/pattern-docked-widget.ts +0 -479
  147. package/src/stories/examples/usage/prompt-input-variants.ts +0 -1356
  148. package/src/stories/examples/usage/streaming-response.ts +0 -687
  149. package/src/stories/examples/usage/types.ts +0 -33
  150. package/src/stories/full-chat.stories.tsx +0 -18
  151. package/src/stories/message-actions.stories.tsx +0 -230
  152. package/src/stories/pattern-centered-conversation.stories.tsx +0 -93
  153. package/src/stories/pattern-docked-widget.stories.tsx +0 -93
  154. package/src/stories/pattern-empty-state.stories.tsx +0 -76
  155. package/src/stories/prompt-input-variants.stories.tsx +0 -456
  156. package/src/stories/streaming-response.stories.tsx +0 -438
  157. package/src/stories/theme-editor.stories.tsx +0 -16
  158. package/src/stories/token-reference.stories.tsx +0 -18
  159. package/src/stories/typography.stories.tsx +0 -78
  160. package/src/ui/avatar.stories.tsx +0 -103
  161. package/src/ui/badge.stories.tsx +0 -86
  162. package/src/ui/button.stories.tsx +0 -145
  163. package/src/ui/collapsible.stories.tsx +0 -69
  164. package/src/ui/dropdown.stories.tsx +0 -59
  165. package/src/ui/hover-card.stories.tsx +0 -77
  166. package/src/ui/hover-card.test.tsx +0 -30
  167. package/src/ui/overlay.stories.tsx +0 -115
  168. package/src/ui/popover.stories.tsx +0 -81
  169. package/src/ui/popover.test.tsx +0 -99
  170. package/src/ui/resizable.stories.tsx +0 -236
  171. package/src/ui/scroll-area.stories.tsx +0 -50
  172. package/src/ui/separator.stories.tsx +0 -81
  173. package/src/ui/skeleton.stories.tsx +0 -337
  174. package/src/ui/switch.stories.tsx +0 -51
  175. package/src/ui/switch.test.tsx +0 -59
  176. package/src/ui/textarea.stories.tsx +0 -76
  177. package/src/ui/tooltip.stories.tsx +0 -74
@@ -1,151 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers <kai-chat>, <kai-conversations>, <kai-prompt-input>
4
- import type { ChatMessage } from './chat-types';
5
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
6
-
7
- // The web components are custom DOM elements, so declare the tags for JSX.
8
- declare module 'solid-js' {
9
- // eslint-disable-next-line @typescript-eslint/no-namespace
10
- namespace JSX {
11
- interface IntrinsicElements {
12
- 'kai-chat': JSX.HTMLAttributes<HTMLElement>;
13
- }
14
- }
15
- }
16
-
17
- const sampleMessages: ChatMessage[] = [
18
- { id: '1', role: 'user', content: 'How do I center a div?' },
19
- {
20
- id: '2',
21
- role: 'assistant',
22
- content:
23
- 'The modern way is a grid:\n\n```css\n.box {\n display: grid;\n place-items: center;\n}\n```\n\nThat centers the child on both axes.',
24
- actions: ['copy', 'like', 'dislike'],
25
- },
26
- ];
27
-
28
- type ChatEl = HTMLElement & {
29
- messages?: ChatMessage[];
30
- value?: string;
31
- placeholder?: string;
32
- loading?: boolean;
33
- suggestionMode?: string;
34
- proseSize?: string;
35
- codeTheme?: string;
36
- codeHighlight?: boolean;
37
- chatTitle?: string;
38
- currentModel?: string;
39
- scrollButton?: boolean;
40
- search?: boolean;
41
- voice?: boolean;
42
- slashCompact?: boolean;
43
- };
44
-
45
- /** Live demo of the actual `<kai-chat>` custom element (Shadow DOM and all). */
46
- function ChatElement(props: { args?: Record<string, unknown> }) {
47
- let el: ChatEl | undefined;
48
- onMount(() => {
49
- if (el) {
50
- // Fixed array data
51
- el.messages = sampleMessages;
52
- // Scalar args from Controls
53
- const args = props.args;
54
- if (args) {
55
- const scalarNames = [
56
- 'value', 'placeholder', 'loading', 'suggestionMode', 'proseSize',
57
- 'codeTheme', 'codeHighlight', 'chatTitle', 'currentModel',
58
- 'scrollButton', 'search', 'voice', 'slashCompact',
59
- ];
60
- for (const name of scalarNames) {
61
- if (name in args) (el as unknown as Record<string, unknown>)[name] = args[name];
62
- }
63
- }
64
- }
65
- });
66
- return <kai-chat ref={(e) => (el = e as ChatEl)} style={{ display: 'block', height: '560px' }} />;
67
- }
68
-
69
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
70
- <kai-chat id="chat" style="display:block; height:100vh;"></kai-chat>
71
-
72
- <script type="module">
73
- import '@kitn.ai/ui/elements'; // registers the custom elements
74
-
75
- const chat = document.getElementById('chat');
76
- chat.messages = [
77
- { id: '1', role: 'user', content: 'How do I center a div?' },
78
- { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
79
- ];
80
-
81
- // events are CustomEvents on the element (they do not bubble)
82
- chat.addEventListener('kai-submit', (e) => console.log('user sent:', e.detail.value));
83
- </script>`;
84
-
85
- const SOLID_SNIPPET = `import '@kitn.ai/ui/elements'; // registers the custom elements
86
- import { onMount } from 'solid-js';
87
- import type { ChatMessage } from '@kitn.ai/ui/elements';
88
-
89
- function Chat() {
90
- let el: HTMLElement & { messages?: ChatMessage[] };
91
- const messages: ChatMessage[] = [
92
- { id: '1', role: 'user', content: 'How do I center a div?' },
93
- { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
94
- ];
95
- onMount(() => { el.messages = messages; });
96
- return (
97
- <kai-chat
98
- ref={el}
99
- style={{ display: 'block', height: '100vh' }}
100
- on:kai-submit={(e) => console.log('user sent:', e.detail.value)}
101
- />
102
- );
103
- }`;
104
-
105
- const meta = {
106
- title: 'Components/Chat',
107
- tags: ['autodocs'],
108
- argTypes: argTypesFor('kai-chat'),
109
- parameters: {
110
- layout: 'fullscreen',
111
- docs: {
112
- description: specDescription('kai-chat', [
113
- '`<kai-chat>` is the framework-agnostic **web component** version of the chat UI — a complete message thread plus prompt input, isolated in **Shadow DOM** so the host page\'s CSS can\'t leak in and the kit\'s styles can\'t leak out. SolidJS is bundled in, so the host needs nothing.',
114
- '**When to use:** dropping a full chat into a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts. If you *are* in SolidJS and want fine-grained control, compose the primitives (`ChatContainer`, `Message`, `PromptInput`) instead.',
115
- '**How to use:** register once with `import \'@kitn.ai/ui/elements\'`, set rich data as JS **properties** (`el.messages = [...]`), and listen for **CustomEvents** (`kai-submit`, `kai-message-action`, `kai-value-change`) directly on the element.',
116
- '**Anatomy:** **header** (model switcher + context-meter, shown when `models`/`context` props are set) → **message list** (scrollable thread of `<Message>` rows, each rendered per `role`; a scroll-to-bottom button appears when scrolled up) → **prompt composer** (`<PromptInput>`: textarea + toolbar with suggestions, slash-command palette, voice/search buttons, and optional attachment area).',
117
- '**Placement:** as a top-level panel or full-page surface. Give it an explicit height (e.g. `height: 100vh`).',
118
- 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
119
- ]),
120
- },
121
- },
122
- } satisfies Meta;
123
-
124
- export default meta;
125
- type Story = StoryObj;
126
-
127
- /** The element used the plain-HTML / any-framework way. */
128
- export const Default: Story = {
129
- args: {
130
- placeholder: 'Send a message...',
131
- loading: false,
132
- suggestionMode: 'submit',
133
- proseSize: 'sm',
134
- codeTheme: 'github-dark-dimmed',
135
- codeHighlight: true,
136
- scrollButton: true,
137
- search: false,
138
- voice: false,
139
- slashCompact: false,
140
- },
141
- render: (args: Record<string, unknown>) => <ChatElement args={args} />,
142
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
143
- };
144
-
145
- /** The same element used inside a SolidJS component (properties via `ref`, events via `on:`). */
146
- export const InSolidJS: Story = {
147
- name: 'In SolidJS',
148
- render: () => <ChatElement />,
149
- parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
150
- };
151
-
@@ -1,72 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers the custom elements
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
-
6
- // The web components are custom DOM elements, so declare the tags for JSX.
7
- declare module 'solid-js' {
8
- // eslint-disable-next-line @typescript-eslint/no-namespace
9
- namespace JSX {
10
- interface IntrinsicElements {
11
- 'kai-checkpoint': JSX.HTMLAttributes<HTMLElement>;
12
- }
13
- }
14
- }
15
-
16
- /** Render the actual `<kai-checkpoint>` custom element configured by attributes. */
17
- function CheckpointElement(props: { label?: string; tooltip?: string; variant?: string; size?: string }) {
18
- let el: HTMLElement | undefined;
19
- onMount(() => {
20
- if (!el) return;
21
- if (props.label) el.setAttribute('label', props.label);
22
- if (props.tooltip) el.setAttribute('tooltip', props.tooltip);
23
- if (props.variant) el.setAttribute('variant', props.variant);
24
- if (props.size) el.setAttribute('size', props.size);
25
- el.addEventListener('kai-select', () => console.log('checkpoint selected'));
26
- });
27
- return <kai-checkpoint ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '16px' }} />;
28
- }
29
-
30
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
31
- <kai-checkpoint label="Restore" tooltip="Restore this checkpoint" variant="outline" size="sm"></kai-checkpoint>
32
-
33
- <script type="module">
34
- import '@kitn.ai/ui/elements'; // registers the custom elements
35
-
36
- const cp = document.querySelector('kai-checkpoint');
37
- // events are CustomEvents on the element (they do not bubble)
38
- cp.addEventListener('kai-select', () => console.log('restore checkpoint'));
39
- </script>`;
40
-
41
- const meta = {
42
- title: 'Components/Checkpoint',
43
- tags: ['autodocs'],
44
- argTypes: argTypesFor('kai-checkpoint'),
45
- parameters: {
46
- layout: 'fullscreen',
47
- docs: {
48
- description: specDescription('kai-checkpoint', [
49
- '`<kai-checkpoint>` is the framework-agnostic **web component** for a bookmark/checkpoint button (with an optional tooltip and label), isolated in **Shadow DOM**.',
50
- '**When to use:** marking a restore point in a conversation in a non-Solid app. In SolidJS, compose the `Checkpoint` primitives.',
51
- '**Placement:** inline inside a message row (e.g. in the action bar) or in a chat toolbar; it is a compact `inline-block` button and does not need a dedicated row.',
52
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set `label`, `tooltip`, `variant` (`ghost` | `default` | `outline`), and `size` via attributes, and listen for the `kai-select` **CustomEvent** on click.",
53
- 'See the **Code** tab for HTML usage.',
54
- ]),
55
- },
56
- },
57
- } satisfies Meta;
58
-
59
- export default meta;
60
- type Story = StoryObj;
61
-
62
- /** A labeled checkpoint button. (Add a `tooltip` attribute for a hover hint — see the Code tab.) */
63
- export const Labeled: Story = {
64
- render: () => <CheckpointElement label="Restore" variant="outline" />,
65
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
66
- };
67
-
68
- /** Icon-only (no label), using an `icon` size. */
69
- export const IconOnly: Story = {
70
- name: 'Icon Only',
71
- render: () => <CheckpointElement size="icon-sm" />,
72
- };
@@ -1,240 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { createSignal, onMount, type JSX } from 'solid-js';
3
- import './choice';
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
- import type { ChoiceCardData } from '../components/choice-card';
6
- import type { CardEvent } from '../primitives/card-contract';
7
-
8
- declare module 'solid-js' {
9
- // eslint-disable-next-line @typescript-eslint/no-namespace
10
- namespace JSX {
11
- interface IntrinsicElements {
12
- 'kai-choice': JSX.HTMLAttributes<HTMLElement> & {
13
- heading?: string;
14
- 'card-id'?: string;
15
- ref?: (el: HTMLElement) => void;
16
- };
17
- }
18
- }
19
- }
20
-
21
- type ChoiceEl = HTMLElement & { data?: ChoiceCardData; resolution?: Record<string, unknown> };
22
-
23
- function Frame(props: { children: JSX.Element }) {
24
- return <div style={{ 'max-width': '460px' }}>{props.children}</div>;
25
- }
26
-
27
- /** Mounts a <kai-choice>, sets `.data`, logs the emitted CardEvent under the render. */
28
- function ChoiceDemo(props: { def: ChoiceCardData; cardId: string; heading?: string }) {
29
- const [log, setLog] = createSignal<CardEvent[]>([]);
30
- let el: ChoiceEl | undefined;
31
- onMount(() => {
32
- if (!el) return;
33
- el.data = props.def;
34
- el.addEventListener('kai-card', (e) => {
35
- const detail = (e as CustomEvent<CardEvent>).detail;
36
- setLog((prev) => [...prev, detail]);
37
- });
38
- });
39
- return (
40
- <Frame>
41
- <div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
42
- <kai-choice ref={(e) => (el = e as ChoiceEl)} card-id={props.cardId} heading={props.heading} />
43
- <pre
44
- style={{
45
- margin: 0,
46
- 'max-height': '180px',
47
- overflow: 'auto',
48
- background: 'var(--color-muted, #f4f4f5)',
49
- 'border-radius': '8px',
50
- padding: '8px',
51
- 'font-size': '12px',
52
- }}
53
- >
54
- {log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
55
- </pre>
56
- </div>
57
- </Frame>
58
- );
59
- }
60
-
61
- const PLANS: ChoiceCardData = {
62
- prompt: 'Which plan fits your team?',
63
- options: [
64
- { id: 'free', label: 'Free', description: 'For trying things out', meta: '$0' },
65
- {
66
- id: 'pro',
67
- label: 'Pro',
68
- description: 'For growing teams',
69
- meta: '$12/seat',
70
- recommended: true,
71
- payload: { plan: 'pro' },
72
- },
73
- { id: 'enterprise', label: 'Enterprise', description: 'SSO, audit log, SLA', meta: 'Contact us' },
74
- { id: 'legacy', label: 'Legacy', description: 'No longer available', disabled: true },
75
- ],
76
- };
77
-
78
- const TEMPLATES: ChoiceCardData = {
79
- prompt: 'Pick a workspace template.',
80
- submitLabel: 'Create workspace',
81
- options: [
82
- {
83
- id: 'blank',
84
- label: 'Blank',
85
- description: 'Start from scratch',
86
- media: { image: 'https://placehold.co/80x80/png?text=Blank', imageAlt: 'A blank canvas' },
87
- },
88
- {
89
- id: 'kanban',
90
- label: 'Kanban',
91
- description: 'Board with columns',
92
- recommended: true,
93
- media: { image: 'https://placehold.co/80x80/png?text=Kanban', imageAlt: 'A kanban board' },
94
- payload: { template: 'kanban' },
95
- },
96
- {
97
- id: 'docs',
98
- label: 'Docs',
99
- description: 'Wiki-style pages',
100
- media: { image: 'https://placehold.co/80x80/png?text=Docs', imageAlt: 'A document page' },
101
- },
102
- ],
103
- };
104
-
105
- const QUICK_REPLIES: ChoiceCardData = {
106
- prompt: 'How did this answer land?',
107
- options: [
108
- { id: 'great', label: 'That solved it', payload: { sentiment: 'positive' } },
109
- { id: 'partly', label: 'Partly — needs more', payload: { sentiment: 'neutral' } },
110
- { id: 'no', label: 'Not what I meant', payload: { sentiment: 'negative' } },
111
- ],
112
- allowOther: { label: 'Something else…', placeholder: 'Tell me what you expected' },
113
- };
114
-
115
- const HEADING_MAP: Record<string, string | undefined> = {
116
- 'card-plans': 'Choose a plan',
117
- 'card-templates': 'New workspace',
118
- 'card-replies': undefined,
119
- };
120
-
121
- const HTML_SNIPPET = (def: ChoiceCardData, cardId: string) => {
122
- const heading = HEADING_MAP[cardId];
123
- return `<kai-choice${heading ? ` heading="${heading}"` : ''}></kai-choice>
124
- <script type="module">
125
- import '@kitn.ai/ui/elements'; // registers the custom elements
126
-
127
- const el = document.querySelector('kai-choice');
128
- // \`data\` is the CardEnvelope.data (set as a property).
129
- el.data = ${JSON.stringify(def, null, 2)};
130
-
131
- // Cards bubble ONE \`kai-card\` CustomEvent carrying a typed CardEvent.
132
- el.addEventListener('kai-card', (e) => {
133
- const ev = e.detail; // { kind:'action', cardId, action, payload? } | { kind:'ready', ... } | ...
134
- if (ev.kind === 'action') console.log('chose', ev.action, ev.payload);
135
- });
136
- </script>`;
137
- };
138
-
139
- const meta = {
140
- title: 'Generative UI/Cards/kai-choice',
141
- tags: ['autodocs'],
142
- argTypes: argTypesFor('kai-choice'),
143
- parameters: {
144
- layout: 'padded',
145
- docs: {
146
- description: specDescription('kai-choice', [
147
- "`<kai-choice>` is a **single-select** 'pick one of N rich options' card (set via the `data` **property**): an optional prompt + a radiogroup of list rows + a **Submit** button. Clicking a row (or Space/Enter on the focused row) **selects** it locally — nothing is emitted. Submit then emits the Card contract's **`action`** verb up a bubbling **`kai-card`** CustomEvent of `{ kind:'action', cardId, action: option.id, payload? }`, then **resolves** the card (the chosen option shown read-only) so the same pick can't double-fire. The Submit label is `submitLabel` (default 'Submit').",
148
- '**Rich-but-bounded options:** every field except `id`/`label` is optional — `description`, `media` (image + `imageAlt`, or a named `icon`), `meta` (trailing price/badge), `recommended` (a pill), `disabled` (inert + skipped in keyboard nav), and an opaque `payload` echoed back. A weaker model can just omit them.',
149
- "**`allowOther`** (opt-in) appends a final 'Other…' row: selecting it reveals an inline text input. The single shared **Submit** stays disabled until the text is non-empty; submitting emits `{ kind:'action', action:'__other__', payload:{ text } }`.",
150
- '**Accessibility:** the options are a WAI-ARIA `radiogroup` (`role="radio"` + `aria-checked`) with **roving tabindex** — one tab stop in, Arrow keys move focus (skipping disabled), Enter/Space select the focused row. Disabled options are `aria-disabled` and not focusable; option images carry alt text; the Other input has a label. **0 axe violations** (light + dark).',
151
- '**Events** (all frozen Card-contract verbs): `ready` on mount, `action` on Submit, `error` for a malformed definition (renders the inline `kai-card` error). It **never invents events**, and — because it carries a registry entry — `<kai-cards>` / `renderCard` dispatch it automatically.',
152
- ]),
153
- },
154
- },
155
- } satisfies Meta;
156
-
157
- export default meta;
158
- type Story = StoryObj;
159
-
160
- /** A list of plans with a `recommended` pill, `meta` prices, and a disabled legacy row.
161
- * Select a row, then click Submit to emit the `action`. */
162
- export const Plans: Story = {
163
- render: () => <ChoiceDemo def={PLANS} cardId="card-plans" heading="Choose a plan" />,
164
- parameters: { docs: { source: { code: HTML_SNIPPET(PLANS, 'card-plans'), language: 'html' } } },
165
- };
166
-
167
- /** Rows with leading images (each `media.image` carries `imageAlt`) and a custom `submitLabel`. */
168
- export const WithMedia: Story = {
169
- render: () => <ChoiceDemo def={TEMPLATES} cardId="card-templates" heading="New workspace" />,
170
- parameters: { docs: { source: { code: HTML_SNIPPET(TEMPLATES, 'card-templates'), language: 'html' } } },
171
- };
172
-
173
- /** Quick replies with the `allowOther` free-text escape: select Other, type, then Submit. */
174
- export const AllowOther: Story = {
175
- render: () => <ChoiceDemo def={QUICK_REPLIES} cardId="card-replies" />,
176
- parameters: { docs: { source: { code: HTML_SNIPPET(QUICK_REPLIES, 'card-replies'), language: 'html' } } },
177
- };
178
-
179
- const RESOLVED_CHOICE: ChoiceCardData = {
180
- prompt: 'Which plan?',
181
- options: [
182
- { id: 'free', label: 'Free', meta: '$0' },
183
- { id: 'pro', label: 'Pro', meta: '$20/mo' },
184
- ],
185
- };
186
-
187
- /** The card after the user chose **Pro** — only the chosen option shown, no radiogroup or Submit. */
188
- export const Resolved: Story = {
189
- name: 'Resolved (read-only)',
190
- render: () => {
191
- let el: ChoiceEl | undefined;
192
- onMount(() => {
193
- if (!el) return;
194
- el.data = RESOLVED_CHOICE;
195
- el.resolution = { kind: 'action', action: 'pro' };
196
- });
197
- return (
198
- <Frame>
199
- <kai-choice ref={(e) => (el = e as ChoiceEl)} card-id="card-resolved-choice" heading="Choose a plan" />
200
- </Frame>
201
- );
202
- },
203
- parameters: {
204
- docs: {
205
- source: {
206
- code: `<kai-choice heading="Choose a plan"></kai-choice>
207
- <script type="module">
208
- import '@kitn.ai/ui/elements';
209
- const el = document.querySelector('kai-choice');
210
- el.data = ${JSON.stringify(RESOLVED_CHOICE, null, 2)};
211
- // Setting .resolution renders the chromed read-only view — no radiogroup or Submit.
212
- el.resolution = { kind: 'action', action: 'pro' };
213
- </script>`,
214
- language: 'html',
215
- },
216
- },
217
- },
218
- };
219
-
220
- /** A malformed `data` (empty `options`) → the inline error state + an `error` event. */
221
- export const ErrorState: Story = {
222
- render: () => <ChoiceDemo def={{ options: [] } as unknown as ChoiceCardData} cardId="card-bad" />,
223
- parameters: {
224
- docs: {
225
- source: {
226
- code: `<kai-choice></kai-choice>
227
- <script type="module">
228
- import '@kitn.ai/ui/elements';
229
- const el = document.querySelector('kai-choice');
230
- // No options → inline error state + an \`error\` event.
231
- el.data = { options: [] };
232
- el.addEventListener('kai-card', (e) => {
233
- if (e.detail.kind === 'error') console.warn('choice error:', e.detail.message);
234
- });
235
- </script>`,
236
- language: 'html',
237
- },
238
- },
239
- },
240
- };
@@ -1,83 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers the custom elements
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
-
6
- // The web components are custom DOM elements, so declare the tags for JSX.
7
- declare module 'solid-js' {
8
- // eslint-disable-next-line @typescript-eslint/no-namespace
9
- namespace JSX {
10
- interface IntrinsicElements {
11
- 'kai-code-block': JSX.HTMLAttributes<HTMLElement>;
12
- }
13
- }
14
- }
15
-
16
- const sampleCode = `export function add(a: number, b: number): number {
17
- return a + b;
18
- }`;
19
-
20
- const pythonCode = `def fib(n):
21
- a, b = 0, 1
22
- for _ in range(n):
23
- a, b = b, a + b
24
- return a`;
25
-
26
- /** Render the actual `<kai-code-block>` custom element with a `code` property. */
27
- function CodeBlockElement(props: { code: string; language?: string }) {
28
- let el: (HTMLElement & { code?: string }) | undefined;
29
- onMount(() => {
30
- if (el) {
31
- el.code = props.code;
32
- if (props.language) el.setAttribute('language', props.language);
33
- }
34
- });
35
- return (
36
- <kai-code-block
37
- ref={(e) => (el = e as HTMLElement)}
38
- style={{ display: 'block', padding: '16px', 'max-width': '720px' }}
39
- />
40
- );
41
- }
42
-
43
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
44
- <kai-code-block id="code" language="ts" code-theme="github-dark-dimmed"></kai-code-block>
45
-
46
- <script type="module">
47
- import '@kitn.ai/ui/elements'; // registers the custom elements
48
-
49
- const code = document.getElementById('code');
50
- code.code = 'export function add(a, b) {\\n return a + b;\\n}';
51
- </script>`;
52
-
53
- const meta = {
54
- title: 'Components/CodeBlock',
55
- tags: ['autodocs'],
56
- argTypes: argTypesFor('kai-code-block'),
57
- parameters: {
58
- layout: 'fullscreen',
59
- docs: {
60
- description: specDescription('kai-code-block', [
61
- '`<kai-code-block>` is the framework-agnostic **web component** for a single syntax-highlighted code block, complete with a copy button, isolated in **Shadow DOM**.',
62
- '**When to use:** dropping a highlighted snippet into a non-Solid app. In SolidJS, compose `CodeBlock` + `CodeBlockCode` directly.',
63
- '**Placement:** as a block inside a message row, tutorial card, or documentation pane; it is `display: block` and fills container width, so set `max-width` on the parent or the element to control line lengths.',
64
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the source via the `code` **property** (`el.code = '...'`), and pick a grammar with the `language` attribute (defaults to `tsx`). Tune highlighting with `code-theme` / `code-highlight`.",
65
- 'See the **Code** tab for HTML usage.',
66
- ]),
67
- },
68
- },
69
- } satisfies Meta;
70
-
71
- export default meta;
72
- type Story = StoryObj;
73
-
74
- /** A TypeScript snippet (the default `tsx` grammar). */
75
- export const TypeScript: Story = {
76
- render: () => <CodeBlockElement code={sampleCode} language="ts" />,
77
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
78
- };
79
-
80
- /** A different grammar via the `language` attribute. */
81
- export const Python: Story = {
82
- render: () => <CodeBlockElement code={pythonCode} language="python" />,
83
- };