@kitn.ai/ui 0.14.1 → 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 (179) hide show
  1. package/dist/kitn-chat.es.js +1 -1
  2. package/package.json +6 -1
  3. package/src/components/component-meta.json +10 -10
  4. package/src/elements/compiled.css +1 -1
  5. package/src/remote/host-embed.ts +1 -1
  6. package/src/components/artifact.stories.tsx +0 -138
  7. package/src/components/attachments.stories.tsx +0 -303
  8. package/src/components/attachments.test.tsx +0 -64
  9. package/src/components/chain-of-thought.stories.tsx +0 -211
  10. package/src/components/chat-container.stories.tsx +0 -187
  11. package/src/components/chat-thread.test.tsx +0 -58
  12. package/src/components/checkpoint.stories.tsx +0 -102
  13. package/src/components/code-block.stories.tsx +0 -150
  14. package/src/components/confirm-card.stories.tsx +0 -74
  15. package/src/components/context.stories.tsx +0 -229
  16. package/src/components/context.test.tsx +0 -133
  17. package/src/components/conversation-item.stories.tsx +0 -125
  18. package/src/components/conversation-list.stories.tsx +0 -133
  19. package/src/components/default-input.test.tsx +0 -111
  20. package/src/components/empty.stories.tsx +0 -434
  21. package/src/components/feedback-bar.stories.tsx +0 -138
  22. package/src/components/feedback-bar.test.tsx +0 -103
  23. package/src/components/file-tree.stories.tsx +0 -73
  24. package/src/components/file-upload.stories.tsx +0 -156
  25. package/src/components/image.stories.tsx +0 -89
  26. package/src/components/loader.stories.tsx +0 -181
  27. package/src/components/markdown.stories.tsx +0 -180
  28. package/src/components/message-action-bar.test.tsx +0 -128
  29. package/src/components/message-narrow.stories.tsx +0 -329
  30. package/src/components/message-skills.stories.tsx +0 -211
  31. package/src/components/message.stories.tsx +0 -281
  32. package/src/components/model-switcher.stories.tsx +0 -123
  33. package/src/components/model-switcher.test.tsx +0 -65
  34. package/src/components/prompt-input.stories.tsx +0 -222
  35. package/src/components/prompt-suggestion.stories.tsx +0 -142
  36. package/src/components/reasoning.stories.tsx +0 -140
  37. package/src/components/resizable.test.tsx +0 -226
  38. package/src/components/scroll-button.stories.tsx +0 -100
  39. package/src/components/slash-command.stories.tsx +0 -163
  40. package/src/components/source-list.test.tsx +0 -152
  41. package/src/components/source.stories.tsx +0 -124
  42. package/src/components/tasks-card.stories.tsx +0 -78
  43. package/src/components/text-shimmer.stories.tsx +0 -87
  44. package/src/components/thinking-bar.stories.tsx +0 -87
  45. package/src/components/tool.stories.tsx +0 -153
  46. package/src/components/voice-input.stories.tsx +0 -83
  47. package/src/elements/artifact.stories.tsx +0 -506
  48. package/src/elements/attachments.stories.tsx +0 -190
  49. package/src/elements/card.stories.tsx +0 -113
  50. package/src/elements/cards.stories.tsx +0 -59
  51. package/src/elements/catalog.stories.tsx +0 -491
  52. package/src/elements/chain-of-thought.declarative.test.tsx +0 -153
  53. package/src/elements/chain-of-thought.stories.tsx +0 -113
  54. package/src/elements/chat-scope-picker.stories.tsx +0 -73
  55. package/src/elements/chat-workspace.stories.tsx +0 -288
  56. package/src/elements/chat.stories.tsx +0 -151
  57. package/src/elements/checkpoint.stories.tsx +0 -72
  58. package/src/elements/choice.stories.tsx +0 -240
  59. package/src/elements/code-block.stories.tsx +0 -83
  60. package/src/elements/composed-shell.stories.tsx +0 -316
  61. package/src/elements/confirm-card.stories.tsx +0 -227
  62. package/src/elements/context-meter.stories.tsx +0 -151
  63. package/src/elements/conversation-list.declarative.test.tsx +0 -137
  64. package/src/elements/conversation-list.stories.tsx +0 -243
  65. package/src/elements/embed.stories.tsx +0 -213
  66. package/src/elements/empty.stories.tsx +0 -111
  67. package/src/elements/feedback-bar.stories.tsx +0 -114
  68. package/src/elements/file-tree.stories.tsx +0 -134
  69. package/src/elements/file-upload.stories.tsx +0 -82
  70. package/src/elements/form.stories.tsx +0 -248
  71. package/src/elements/image.stories.tsx +0 -71
  72. package/src/elements/link-preview.stories.tsx +0 -209
  73. package/src/elements/loader.stories.tsx +0 -88
  74. package/src/elements/markdown.stories.tsx +0 -76
  75. package/src/elements/message-skills.declarative.test.tsx +0 -104
  76. package/src/elements/message-skills.stories.tsx +0 -114
  77. package/src/elements/message.stories.tsx +0 -218
  78. package/src/elements/model-switcher.declarative.test.tsx +0 -130
  79. package/src/elements/model-switcher.stories.tsx +0 -198
  80. package/src/elements/popover.stories.tsx +0 -80
  81. package/src/elements/prompt-input-slash-command.test.tsx +0 -128
  82. package/src/elements/prompt-input-stoppable.test.tsx +0 -88
  83. package/src/elements/prompt-input-toolbar-actions.test.tsx +0 -120
  84. package/src/elements/prompt-input.stories.tsx +0 -331
  85. package/src/elements/prompt-suggestions.declarative.test.tsx +0 -164
  86. package/src/elements/prompt-suggestions.stories.tsx +0 -213
  87. package/src/elements/reasoning.stories.tsx +0 -77
  88. package/src/elements/remote.stories.tsx +0 -314
  89. package/src/elements/resizable.stories.tsx +0 -426
  90. package/src/elements/response-stream.stories.tsx +0 -79
  91. package/src/elements/scroll-button.stories.tsx +0 -202
  92. package/src/elements/scroll-button.test.tsx +0 -148
  93. package/src/elements/source-list.stories.tsx +0 -143
  94. package/src/elements/source.stories.tsx +0 -88
  95. package/src/elements/switch.stories.tsx +0 -56
  96. package/src/elements/tasks.stories.tsx +0 -237
  97. package/src/elements/text-shimmer.stories.tsx +0 -64
  98. package/src/elements/thinking-bar.stories.tsx +0 -72
  99. package/src/elements/tool.stories.tsx +0 -89
  100. package/src/elements/voice-input.stories.tsx +0 -88
  101. package/src/primitives/controllable.test.ts +0 -47
  102. package/src/stories/chat-panel-layout.stories.tsx +0 -145
  103. package/src/stories/chat-scene.tsx +0 -571
  104. package/src/stories/checkpoint-restore.stories.tsx +0 -256
  105. package/src/stories/context-usage.stories.tsx +0 -212
  106. package/src/stories/conversation-with-reasoning.stories.tsx +0 -182
  107. package/src/stories/conversation-with-sources.stories.tsx +0 -471
  108. package/src/stories/docs/Accessibility.mdx +0 -119
  109. package/src/stories/docs/ChildElements.mdx +0 -269
  110. package/src/stories/docs/ForAIAgents.mdx +0 -162
  111. package/src/stories/docs/GettingStarted.mdx +0 -65
  112. package/src/stories/docs/Installation.mdx +0 -83
  113. package/src/stories/docs/Introduction.mdx +0 -50
  114. package/src/stories/docs/SolidJsAdvanced.mdx +0 -17
  115. package/src/stories/docs/Theming.mdx +0 -85
  116. package/src/stories/docs/element-controls.ts +0 -121
  117. package/src/stories/docs/frameworks/Angular.mdx +0 -245
  118. package/src/stories/docs/frameworks/Html.mdx +0 -227
  119. package/src/stories/docs/frameworks/Overview.mdx +0 -47
  120. package/src/stories/docs/frameworks/React.mdx +0 -227
  121. package/src/stories/docs/frameworks/Solid.mdx +0 -173
  122. package/src/stories/docs/frameworks/Svelte.mdx +0 -194
  123. package/src/stories/docs/frameworks/Vue.mdx +0 -238
  124. package/src/stories/docs/generative-ui-overview.mdx +0 -186
  125. package/src/stories/docs/recipes/SpeechToText.mdx +0 -7
  126. package/src/stories/docs/recipes/Streaming.mdx +0 -68
  127. package/src/stories/docs/recipes/TextToSpeech.mdx +0 -35
  128. package/src/stories/docs/theme-editor/canvas.tsx +0 -32
  129. package/src/stories/docs/theme-editor/inspector.tsx +0 -66
  130. package/src/stories/docs/theme-editor/presets.test.ts +0 -32
  131. package/src/stories/docs/theme-editor/presets.ts +0 -64
  132. package/src/stories/docs/theme-editor/theme-css.test.ts +0 -19
  133. package/src/stories/docs/theme-editor/theme-css.ts +0 -15
  134. package/src/stories/docs/theme-editor/theme-editor.tsx +0 -146
  135. package/src/stories/docs/theme-tokens.tsx +0 -174
  136. package/src/stories/examples/ChoosingComponents.mdx +0 -106
  137. package/src/stories/examples/sample-data.ts +0 -79
  138. package/src/stories/examples/usage/checkpoint-restore.ts +0 -156
  139. package/src/stories/examples/usage/context-usage.ts +0 -591
  140. package/src/stories/examples/usage/conversation-with-reasoning.ts +0 -224
  141. package/src/stories/examples/usage/conversation-with-sources.ts +0 -589
  142. package/src/stories/examples/usage/empty-state.ts +0 -144
  143. package/src/stories/examples/usage/full-chat-app.ts +0 -277
  144. package/src/stories/examples/usage/index.ts +0 -63
  145. package/src/stories/examples/usage/message-actions.ts +0 -704
  146. package/src/stories/examples/usage/pattern-centered-conversation.ts +0 -446
  147. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +0 -442
  148. package/src/stories/examples/usage/pattern-docked-widget.ts +0 -479
  149. package/src/stories/examples/usage/prompt-input-variants.ts +0 -1356
  150. package/src/stories/examples/usage/streaming-response.ts +0 -687
  151. package/src/stories/examples/usage/types.ts +0 -33
  152. package/src/stories/full-chat.stories.tsx +0 -18
  153. package/src/stories/message-actions.stories.tsx +0 -230
  154. package/src/stories/pattern-centered-conversation.stories.tsx +0 -93
  155. package/src/stories/pattern-docked-widget.stories.tsx +0 -93
  156. package/src/stories/pattern-empty-state.stories.tsx +0 -76
  157. package/src/stories/prompt-input-variants.stories.tsx +0 -456
  158. package/src/stories/streaming-response.stories.tsx +0 -438
  159. package/src/stories/theme-editor.stories.tsx +0 -16
  160. package/src/stories/token-reference.stories.tsx +0 -18
  161. package/src/stories/typography.stories.tsx +0 -78
  162. package/src/ui/avatar.stories.tsx +0 -103
  163. package/src/ui/badge.stories.tsx +0 -86
  164. package/src/ui/button.stories.tsx +0 -145
  165. package/src/ui/collapsible.stories.tsx +0 -69
  166. package/src/ui/dropdown.stories.tsx +0 -59
  167. package/src/ui/hover-card.stories.tsx +0 -77
  168. package/src/ui/hover-card.test.tsx +0 -30
  169. package/src/ui/overlay.stories.tsx +0 -115
  170. package/src/ui/popover.stories.tsx +0 -81
  171. package/src/ui/popover.test.tsx +0 -99
  172. package/src/ui/resizable.stories.tsx +0 -236
  173. package/src/ui/scroll-area.stories.tsx +0 -50
  174. package/src/ui/separator.stories.tsx +0 -81
  175. package/src/ui/skeleton.stories.tsx +0 -337
  176. package/src/ui/switch.stories.tsx +0 -51
  177. package/src/ui/switch.test.tsx +0 -59
  178. package/src/ui/textarea.stories.tsx +0 -76
  179. package/src/ui/tooltip.stories.tsx +0 -74
@@ -1,237 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { createSignal, onMount, type JSX } from 'solid-js';
3
- import './tasks';
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
- import type { TasksCardData } from '../components/tasks-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-tasks': JSX.HTMLAttributes<HTMLElement> & {
13
- heading?: string;
14
- 'card-id'?: string;
15
- ref?: (el: HTMLElement) => void;
16
- };
17
- }
18
- }
19
- }
20
-
21
- type TasksEl = HTMLElement & { data?: TasksCardData; 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-tasks>, sets `.data`, logs the emitted CardEvent under the render. */
28
- function TasksDemo(props: { def: TasksCardData; cardId: string; heading?: string }) {
29
- const [log, setLog] = createSignal<CardEvent[]>([]);
30
- let el: TasksEl | 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-tasks ref={(e) => (el = e as TasksEl)} 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 PLAN: TasksCardData = {
62
- mode: 'select',
63
- selectAll: true,
64
- confirmLabel: 'Run selected',
65
- tasks: [
66
- { id: 'lint', label: 'Run linter', checked: true },
67
- { id: 'test', label: 'Run unit tests', checked: true },
68
- { id: 'build', label: 'Build production bundle' },
69
- { id: 'deploy', label: 'Deploy to staging', description: 'Reversible; staging only' },
70
- ],
71
- };
72
-
73
- const REQUIRE_ONE: TasksCardData = {
74
- confirmLabel: 'Apply',
75
- allowEmpty: false,
76
- tasks: [
77
- { id: 'cache', label: 'Clear the CDN cache' },
78
- { id: 'reindex', label: 'Rebuild the search index' },
79
- { id: 'restart', label: 'Restart the workers' },
80
- ],
81
- };
82
-
83
- const BOUNDED: TasksCardData = {
84
- heading: 'Pick up to 2 reviewers',
85
- confirmLabel: 'Request review',
86
- min: 1,
87
- max: 2,
88
- tasks: [
89
- { id: 'ana', label: 'Ana' },
90
- { id: 'ben', label: 'Ben' },
91
- { id: 'cat', label: 'Cat' },
92
- { id: 'dan', label: 'Dan' },
93
- ],
94
- };
95
-
96
- const WITH_DESCRIPTIONS: TasksCardData = {
97
- selectAll: true,
98
- confirmLabel: 'Run cleanup',
99
- tasks: [
100
- { id: 'tmp', label: 'Delete temp files', description: 'Frees ~2.1 GB; safe to remove' },
101
- { id: 'logs', label: 'Rotate logs', description: 'Archives logs older than 30 days' },
102
- { id: 'orphans', label: 'Prune orphaned blobs', description: 'Unreferenced uploads only' },
103
- ],
104
- };
105
-
106
- const HEADING_MAP: Record<string, string | undefined> = {
107
- 'card-plan': 'Approve the plan steps',
108
- 'card-require': 'Choose maintenance steps',
109
- 'card-bounded': undefined,
110
- 'card-desc': 'Storage cleanup',
111
- };
112
-
113
- const HTML_SNIPPET = (def: TasksCardData, cardId: string) => {
114
- const heading = HEADING_MAP[cardId];
115
- return `<kai-tasks${heading ? ` heading="${heading}"` : ''}></kai-tasks>
116
- <script type="module">
117
- import '@kitn.ai/ui/elements'; // registers the custom elements
118
-
119
- const el = document.querySelector('kai-tasks');
120
- // \`data\` is the CardEnvelope.data (set as a property).
121
- el.data = ${JSON.stringify(def, null, 2)};
122
-
123
- // Toggling rows is local; only CONFIRM emits — a single bubbling \`kai-card\` event.
124
- el.addEventListener('kai-card', (e) => {
125
- const ev = e.detail; // { kind:'submit', cardId, data:{ selected } } | ...
126
- if (ev.kind === 'submit') console.log('selected', ev.data.selected);
127
- });
128
- </script>`;
129
- };
130
-
131
- const meta = {
132
- title: 'Generative UI/Cards/kai-tasks',
133
- tags: ['autodocs'],
134
- argTypes: argTypesFor('kai-tasks'),
135
- parameters: {
136
- layout: 'padded',
137
- docs: {
138
- description: specDescription('kai-tasks', [
139
- "`<kai-tasks>` is a **selectable** task/plan list (set via the `data` **property**): checkbox rows + an optional select-all + a confirm button. The user picks a subset, confirms, and the card emits the Card contract's **`submit`** verb up a bubbling **`kai-card`** CustomEvent of `{ kind: 'submit', cardId, data: { selected } }` — the checked ids in **input order**. Toggling rows is local UI state; **only the final confirm emits** (the wire stays quiet, the result atomic).",
140
- '**Anatomy:** `<kai-card>` chrome (optional heading) → **select-all row** (shown when `selectAll:true` and ≥2 toggleable tasks; indeterminate state when partially checked) → **task rows** (one per `tasks[]` item: checkbox + label + optional description; disabled when `max` reached or `task.disabled`) → **card footer** (selected-count label + confirm `<Button>`, disabled until gating passes; replaced by a read-only resolved summary after submission).',
141
- '**Gating:** confirm is enabled when `selectedCount >= (min ?? (allowEmpty ? 0 : 1))` and `<= (max ?? ∞)`. Select-all checks every toggleable (non-`disabled`) row and shows an **indeterminate** (`aria-checked="mixed"`) state when only some are checked. When `max` is reached, unchecked rows become non-toggleable. v1 is **select/approve only** (a future `mode:\'progress\'` is reserved in the schema).',
142
- '**Events** (all frozen Card-contract verbs): `ready` on mount, `submit` on confirm, `error` for a malformed definition (renders the inline `kai-card` error). It **never invents events**. The same shapes flow over the remote iframe transport unchanged.',
143
- ]),
144
- },
145
- },
146
- } satisfies Meta;
147
-
148
- export default meta;
149
- type Story = StoryObj;
150
-
151
- /** Select a plan (select-all on, a few pre-checked). */
152
- export const SelectAPlan: Story = {
153
- render: () => <TasksDemo def={PLAN} cardId="card-plan" heading="Approve the plan steps" />,
154
- parameters: { docs: { source: { code: HTML_SNIPPET(PLAN, 'card-plan'), language: 'html' } } },
155
- };
156
-
157
- /** Require at least one (`allowEmpty:false`, the default) — confirm stays disabled until a row is checked. */
158
- export const RequireAtLeastOne: Story = {
159
- render: () => <TasksDemo def={REQUIRE_ONE} cardId="card-require" heading="Choose maintenance steps" />,
160
- parameters: { docs: { source: { code: HTML_SNIPPET(REQUIRE_ONE, 'card-require'), language: 'html' } } },
161
- };
162
-
163
- /** Bounded (`min`/`max`) — confirm gating + max-reached row disabling. */
164
- export const Bounded: Story = {
165
- render: () => <TasksDemo def={BOUNDED} cardId="card-bounded" />,
166
- parameters: { docs: { source: { code: HTML_SNIPPET(BOUNDED, 'card-bounded'), language: 'html' } } },
167
- };
168
-
169
- /** Rows with secondary descriptions (linked via aria-describedby). */
170
- export const WithDescriptions: Story = {
171
- render: () => <TasksDemo def={WITH_DESCRIPTIONS} cardId="card-desc" heading="Storage cleanup" />,
172
- parameters: { docs: { source: { code: HTML_SNIPPET(WITH_DESCRIPTIONS, 'card-desc'), language: 'html' } } },
173
- };
174
-
175
- const RESOLVED_TASKS: TasksCardData = {
176
- confirmLabel: 'Export',
177
- tasks: [
178
- { id: 'sum', label: 'Executive summary' },
179
- { id: 'data', label: 'Raw data' },
180
- { id: 'charts', label: 'Charts' },
181
- ],
182
- };
183
-
184
- /** The card after the user confirmed their selection — shows "Selected 2 of 3", no checkboxes or button. */
185
- export const Resolved: Story = {
186
- name: 'Resolved (read-only)',
187
- render: () => {
188
- let el: TasksEl | undefined;
189
- onMount(() => {
190
- if (!el) return;
191
- el.data = RESOLVED_TASKS;
192
- el.resolution = { kind: 'submit', data: { selected: ['sum', 'charts'] } };
193
- });
194
- return (
195
- <Frame>
196
- <kai-tasks ref={(e) => (el = e as TasksEl)} card-id="card-resolved-tasks" heading="Export report" />
197
- </Frame>
198
- );
199
- },
200
- parameters: {
201
- docs: {
202
- source: {
203
- code: `<kai-tasks heading="Export report"></kai-tasks>
204
- <script type="module">
205
- import '@kitn.ai/ui/elements';
206
- const el = document.querySelector('kai-tasks');
207
- el.data = ${JSON.stringify(RESOLVED_TASKS, null, 2)};
208
- // Setting .resolution renders the chromed read-only view — no checkboxes or confirm button.
209
- el.resolution = { kind: 'submit', data: { selected: ['sum', 'charts'] } };
210
- </script>`,
211
- language: 'html',
212
- },
213
- },
214
- },
215
- };
216
-
217
- /** A malformed `data` (empty `tasks`) → the inline error state + an `error` event. */
218
- export const ErrorState: Story = {
219
- render: () => <TasksDemo def={{ tasks: [] } as unknown as TasksCardData} cardId="card-bad" />,
220
- parameters: {
221
- docs: {
222
- source: {
223
- code: `<kai-tasks></kai-tasks>
224
- <script type="module">
225
- import '@kitn.ai/ui/elements';
226
- const el = document.querySelector('kai-tasks');
227
- // No tasks → inline error state + an \`error\` event.
228
- el.data = { tasks: [] };
229
- el.addEventListener('kai-card', (e) => {
230
- if (e.detail.kind === 'error') console.warn('tasks error:', e.detail.message);
231
- });
232
- </script>`,
233
- language: 'html',
234
- },
235
- },
236
- },
237
- };
@@ -1,64 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import './register'; // side effect: registers the custom elements
3
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
4
-
5
- // The web components are custom DOM elements, so declare the tags for JSX.
6
- declare module 'solid-js' {
7
- // eslint-disable-next-line @typescript-eslint/no-namespace
8
- namespace JSX {
9
- interface IntrinsicElements {
10
- 'kai-text-shimmer': JSX.HTMLAttributes<HTMLElement> & {
11
- text?: string;
12
- duration?: number;
13
- spread?: number;
14
- };
15
- }
16
- }
17
- }
18
-
19
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
20
- <kai-text-shimmer text="Thinking…" duration="3" spread="20"></kai-text-shimmer>
21
-
22
- <script type="module">
23
- import '@kitn.ai/ui/elements'; // registers the custom elements
24
- </script>`;
25
-
26
- const meta = {
27
- title: 'Components/TextShimmer',
28
- tags: ['autodocs'],
29
- argTypes: argTypesFor('kai-text-shimmer'),
30
- parameters: {
31
- layout: 'fullscreen',
32
- docs: {
33
- description: specDescription('kai-text-shimmer', [
34
- '`<kai-text-shimmer>` is the framework-agnostic **web component** for animated shimmering text — a gradient sweep across a label, isolated in **Shadow DOM**.',
35
- '**When to use:** signalling a quiet, in-progress state ("Thinking…", "Generating…") inline. In SolidJS, use the `TextShimmer` primitive.',
36
- '**Placement:** inline within a status row, toolbar label, or above a streaming content area — it renders as an inline `span` so it flows naturally alongside other text or icons.',
37
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the `text` attribute, and tune `duration` (seconds) and `spread` (gradient width, 5–45).",
38
- 'See the **Code** tab for HTML usage.',
39
- ]),
40
- },
41
- },
42
- } satisfies Meta;
43
-
44
- export default meta;
45
- type Story = StoryObj;
46
-
47
- /** Default shimmer. */
48
- export const Default: Story = {
49
- render: () => (
50
- <div style={{ padding: '24px', 'font-size': '18px' }}>
51
- <kai-text-shimmer text="Thinking…" />
52
- </div>
53
- ),
54
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
55
- };
56
-
57
- /** Faster sweep with a wider gradient spread. */
58
- export const Tuned: Story = {
59
- render: () => (
60
- <div style={{ padding: '24px', 'font-size': '18px' }}>
61
- <kai-text-shimmer text="Generating response…" duration={2} spread={35} />
62
- </div>
63
- ),
64
- };
@@ -1,72 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import './register'; // side effect: registers the custom elements
3
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
4
-
5
- // The web components are custom DOM elements, so declare the tags for JSX.
6
- declare module 'solid-js' {
7
- // eslint-disable-next-line @typescript-eslint/no-namespace
8
- namespace JSX {
9
- interface IntrinsicElements {
10
- 'kai-thinking-bar': JSX.HTMLAttributes<HTMLElement> & {
11
- text?: string;
12
- stoppable?: boolean | string;
13
- 'stop-label'?: string;
14
- 'on:kai-stop'?: (e: CustomEvent) => void;
15
- };
16
- }
17
- }
18
- }
19
-
20
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
21
- <kai-thinking-bar text="Thinking" stoppable stop-label="Answer now"></kai-thinking-bar>
22
-
23
- <script type="module">
24
- import '@kitn.ai/ui/elements'; // registers the custom elements
25
-
26
- document.querySelector('kai-thinking-bar')
27
- .addEventListener('kai-stop', () => console.log('user asked to stop'));
28
- </script>`;
29
-
30
- const meta = {
31
- title: 'Components/ThinkingBar',
32
- tags: ['autodocs'],
33
- argTypes: argTypesFor('kai-thinking-bar'),
34
- parameters: {
35
- layout: 'fullscreen',
36
- docs: {
37
- description: specDescription('kai-thinking-bar', [
38
- '`<kai-thinking-bar>` is the framework-agnostic **web component** for an animated "thinking" indicator with an optional stop affordance — a pure leaf element isolated in **Shadow DOM**. (`<kai-chat>` does not surface this; compose it yourself.)',
39
- '**When to use:** showing that the assistant is reasoning, optionally letting the user interrupt with "Answer now". In SolidJS, use the `ThinkingBar` primitive.',
40
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the `text`/`stop-label` attributes, add the `stoppable` flag to show the stop button, and listen for the `kai-stop` **CustomEvent**.",
41
- 'See the **Code** tab for HTML usage.',
42
- ]),
43
- },
44
- },
45
- } satisfies Meta;
46
-
47
- export default meta;
48
- type Story = StoryObj;
49
-
50
- /** A plain thinking indicator. */
51
- export const Default: Story = {
52
- render: () => (
53
- <div style={{ padding: '24px' }}>
54
- <kai-thinking-bar text="Thinking" />
55
- </div>
56
- ),
57
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
58
- };
59
-
60
- /** Stoppable — shows an "Answer now" affordance that fires a `stop` event. */
61
- export const Stoppable: Story = {
62
- render: () => (
63
- <div style={{ padding: '24px' }}>
64
- <kai-thinking-bar
65
- text="Reasoning"
66
- stoppable
67
- stop-label="Answer now"
68
- on:kai-stop={() => console.log('stop')}
69
- />
70
- </div>
71
- ),
72
- };
@@ -1,89 +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 type { ToolPart } from '../components/tool';
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-tool': JSX.HTMLAttributes<HTMLElement>;
13
- }
14
- }
15
- }
16
-
17
- const completedTool: ToolPart = {
18
- type: 'database_query',
19
- state: 'output-available',
20
- input: { table: 'users', limit: 10 },
21
- output: { rows: 10, ms: 42 },
22
- };
23
-
24
- const runningTool: ToolPart = {
25
- type: 'search',
26
- state: 'input-available',
27
- input: { query: 'kitn docs' },
28
- };
29
-
30
- /** Render the actual `<kai-tool>` custom element with a `tool` property. */
31
- function ToolElement(props: { tool: ToolPart; open?: boolean }) {
32
- let el: (HTMLElement & { tool?: ToolPart; open?: boolean }) | undefined;
33
- onMount(() => {
34
- if (el) {
35
- el.tool = props.tool;
36
- if (props.open) el.open = true;
37
- }
38
- });
39
- return (
40
- <kai-tool ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
41
- );
42
- }
43
-
44
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
45
- <kai-tool id="tool" open></kai-tool>
46
-
47
- <script type="module">
48
- import '@kitn.ai/ui/elements'; // registers the custom elements
49
-
50
- const tool = document.getElementById('tool');
51
- tool.tool = {
52
- type: 'database_query',
53
- state: 'output-available',
54
- input: { table: 'users', limit: 10 },
55
- output: { rows: 10, ms: 42 },
56
- };
57
- </script>`;
58
-
59
- const meta = {
60
- title: 'Components/Tool',
61
- tags: ['autodocs'],
62
- argTypes: argTypesFor('kai-tool'),
63
- parameters: {
64
- layout: 'fullscreen',
65
- docs: {
66
- description: specDescription('kai-tool', [
67
- '`<kai-tool>` is the framework-agnostic **web component** for a single tool-call panel — a collapsible input/output inspector with a state badge — isolated in **Shadow DOM**.',
68
- '**When to use:** rendering an agent/tool-call trace in a non-Solid app. In SolidJS, use the `Tool` primitive directly.',
69
- '**Placement:** inline inside an assistant message row, one element per tool call, below the text content; renders as a block sized to its content.',
70
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the call via the `tool` **property** (`el.tool = {...}`), and add the `open` flag to start it expanded.",
71
- 'See the **Code** tab for HTML usage.',
72
- ]),
73
- },
74
- },
75
- } satisfies Meta;
76
-
77
- export default meta;
78
- type Story = StoryObj;
79
-
80
- /** A completed call with input and output, started expanded. */
81
- export const Completed: Story = {
82
- render: () => <ToolElement tool={completedTool} open />,
83
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
84
- };
85
-
86
- /** A call still awaiting output (collapsed). */
87
- export const Running: Story = {
88
- render: () => <ToolElement tool={runningTool} />,
89
- };
@@ -1,88 +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-voice-input': JSX.HTMLAttributes<HTMLElement> & {
12
- disabled?: boolean | string;
13
- };
14
- }
15
- }
16
- }
17
-
18
- /** Render `<kai-voice-input>` with a stub `transcribe` function-property. */
19
- function VoiceElement(props: { disabled?: boolean }) {
20
- let el: (HTMLElement & { transcribe?: (audio: Blob) => Promise<string> }) | undefined;
21
- onMount(() => {
22
- if (!el) return;
23
- // transcribe MUST be set as a JS property — a value-returning callback
24
- // can't be modelled as an attribute.
25
- el.transcribe = async () => {
26
- await new Promise((r) => setTimeout(r, 400));
27
- return 'transcribed text';
28
- };
29
- el.addEventListener('kai-transcription', (e) => {
30
- const ev = e as CustomEvent<{ text: string }>;
31
- console.log('kai-transcription', ev.detail.text);
32
- });
33
- });
34
- return (
35
- <kai-voice-input
36
- ref={(e) => (el = e as HTMLElement)}
37
- disabled={props.disabled ? true : undefined}
38
- style={{ display: 'inline-block', padding: '40px' }}
39
- />
40
- );
41
- }
42
-
43
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
44
- <kai-voice-input id="voice"></kai-voice-input>
45
-
46
- <script type="module">
47
- import '@kitn.ai/ui/elements'; // registers the custom elements
48
-
49
- const voice = document.getElementById('voice');
50
- // transcribe is a FUNCTION property — your async transcriber
51
- voice.transcribe = async (blob) => {
52
- const text = await myTranscriptionApi(blob);
53
- return text;
54
- };
55
- voice.addEventListener('kai-transcription', (e) => console.log(e.detail.text));
56
- </script>`;
57
-
58
- const meta = {
59
- title: 'Components/VoiceInput',
60
- tags: ['autodocs'],
61
- argTypes: argTypesFor('kai-voice-input'),
62
- parameters: {
63
- layout: 'fullscreen',
64
- docs: {
65
- description: specDescription('kai-voice-input', [
66
- '`<kai-voice-input>` is the framework-agnostic **web component** for a mic button that records and transcribes audio — isolated in **Shadow DOM**. It is the canonical **function-property** element.',
67
- '**When to use:** adding voice dictation to an input in a non-Solid app. In SolidJS, use the `VoiceInput` primitive.',
68
- '**Placement:** inline beside the prompt input field or as an icon button in the composer toolbar; renders as an inline-block sized to the mic button.',
69
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, then set the `transcribe` **function property** (`el.transcribe = async blob => '...'`) — a value-returning callback can't be modelled as an event. It also emits `kai-audio-captured` (raw blob) and `kai-transcription` (text) **CustomEvents**.",
70
- 'See the **Code** tab for HTML usage.',
71
- ]),
72
- },
73
- },
74
- } satisfies Meta;
75
-
76
- export default meta;
77
- type Story = StoryObj;
78
-
79
- /** A working mic button wired to a stub transcriber. */
80
- export const Default: Story = {
81
- render: () => <VoiceElement />,
82
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
83
- };
84
-
85
- /** A disabled mic button (non-interactive). */
86
- export const Disabled: Story = {
87
- render: () => <VoiceElement disabled />,
88
- };
@@ -1,47 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createRoot, createSignal } from 'solid-js';
3
- import { createControllableSignal } from './controllable';
4
-
5
- describe('createControllableSignal', () => {
6
- it('uses the initial value when uncontrolled (controlled accessor is undefined)', () => {
7
- createRoot((dispose) => {
8
- const [value] = createControllableSignal(() => undefined, true);
9
- expect(value()).toBe(true);
10
- dispose();
11
- });
12
- });
13
-
14
- it('updates via the setter when uncontrolled', () => {
15
- createRoot((dispose) => {
16
- const [value, setValue] = createControllableSignal(() => undefined, false);
17
- setValue(true);
18
- expect(value()).toBe(true);
19
- dispose();
20
- });
21
- });
22
-
23
- it('reflects the controlled value and ignores the internal setter while controlled', () => {
24
- createRoot((dispose) => {
25
- const [controlled, setControlled] = createSignal<boolean | undefined>(false);
26
- const [value, setValue] = createControllableSignal(controlled, false);
27
- expect(value()).toBe(false);
28
- setValue(true); // masked while controlled
29
- expect(value()).toBe(false);
30
- setControlled(true); // the controlling value wins
31
- expect(value()).toBe(true);
32
- dispose();
33
- });
34
- });
35
-
36
- it('falls back to the internal value when control is released (becomes undefined)', () => {
37
- createRoot((dispose) => {
38
- const [controlled, setControlled] = createSignal<boolean | undefined>(true);
39
- const [value, setValue] = createControllableSignal(controlled, false);
40
- expect(value()).toBe(true);
41
- setValue(true); // sets internal (masked now)
42
- setControlled(undefined); // uncontrolled → internal shows
43
- expect(value()).toBe(true);
44
- dispose();
45
- });
46
- });
47
- });