@kitnai/chat 0.7.0 → 0.8.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 (214) hide show
  1. package/README.md +9 -9
  2. package/dist/custom-elements.json +1626 -883
  3. package/dist/kitn-chat.es.js +36 -36
  4. package/dist/llms/llms-full.txt +303 -142
  5. package/dist/llms/llms.txt +18 -18
  6. package/dist/schemas/card-envelope.schema.json +14 -0
  7. package/dist/schemas/card-event.schema.json +12 -0
  8. package/dist/schemas/confirm.schema.json +65 -0
  9. package/dist/schemas/embed.schema.json +65 -0
  10. package/dist/schemas/form.result.schema.json +7 -0
  11. package/dist/schemas/form.schema.json +33 -0
  12. package/dist/schemas/link.schema.json +56 -0
  13. package/dist/schemas/task-list.result.schema.json +16 -0
  14. package/dist/schemas/task-list.schema.json +78 -0
  15. package/dist/theme.tokens.css +65 -65
  16. package/dist/tsx-B8rCNbgL.js +1 -0
  17. package/dist/typescript-RycA9KXf.js +1 -0
  18. package/frameworks/react/index.tsx +356 -189
  19. package/frameworks/react/runtime.tsx +2 -2
  20. package/llms-full.txt +303 -142
  21. package/llms.txt +18 -18
  22. package/package.json +5 -2
  23. package/src/components/artifact.stories.tsx +138 -0
  24. package/src/components/artifact.tsx +581 -0
  25. package/src/components/attachments.stories.tsx +7 -8
  26. package/src/components/attachments.tsx +2 -2
  27. package/src/components/card.tsx +110 -0
  28. package/src/components/chain-of-thought.stories.tsx +7 -8
  29. package/src/components/chat-container.stories.tsx +7 -8
  30. package/src/components/chat-container.tsx +4 -0
  31. package/src/components/checkpoint.stories.tsx +7 -8
  32. package/src/components/checkpoint.tsx +3 -0
  33. package/src/components/code-block.stories.tsx +8 -9
  34. package/src/components/code-block.tsx +5 -2
  35. package/src/components/component-meta.json +3419 -0
  36. package/src/components/confirm-card.stories.tsx +74 -0
  37. package/src/components/confirm-card.tsx +299 -0
  38. package/src/components/context.stories.tsx +7 -8
  39. package/src/components/conversation-item.stories.tsx +7 -8
  40. package/src/components/conversation-item.tsx +2 -2
  41. package/src/components/conversation-list.stories.tsx +7 -8
  42. package/src/components/conversation-list.tsx +1 -1
  43. package/src/components/embed.tsx +196 -0
  44. package/src/components/empty.stories.tsx +8 -9
  45. package/src/components/feedback-bar.stories.tsx +7 -8
  46. package/src/components/file-tree.stories.tsx +73 -0
  47. package/src/components/file-tree.tsx +383 -0
  48. package/src/components/file-upload.stories.tsx +7 -8
  49. package/src/components/form-widgets.tsx +461 -0
  50. package/src/components/form.tsx +796 -0
  51. package/src/components/image.stories.tsx +7 -8
  52. package/src/components/link-card.tsx +194 -0
  53. package/src/components/loader.stories.tsx +7 -8
  54. package/src/components/markdown.stories.tsx +7 -8
  55. package/src/components/message-narrow.stories.tsx +12 -13
  56. package/src/components/message-skills.stories.tsx +16 -17
  57. package/src/components/message.stories.tsx +17 -18
  58. package/src/components/model-switcher.stories.tsx +7 -8
  59. package/src/components/prompt-input.stories.tsx +8 -9
  60. package/src/components/prompt-suggestion.stories.tsx +7 -8
  61. package/src/components/prompt-suggestion.tsx +3 -3
  62. package/src/components/reasoning.stories.tsx +7 -8
  63. package/src/components/scroll-button.stories.tsx +7 -8
  64. package/src/components/slash-command.stories.tsx +8 -9
  65. package/src/components/slash-command.tsx +2 -2
  66. package/src/components/source.stories.tsx +7 -8
  67. package/src/components/source.tsx +1 -1
  68. package/src/components/task-list-card.stories.tsx +78 -0
  69. package/src/components/task-list-card.tsx +388 -0
  70. package/src/components/text-shimmer.stories.tsx +7 -8
  71. package/src/components/thinking-bar.stories.tsx +7 -8
  72. package/src/components/tool.stories.tsx +7 -8
  73. package/src/components/tool.tsx +2 -2
  74. package/src/components/voice-input.stories.tsx +7 -8
  75. package/src/elements/artifact.stories.tsx +291 -0
  76. package/src/elements/artifact.tsx +72 -0
  77. package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -20
  78. package/src/elements/attachments.tsx +4 -4
  79. package/src/elements/card.stories.tsx +118 -0
  80. package/src/elements/card.tsx +40 -0
  81. package/src/elements/catalog.stories.tsx +491 -0
  82. package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -22
  83. package/src/elements/chain-of-thought.tsx +3 -3
  84. package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -19
  85. package/src/elements/chat-scope-picker.tsx +4 -4
  86. package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +15 -23
  87. package/src/elements/chat-workspace.tsx +2 -2
  88. package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +12 -20
  89. package/src/elements/chat.tsx +2 -2
  90. package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -20
  91. package/src/elements/checkpoint.tsx +8 -4
  92. package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -19
  93. package/src/elements/code-block.tsx +3 -3
  94. package/src/elements/compiled.css +1 -1
  95. package/src/elements/composed-shell.stories.tsx +316 -0
  96. package/src/elements/confirm-card.stories.tsx +186 -0
  97. package/src/elements/confirm-card.tsx +45 -0
  98. package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -19
  99. package/src/elements/context-meter.tsx +3 -3
  100. package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +12 -20
  101. package/src/elements/conversation-list.tsx +2 -2
  102. package/src/elements/css.ts +1 -1
  103. package/src/elements/define.tsx +10 -10
  104. package/src/elements/element-meta.json +1379 -733
  105. package/src/elements/element-types.d.ts +251 -125
  106. package/src/elements/embed.stories.tsx +197 -0
  107. package/src/elements/embed.tsx +35 -0
  108. package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -21
  109. package/src/elements/empty.tsx +3 -3
  110. package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -20
  111. package/src/elements/feedback-bar.tsx +4 -4
  112. package/src/elements/file-tree.stories.tsx +133 -0
  113. package/src/elements/file-tree.tsx +52 -0
  114. package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -21
  115. package/src/elements/file-upload.tsx +4 -4
  116. package/src/elements/form.stories.tsx +204 -0
  117. package/src/elements/form.tsx +37 -0
  118. package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -19
  119. package/src/elements/image.tsx +3 -3
  120. package/src/elements/link-card.stories.tsx +193 -0
  121. package/src/elements/link-card.tsx +34 -0
  122. package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -20
  123. package/src/elements/loader.tsx +3 -3
  124. package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -19
  125. package/src/elements/markdown.tsx +3 -3
  126. package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -19
  127. package/src/elements/message-skills.tsx +3 -3
  128. package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -21
  129. package/src/elements/message.tsx +5 -5
  130. package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -19
  131. package/src/elements/model-switcher.tsx +5 -5
  132. package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +14 -22
  133. package/src/elements/prompt-input.tsx +3 -3
  134. package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -22
  135. package/src/elements/prompt-suggestions.tsx +4 -4
  136. package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -19
  137. package/src/elements/reasoning.tsx +4 -4
  138. package/src/elements/register.ts +11 -1
  139. package/src/elements/resizable.stories.tsx +200 -0
  140. package/src/elements/resizable.tsx +264 -0
  141. package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -19
  142. package/src/elements/response-stream.tsx +4 -4
  143. package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -20
  144. package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -21
  145. package/src/elements/source.tsx +5 -5
  146. package/src/elements/styles.css +140 -1
  147. package/src/elements/task-list-card.stories.tsx +194 -0
  148. package/src/elements/task-list-card.tsx +40 -0
  149. package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -19
  150. package/src/elements/text-shimmer.tsx +3 -3
  151. package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -20
  152. package/src/elements/thinking-bar.tsx +5 -5
  153. package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -19
  154. package/src/elements/tool.tsx +3 -3
  155. package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -19
  156. package/src/elements/voice-input.tsx +4 -4
  157. package/src/index.ts +94 -2
  158. package/src/primitives/card-contract.ts +60 -0
  159. package/src/primitives/card-host.tsx +35 -0
  160. package/src/primitives/card-routing.ts +79 -0
  161. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  162. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  163. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  164. package/src/primitives/card-schemas/embed.schema.json +65 -0
  165. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  166. package/src/primitives/card-schemas/form.schema.json +33 -0
  167. package/src/primitives/card-schemas/link.schema.json +56 -0
  168. package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
  169. package/src/primitives/card-schemas/task-list.schema.json +78 -0
  170. package/src/primitives/card-validate.ts +95 -0
  171. package/src/primitives/embed-providers.ts +254 -0
  172. package/src/primitives/highlighter.ts +4 -0
  173. package/src/primitives/link-preview.ts +87 -0
  174. package/src/primitives/pdf-preview.ts +121 -0
  175. package/src/stories/chat-panel-layout.stories.tsx +2 -1
  176. package/src/stories/chat-scene.tsx +22 -21
  177. package/src/stories/checkpoint-restore.stories.tsx +10 -10
  178. package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
  179. package/src/stories/conversation-with-sources.stories.tsx +7 -7
  180. package/src/stories/docs/Accessibility.mdx +2 -2
  181. package/src/stories/docs/ForAIAgents.mdx +3 -3
  182. package/src/stories/docs/GettingStarted.mdx +2 -2
  183. package/src/stories/docs/Installation.mdx +2 -2
  184. package/src/stories/docs/Integrations.mdx +29 -29
  185. package/src/stories/docs/Introduction.mdx +3 -3
  186. package/src/stories/docs/Theming.mdx +2 -2
  187. package/src/stories/docs/element-controls.ts +32 -0
  188. package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
  189. package/src/stories/examples/ChoosingComponents.mdx +94 -0
  190. package/src/stories/examples/sample-data.ts +79 -0
  191. package/src/stories/message-actions.stories.tsx +13 -13
  192. package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
  193. package/src/stories/pattern-docked-widget.stories.tsx +1 -1
  194. package/src/stories/pattern-empty-state.stories.tsx +3 -3
  195. package/src/stories/prompt-input-variants.stories.tsx +13 -13
  196. package/src/stories/streaming-response.stories.tsx +3 -3
  197. package/src/stories/typography.stories.tsx +4 -4
  198. package/src/ui/avatar.stories.tsx +7 -8
  199. package/src/ui/badge.stories.tsx +7 -8
  200. package/src/ui/button.stories.tsx +8 -9
  201. package/src/ui/button.tsx +1 -0
  202. package/src/ui/collapsible.stories.tsx +6 -7
  203. package/src/ui/dropdown.stories.tsx +6 -7
  204. package/src/ui/hover-card.stories.tsx +6 -7
  205. package/src/ui/resizable.stories.tsx +74 -9
  206. package/src/ui/resizable.tsx +351 -71
  207. package/src/ui/scroll-area.stories.tsx +6 -7
  208. package/src/ui/scroll-area.tsx +3 -1
  209. package/src/ui/separator.stories.tsx +7 -8
  210. package/src/ui/skeleton.stories.tsx +7 -8
  211. package/src/ui/textarea.stories.tsx +6 -7
  212. package/src/ui/tooltip.stories.tsx +8 -9
  213. package/theme.css +65 -65
  214. package/src/stories/docs/element-spec.tsx +0 -86
@@ -0,0 +1,196 @@
1
+ import {
2
+ type JSX,
3
+ splitProps,
4
+ createSignal,
5
+ createEffect,
6
+ createMemo,
7
+ on,
8
+ onMount,
9
+ Show,
10
+ } from 'solid-js';
11
+ import { cn } from '../utils/cn';
12
+ import { Play, Video, ExternalLink, TriangleAlert } from 'lucide-solid';
13
+ import type { CardEvent } from '../primitives/card-contract';
14
+ import {
15
+ type EmbedCardData,
16
+ type ResolvedEmbed,
17
+ resolveEmbed,
18
+ watchUrl,
19
+ providerLabel,
20
+ aspectRatioValue,
21
+ } from '../primitives/embed-providers';
22
+
23
+ export interface EmbedProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onError'> {
24
+ /** The card id correlating every emitted event. */
25
+ cardId: string;
26
+ /** The embed payload (data-down). */
27
+ data: EmbedCardData;
28
+ /** Emit a contract CardEvent up (host routes it). */
29
+ onEmit?: (event: CardEvent) => void;
30
+ }
31
+
32
+ /**
33
+ * `Embed` — a privacy-first lazy media facade. Initial render is a poster + a play
34
+ * button: NO provider iframe, NO provider JS, NO cookies until the user opts in.
35
+ * On play it swaps in the provider `<iframe>` (youtube-nocookie / vimeo dnt /
36
+ * allowlisted generic). A persistent "Open on {provider}" affordance routes the
37
+ * contract `open` verb so a blocked embed is never a dead end. The only verbs it
38
+ * emits are lifecycle `ready`, `open`, and failure `error`.
39
+ */
40
+ export function Embed(props: EmbedProps): JSX.Element {
41
+ const [local, rest] = splitProps(props, ['cardId', 'data', 'onEmit', 'class']);
42
+
43
+ const emit = (event: CardEvent) => local.onEmit?.(event);
44
+
45
+ const [playing, setPlaying] = createSignal(false);
46
+ const [posterBroken, setPosterBroken] = createSignal(false);
47
+ let playerRegion: HTMLDivElement | undefined;
48
+
49
+ // Resolve the provider URL/poster/sandbox; capture a resolution error to render inline.
50
+ const resolved = createMemo<{ ok: true; value: ResolvedEmbed } | { ok: false; error: string }>(
51
+ () => {
52
+ try {
53
+ return { ok: true, value: resolveEmbed(local.data) };
54
+ } catch (e) {
55
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
56
+ }
57
+ },
58
+ );
59
+
60
+ const ratio = createMemo(() => aspectRatioValue(local.data.aspectRatio));
61
+ const titleText = () => local.data.title ?? 'Embedded video';
62
+ const poster = createMemo(() =>
63
+ resolved().ok && !posterBroken() ? (resolved() as { value: ResolvedEmbed }).value.posterUrl : undefined,
64
+ );
65
+ const watch = createMemo(() => watchUrl(local.data));
66
+ const label = () => providerLabel(local.data.provider);
67
+
68
+ onMount(() => emit({ kind: 'ready', cardId: local.cardId }));
69
+
70
+ // Resolution failure → a single `error` event (defense in depth; the schema allOf
71
+ // and the origin allowlist should catch most of these earlier).
72
+ createEffect(
73
+ on(resolved, (r) => {
74
+ if (!r.ok) emit({ kind: 'error', cardId: local.cardId, message: r.error });
75
+ }),
76
+ );
77
+
78
+ // Reset facade state when the payload changes.
79
+ createEffect(
80
+ on(
81
+ () => local.data,
82
+ () => {
83
+ setPlaying(false);
84
+ setPosterBroken(false);
85
+ },
86
+ { defer: true },
87
+ ),
88
+ );
89
+
90
+ const play = () => {
91
+ if (!resolved().ok) return;
92
+ setPlaying(true);
93
+ // Move focus into the player region for SR/keyboard continuity.
94
+ queueMicrotask(() => playerRegion?.focus());
95
+ };
96
+ const onPlayKeyDown = (e: KeyboardEvent) => {
97
+ if (e.key === 'Enter' || e.key === ' ') {
98
+ e.preventDefault();
99
+ play();
100
+ }
101
+ };
102
+ const openOnProvider = () => {
103
+ const w = watch();
104
+ if (w) emit({ kind: 'open', cardId: local.cardId, url: w, target: 'tab' });
105
+ };
106
+
107
+ return (
108
+ <div
109
+ class={cn('overflow-hidden rounded-xl border border-border bg-card text-card-foreground', local.class)}
110
+ {...rest}
111
+ >
112
+ <div class="relative w-full bg-black" style={{ 'aspect-ratio': ratio() }}>
113
+ <Show
114
+ when={resolved().ok}
115
+ fallback={
116
+ <div
117
+ role="img"
118
+ aria-label="Can't load this video"
119
+ class="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-muted-foreground"
120
+ >
121
+ <TriangleAlert size={28} aria-hidden="true" />
122
+ <span>Can't load this video</span>
123
+ </div>
124
+ }
125
+ >
126
+ <Show
127
+ when={playing()}
128
+ fallback={
129
+ <button
130
+ type="button"
131
+ aria-label={`Play ${local.data.title ?? 'video'}`}
132
+ class="group absolute inset-0 flex h-full w-full items-center justify-center outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
133
+ onClick={() => play()}
134
+ onKeyDown={onPlayKeyDown}
135
+ >
136
+ {/* Poster (or a neutral placeholder when absent/broken). */}
137
+ <Show
138
+ when={poster()}
139
+ fallback={
140
+ <div
141
+ class="absolute inset-0 bg-gradient-to-br from-zinc-800 to-zinc-950"
142
+ aria-hidden="true"
143
+ />
144
+ }
145
+ >
146
+ <img
147
+ src={poster()}
148
+ alt={local.data.title ?? ''}
149
+ class="absolute inset-0 h-full w-full object-cover"
150
+ onError={() => setPosterBroken(true)}
151
+ />
152
+ </Show>
153
+ <span
154
+ class="relative flex h-14 w-14 items-center justify-center rounded-full bg-black/60 text-white shadow-lg transition-transform motion-safe:group-hover:scale-110"
155
+ aria-hidden="true"
156
+ >
157
+ <Play size={26} fill="currentColor" />
158
+ </span>
159
+ </button>
160
+ }
161
+ >
162
+ <div ref={(el) => (playerRegion = el)} tabindex="-1" class="absolute inset-0 outline-none">
163
+ <iframe
164
+ src={(resolved() as { value: ResolvedEmbed }).value.embedUrl}
165
+ title={titleText()}
166
+ sandbox={(resolved() as { value: ResolvedEmbed }).value.sandbox}
167
+ allow={(resolved() as { value: ResolvedEmbed }).value.allow}
168
+ allowfullscreen
169
+ class="absolute inset-0 h-full w-full border-0"
170
+ />
171
+ </div>
172
+ </Show>
173
+ </Show>
174
+ </div>
175
+ {/* Footer: title + the always-available "Open on {provider}" fallback affordance. */}
176
+ <div class="flex items-center justify-between gap-2 px-3 py-2">
177
+ <span class="flex min-w-0 items-center gap-1.5 text-sm text-foreground">
178
+ <Video size={14} class="shrink-0 opacity-70" aria-hidden="true" />
179
+ <span class="truncate">{local.data.title ?? label()}</span>
180
+ </span>
181
+ <Show when={watch()}>
182
+ <button
183
+ type="button"
184
+ class="inline-flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
185
+ onClick={() => openOnProvider()}
186
+ >
187
+ Open on {label()}
188
+ <ExternalLink size={12} aria-hidden="true" />
189
+ </button>
190
+ </Show>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ export type { EmbedCardData } from '../primitives/embed-providers';
@@ -10,6 +10,7 @@ import { PromptInput, PromptInputTextarea, PromptInputActions } from './prompt-i
10
10
  import {
11
11
  FolderPlus, MessageCircleQuestion, Inbox, Search, Sparkles, FileText, ArrowUp, Plus, Upload,
12
12
  } from 'lucide-solid';
13
+ import { componentDescription } from '../stories/docs/element-controls';
13
14
 
14
15
  /**
15
16
  * Story for the compound `Empty` family. `Empty` is the root container; the
@@ -24,14 +25,12 @@ const meta = {
24
25
  parameters: {
25
26
  layout: 'padded',
26
27
  docs: {
27
- description: {
28
- component: [
29
- 'A composable empty-state block (modeled on shadcn/ui `Empty`): a centered media tile, title, description, and a content slot for actions or suggestions. Token-driven styling; no border by default.',
30
- '**When to use:** when a region has nothing to show yet an empty list/inbox, no search results, a blank chat, or a drop zone and you want to guide the user toward a next action.',
31
- '**How to use:** compose `Empty > EmptyHeader (EmptyMedia + EmptyTitle + EmptyDescription)` and an optional `EmptyContent` for buttons or prompt suggestions. Set `EmptyMedia` `variant` to `icon` for a muted tile or `default` for a bare slot (avatar/illustration). Add `border border-dashed` via `class` for a card.',
32
- '**Placement:** empty lists/sidebars, search-result panes, blank chat launch states, and file drop zones.',
33
- ].join('\n\n'),
34
- },
28
+ description: componentDescription([
29
+ 'A composable empty-state block (modeled on shadcn/ui `Empty`): a centered media tile, title, description, and a content slot for actions or suggestions. Token-driven styling; no border by default.',
30
+ '**When to use:** when a region has nothing to show yet — an empty list/inbox, no search results, a blank chat, or a drop zone and you want to guide the user toward a next action.',
31
+ '**How to use:** compose `Empty > EmptyHeader (EmptyMedia + EmptyTitle + EmptyDescription)` and an optional `EmptyContent` for buttons or prompt suggestions. Set `EmptyMedia` `variant` to `icon` for a muted tile or `default` for a bare slot (avatar/illustration). Add `border border-dashed` via `class` for a card.',
32
+ '**Placement:** empty lists/sidebars, search-result panes, blank chat launch states, and file drop zones.',
33
+ ]),
35
34
  controls: { exclude: ['use:eventListener'] },
36
35
  },
37
36
  },
@@ -346,7 +345,7 @@ export const WithInput: Story = {
346
345
  <PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')} class="w-full">
347
346
  <PromptInputTextarea placeholder="Ask anything..." class="min-h-[44px] px-3 pt-2.5" />
348
347
  <PromptInputActions class="justify-end px-2 pb-2">
349
- <Button size="icon-sm" class="rounded-full" disabled={!value().trim()}>
348
+ <Button size="icon-sm" class="rounded-full" aria-label="Send message" disabled={!value().trim()}>
350
349
  <ArrowUp class="size-4" />
351
350
  </Button>
352
351
  </PromptInputActions>
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { fn } from 'storybook/test';
3
3
  import { FeedbackBar } from './feedback-bar';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  const meta = {
6
7
  title: 'Components/FeedbackBar',
@@ -10,14 +11,12 @@ const meta = {
10
11
  layout: 'padded',
11
12
  docs: {
12
13
  controls: { exclude: ['use:eventListener'] },
13
- description: {
14
- component: [
15
- 'An inline bar that prompts the user to rate a response, with thumbs-up / thumbs-down actions and a dismiss button.',
16
- '**When to use:** after an assistant message, to collect quick helpful / not-helpful feedback on the answer.',
17
- '**How to use:** set a `title`, optionally pass an `icon`, and wire `onHelpful`, `onNotHelpful`, and `onClose` to capture the rating or hide the bar.',
18
- '**Placement:** directly beneath a completed assistant message, or in a message action row.',
19
- ].join('\n\n'),
20
- },
14
+ description: componentDescription([
15
+ 'An inline bar that prompts the user to rate a response, with thumbs-up / thumbs-down actions and a dismiss button.',
16
+ '**When to use:** after an assistant message, to collect quick helpful / not-helpful feedback on the answer.',
17
+ '**How to use:** set a `title`, optionally pass an `icon`, and wire `onHelpful`, `onNotHelpful`, and `onClose` to capture the rating or hide the bar.',
18
+ '**Placement:** directly beneath a completed assistant message, or in a message action row.',
19
+ ]),
21
20
  },
22
21
  },
23
22
  argTypes: {
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal } from 'solid-js';
3
+ import { FileTree, type FileTreeFile } from './file-tree';
4
+ import { componentDescription } from '../stories/docs/element-controls';
5
+
6
+ const FILES: FileTreeFile[] = [
7
+ { path: 'index.html', type: 'html' },
8
+ { path: 'about.html', type: 'html' },
9
+ { path: 'css/site.css', type: 'other', language: 'css' },
10
+ { path: 'src/app.ts', type: 'other', language: 'ts' },
11
+ { path: 'src/lib/format.ts', type: 'other', language: 'ts' },
12
+ { path: 'src/lib/parse.ts', type: 'other', language: 'ts' },
13
+ { path: 'assets/logo.svg', type: 'image' },
14
+ { path: 'assets/report.pdf', type: 'pdf' },
15
+ ];
16
+
17
+ const meta = {
18
+ title: 'Components/FileTree',
19
+ component: FileTree,
20
+ tags: ['autodocs'],
21
+ parameters: {
22
+ layout: 'padded',
23
+ docs: {
24
+ description: componentDescription([
25
+ 'A collapsible, keyboard-navigable file explorer built from a flat list of `/`-delimited paths — nested folders are derived automatically. ARIA `tree`/`treeitem`.',
26
+ '**When to use:** any file/folder tree — the Code tab of `Artifact` uses it, and it is exported for standalone reuse (project explorers, attachment browsers, doc outlines).',
27
+ '**How to use:** pass `files` (`{ path, url?, code?, language?, type? }[]`); folders come from `/` in each path and `type` picks the icon. Control highlight via `activeFile`, initial open folders via `defaultExpanded`, and handle `onSelect(path, file)`. Arrow keys navigate; Enter/Space selects or toggles.',
28
+ '**Placement:** a sidebar/explorer column.',
29
+ ]),
30
+ },
31
+ },
32
+ argTypes: {
33
+ files: { control: false },
34
+ activeFile: { control: 'text' },
35
+ },
36
+ args: { files: FILES, activeFile: 'src/app.ts' },
37
+ render: (args) => (
38
+ <div class="w-64 h-80 overflow-auto rounded-lg border border-border scrollbar-thin">
39
+ <FileTree files={args.files} activeFile={args.activeFile} />
40
+ </div>
41
+ ),
42
+ } satisfies Meta<typeof FileTree>;
43
+
44
+ export default meta;
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ const IMPORT = `import { FileTree } from '@kitnai/chat';`;
48
+ const src = (code: string) => ({
49
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
50
+ });
51
+
52
+ export const Playground: Story = {
53
+ ...src(`<FileTree files={files} activeFile="src/app.ts" onSelect={(p) => set(p)} />`),
54
+ };
55
+
56
+ /** Drives selection from `onSelect` — the controlled pattern. */
57
+ export const Interactive: Story = {
58
+ render: () => {
59
+ const [active, setActive] = createSignal('src/lib/format.ts');
60
+ return (
61
+ <div class="flex flex-col gap-2">
62
+ <div class="w-64 h-80 overflow-auto rounded-lg border border-border scrollbar-thin">
63
+ <FileTree files={FILES} activeFile={active()} onSelect={(p) => setActive(p)} />
64
+ </div>
65
+ <span class="text-sm text-muted-foreground">
66
+ Selected: <code>{active()}</code>
67
+ </span>
68
+ </div>
69
+ );
70
+ },
71
+ ...src(`const [active, setActive] = createSignal('src/lib/format.ts');
72
+ <FileTree files={files} activeFile={active()} onSelect={setActive} />`),
73
+ };