@kitn.ai/ui 0.14.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 (370) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +394 -0
  3. package/dist/bash-InADTalH.js +1 -0
  4. package/dist/core-AYMC6_lb.js +12 -0
  5. package/dist/css-M7EaDHN_.js +1 -0
  6. package/dist/custom-elements.json +4739 -0
  7. package/dist/engine-javascript-vq0WuIJl.js +141 -0
  8. package/dist/github-dark-dimmed-DUshB20C.js +1 -0
  9. package/dist/github-light-JYsPkUQd.js +1 -0
  10. package/dist/host-embed.d.ts +33 -0
  11. package/dist/html-CPZ3oZQ7.js +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/javascript-C25yR2R2.js +1 -0
  14. package/dist/json-DxJze_jm.js +1 -0
  15. package/dist/kitn-chat-provider.d.ts +2 -0
  16. package/dist/kitn-chat-provider.es.js +240 -0
  17. package/dist/kitn-chat.es.js +77 -0
  18. package/dist/llms/llms-full.txt +1018 -0
  19. package/dist/llms/llms.txt +104 -0
  20. package/dist/origin.d.ts +5 -0
  21. package/dist/provider-runtime.d.ts +27 -0
  22. package/dist/schemas/card-envelope.schema.json +14 -0
  23. package/dist/schemas/card-event.schema.json +12 -0
  24. package/dist/schemas/choice.schema.json +70 -0
  25. package/dist/schemas/confirm.schema.json +65 -0
  26. package/dist/schemas/embed.schema.json +65 -0
  27. package/dist/schemas/form.result.schema.json +7 -0
  28. package/dist/schemas/form.schema.json +33 -0
  29. package/dist/schemas/link.schema.json +56 -0
  30. package/dist/schemas/tasks.result.schema.json +16 -0
  31. package/dist/schemas/tasks.schema.json +78 -0
  32. package/dist/svelte--5p79yCD.js +1 -0
  33. package/dist/theme.tokens.css +176 -0
  34. package/dist/tsx-B8rCNbgL.js +1 -0
  35. package/dist/typescript-RycA9KXf.js +1 -0
  36. package/dist/validate.d.ts +4 -0
  37. package/dist/version.d.ts +4 -0
  38. package/dist/vue-BmIZj4XD.js +1 -0
  39. package/dist/wire.d.ts +53 -0
  40. package/frameworks/react/index.tsx +948 -0
  41. package/frameworks/react/runtime.tsx +94 -0
  42. package/llms-full.txt +1018 -0
  43. package/llms.txt +104 -0
  44. package/package.json +141 -0
  45. package/src/components/artifact.stories.tsx +138 -0
  46. package/src/components/artifact.tsx +743 -0
  47. package/src/components/attachments.stories.tsx +303 -0
  48. package/src/components/attachments.test.tsx +64 -0
  49. package/src/components/attachments.tsx +399 -0
  50. package/src/components/card-fallback.tsx +28 -0
  51. package/src/components/card-renderer.tsx +52 -0
  52. package/src/components/card.tsx +110 -0
  53. package/src/components/chain-of-thought.stories.tsx +211 -0
  54. package/src/components/chain-of-thought.tsx +139 -0
  55. package/src/components/chat-container.stories.tsx +187 -0
  56. package/src/components/chat-container.tsx +82 -0
  57. package/src/components/chat-scope-picker.tsx +47 -0
  58. package/src/components/chat-thread.test.tsx +58 -0
  59. package/src/components/chat-thread.tsx +249 -0
  60. package/src/components/checkpoint.stories.tsx +102 -0
  61. package/src/components/checkpoint.tsx +88 -0
  62. package/src/components/choice-card.tsx +569 -0
  63. package/src/components/code-block.stories.tsx +150 -0
  64. package/src/components/code-block.tsx +105 -0
  65. package/src/components/component-meta.json +3735 -0
  66. package/src/components/confirm-card.stories.tsx +74 -0
  67. package/src/components/confirm-card.tsx +315 -0
  68. package/src/components/context.stories.tsx +229 -0
  69. package/src/components/context.test.tsx +133 -0
  70. package/src/components/context.tsx +363 -0
  71. package/src/components/conversation-item.stories.tsx +125 -0
  72. package/src/components/conversation-item.tsx +16 -0
  73. package/src/components/conversation-list.stories.tsx +133 -0
  74. package/src/components/conversation-list.tsx +101 -0
  75. package/src/components/default-input.test.tsx +111 -0
  76. package/src/components/embed.tsx +196 -0
  77. package/src/components/empty.stories.tsx +434 -0
  78. package/src/components/empty.tsx +166 -0
  79. package/src/components/feedback-bar.stories.tsx +138 -0
  80. package/src/components/feedback-bar.test.tsx +103 -0
  81. package/src/components/feedback-bar.tsx +166 -0
  82. package/src/components/file-tree.stories.tsx +73 -0
  83. package/src/components/file-tree.tsx +383 -0
  84. package/src/components/file-upload.stories.tsx +156 -0
  85. package/src/components/file-upload.tsx +161 -0
  86. package/src/components/form-widgets.tsx +461 -0
  87. package/src/components/form.tsx +873 -0
  88. package/src/components/image.stories.tsx +89 -0
  89. package/src/components/image.tsx +67 -0
  90. package/src/components/link-preview.tsx +194 -0
  91. package/src/components/loader.stories.tsx +181 -0
  92. package/src/components/loader.tsx +333 -0
  93. package/src/components/markdown.stories.tsx +180 -0
  94. package/src/components/markdown.tsx +81 -0
  95. package/src/components/message-action-bar.test.tsx +128 -0
  96. package/src/components/message-narrow.stories.tsx +329 -0
  97. package/src/components/message-skills.stories.tsx +211 -0
  98. package/src/components/message-skills.tsx +36 -0
  99. package/src/components/message.stories.tsx +281 -0
  100. package/src/components/message.tsx +227 -0
  101. package/src/components/model-switcher.stories.tsx +123 -0
  102. package/src/components/model-switcher.test.tsx +65 -0
  103. package/src/components/model-switcher.tsx +87 -0
  104. package/src/components/prompt-input.stories.tsx +222 -0
  105. package/src/components/prompt-input.tsx +211 -0
  106. package/src/components/prompt-suggestion.stories.tsx +142 -0
  107. package/src/components/prompt-suggestion.tsx +115 -0
  108. package/src/components/reasoning.stories.tsx +140 -0
  109. package/src/components/reasoning.tsx +156 -0
  110. package/src/components/resizable.test.tsx +226 -0
  111. package/src/components/response-stream.tsx +103 -0
  112. package/src/components/scroll-button.stories.tsx +100 -0
  113. package/src/components/scroll-button.tsx +34 -0
  114. package/src/components/slash-command.stories.tsx +163 -0
  115. package/src/components/slash-command.tsx +232 -0
  116. package/src/components/source-list.test.tsx +152 -0
  117. package/src/components/source.stories.tsx +124 -0
  118. package/src/components/source.tsx +129 -0
  119. package/src/components/tasks-card.stories.tsx +78 -0
  120. package/src/components/tasks-card.tsx +432 -0
  121. package/src/components/text-shimmer.stories.tsx +87 -0
  122. package/src/components/text-shimmer.tsx +37 -0
  123. package/src/components/thinking-bar.stories.tsx +87 -0
  124. package/src/components/thinking-bar.tsx +50 -0
  125. package/src/components/tool.stories.tsx +153 -0
  126. package/src/components/tool.tsx +184 -0
  127. package/src/components/use-card-resolution.ts +28 -0
  128. package/src/components/voice-input.stories.tsx +83 -0
  129. package/src/components/voice-input.tsx +107 -0
  130. package/src/elements/artifact.stories.tsx +506 -0
  131. package/src/elements/artifact.tsx +137 -0
  132. package/src/elements/attachments.stories.tsx +190 -0
  133. package/src/elements/attachments.tsx +136 -0
  134. package/src/elements/card.stories.tsx +113 -0
  135. package/src/elements/card.tsx +40 -0
  136. package/src/elements/cards.stories.tsx +59 -0
  137. package/src/elements/cards.tsx +97 -0
  138. package/src/elements/catalog.stories.tsx +491 -0
  139. package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
  140. package/src/elements/chain-of-thought.stories.tsx +113 -0
  141. package/src/elements/chain-of-thought.tsx +92 -0
  142. package/src/elements/chat-scope-picker.stories.tsx +73 -0
  143. package/src/elements/chat-scope-picker.tsx +36 -0
  144. package/src/elements/chat-types.ts +42 -0
  145. package/src/elements/chat-workspace.stories.tsx +288 -0
  146. package/src/elements/chat-workspace.tsx +161 -0
  147. package/src/elements/chat.stories.tsx +151 -0
  148. package/src/elements/chat.tsx +80 -0
  149. package/src/elements/checkpoint.stories.tsx +72 -0
  150. package/src/elements/checkpoint.tsx +47 -0
  151. package/src/elements/choice.stories.tsx +240 -0
  152. package/src/elements/choice.tsx +48 -0
  153. package/src/elements/code-block.stories.tsx +83 -0
  154. package/src/elements/code-block.tsx +42 -0
  155. package/src/elements/compiled.css +2 -0
  156. package/src/elements/composed-shell.stories.tsx +316 -0
  157. package/src/elements/confirm-card.stories.tsx +227 -0
  158. package/src/elements/confirm-card.tsx +51 -0
  159. package/src/elements/context-meter.stories.tsx +151 -0
  160. package/src/elements/context-meter.tsx +121 -0
  161. package/src/elements/conversation-list.declarative.test.tsx +137 -0
  162. package/src/elements/conversation-list.stories.tsx +243 -0
  163. package/src/elements/conversation-list.tsx +80 -0
  164. package/src/elements/css.ts +5 -0
  165. package/src/elements/default-input.tsx +247 -0
  166. package/src/elements/define.tsx +171 -0
  167. package/src/elements/element-meta.json +3314 -0
  168. package/src/elements/element-types.d.ts +710 -0
  169. package/src/elements/embed.stories.tsx +213 -0
  170. package/src/elements/embed.tsx +35 -0
  171. package/src/elements/empty.stories.tsx +111 -0
  172. package/src/elements/empty.tsx +29 -0
  173. package/src/elements/feedback-bar.stories.tsx +114 -0
  174. package/src/elements/feedback-bar.tsx +61 -0
  175. package/src/elements/file-tree.stories.tsx +134 -0
  176. package/src/elements/file-tree.tsx +52 -0
  177. package/src/elements/file-upload.stories.tsx +82 -0
  178. package/src/elements/file-upload.tsx +44 -0
  179. package/src/elements/form.stories.tsx +248 -0
  180. package/src/elements/form.tsx +43 -0
  181. package/src/elements/framework-usage.json +563 -0
  182. package/src/elements/image.stories.tsx +71 -0
  183. package/src/elements/image.tsx +32 -0
  184. package/src/elements/link-preview.stories.tsx +209 -0
  185. package/src/elements/link-preview.tsx +34 -0
  186. package/src/elements/loader.stories.tsx +88 -0
  187. package/src/elements/loader.tsx +25 -0
  188. package/src/elements/markdown.stories.tsx +76 -0
  189. package/src/elements/markdown.tsx +38 -0
  190. package/src/elements/message-skills.declarative.test.tsx +104 -0
  191. package/src/elements/message-skills.stories.tsx +114 -0
  192. package/src/elements/message-skills.tsx +85 -0
  193. package/src/elements/message.stories.tsx +218 -0
  194. package/src/elements/message.tsx +162 -0
  195. package/src/elements/model-switcher.declarative.test.tsx +130 -0
  196. package/src/elements/model-switcher.stories.tsx +198 -0
  197. package/src/elements/model-switcher.tsx +93 -0
  198. package/src/elements/popover.stories.tsx +80 -0
  199. package/src/elements/popover.tsx +59 -0
  200. package/src/elements/prompt-input-slash-command.test.tsx +128 -0
  201. package/src/elements/prompt-input-stoppable.test.tsx +88 -0
  202. package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
  203. package/src/elements/prompt-input.stories.tsx +331 -0
  204. package/src/elements/prompt-input.tsx +185 -0
  205. package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
  206. package/src/elements/prompt-suggestions.stories.tsx +213 -0
  207. package/src/elements/prompt-suggestions.tsx +94 -0
  208. package/src/elements/reasoning.stories.tsx +77 -0
  209. package/src/elements/reasoning.tsx +50 -0
  210. package/src/elements/register.ts +56 -0
  211. package/src/elements/remote.stories.tsx +314 -0
  212. package/src/elements/remote.tsx +221 -0
  213. package/src/elements/resizable.d.ts +27 -0
  214. package/src/elements/resizable.stories.tsx +426 -0
  215. package/src/elements/resizable.tsx +553 -0
  216. package/src/elements/response-stream.stories.tsx +79 -0
  217. package/src/elements/response-stream.tsx +40 -0
  218. package/src/elements/scroll-button.stories.tsx +202 -0
  219. package/src/elements/scroll-button.test.tsx +148 -0
  220. package/src/elements/scroll-button.tsx +126 -0
  221. package/src/elements/source-list.stories.tsx +143 -0
  222. package/src/elements/source.stories.tsx +88 -0
  223. package/src/elements/source.tsx +120 -0
  224. package/src/elements/styles.css +199 -0
  225. package/src/elements/switch.stories.tsx +56 -0
  226. package/src/elements/switch.tsx +43 -0
  227. package/src/elements/tasks.stories.tsx +237 -0
  228. package/src/elements/tasks.tsx +46 -0
  229. package/src/elements/text-shimmer.stories.tsx +64 -0
  230. package/src/elements/text-shimmer.tsx +28 -0
  231. package/src/elements/thinking-bar.stories.tsx +72 -0
  232. package/src/elements/thinking-bar.tsx +34 -0
  233. package/src/elements/tool.stories.tsx +89 -0
  234. package/src/elements/tool.tsx +23 -0
  235. package/src/elements/voice-input.stories.tsx +88 -0
  236. package/src/elements/voice-input.tsx +41 -0
  237. package/src/index.ts +253 -0
  238. package/src/primitives/card-contract.ts +71 -0
  239. package/src/primitives/card-host.tsx +35 -0
  240. package/src/primitives/card-registry.tsx +67 -0
  241. package/src/primitives/card-resolution.ts +36 -0
  242. package/src/primitives/card-routing.ts +79 -0
  243. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  244. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  245. package/src/primitives/card-schemas/choice.schema.json +70 -0
  246. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  247. package/src/primitives/card-schemas/embed.schema.json +65 -0
  248. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  249. package/src/primitives/card-schemas/form.schema.json +33 -0
  250. package/src/primitives/card-schemas/link.schema.json +56 -0
  251. package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
  252. package/src/primitives/card-schemas/tasks.schema.json +78 -0
  253. package/src/primitives/card-validate.ts +95 -0
  254. package/src/primitives/chat-config.tsx +76 -0
  255. package/src/primitives/controllable.test.ts +47 -0
  256. package/src/primitives/controllable.ts +21 -0
  257. package/src/primitives/embed-providers.ts +254 -0
  258. package/src/primitives/highlighter.ts +157 -0
  259. package/src/primitives/link-preview.ts +87 -0
  260. package/src/primitives/pdf-preview.ts +121 -0
  261. package/src/primitives/use-auto-resize.ts +31 -0
  262. package/src/primitives/use-resize-observer.ts +12 -0
  263. package/src/primitives/use-stick-to-bottom.ts +43 -0
  264. package/src/primitives/use-text-stream.ts +112 -0
  265. package/src/primitives/use-voice-recorder.ts +50 -0
  266. package/src/remote/host-embed.ts +345 -0
  267. package/src/remote/index.ts +2 -0
  268. package/src/remote/origin.ts +30 -0
  269. package/src/remote/provider-runtime.ts +259 -0
  270. package/src/remote/provider.ts +2 -0
  271. package/src/remote/validate.ts +22 -0
  272. package/src/remote/version.ts +12 -0
  273. package/src/remote/wire.ts +48 -0
  274. package/src/stories/chat-panel-layout.stories.tsx +145 -0
  275. package/src/stories/chat-scene.tsx +571 -0
  276. package/src/stories/checkpoint-restore.stories.tsx +256 -0
  277. package/src/stories/context-usage.stories.tsx +212 -0
  278. package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
  279. package/src/stories/conversation-with-sources.stories.tsx +471 -0
  280. package/src/stories/docs/Accessibility.mdx +119 -0
  281. package/src/stories/docs/ChildElements.mdx +269 -0
  282. package/src/stories/docs/ForAIAgents.mdx +162 -0
  283. package/src/stories/docs/GettingStarted.mdx +65 -0
  284. package/src/stories/docs/Installation.mdx +83 -0
  285. package/src/stories/docs/Introduction.mdx +50 -0
  286. package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
  287. package/src/stories/docs/Theming.mdx +85 -0
  288. package/src/stories/docs/element-controls.ts +121 -0
  289. package/src/stories/docs/frameworks/Angular.mdx +245 -0
  290. package/src/stories/docs/frameworks/Html.mdx +227 -0
  291. package/src/stories/docs/frameworks/Overview.mdx +47 -0
  292. package/src/stories/docs/frameworks/React.mdx +227 -0
  293. package/src/stories/docs/frameworks/Solid.mdx +173 -0
  294. package/src/stories/docs/frameworks/Svelte.mdx +194 -0
  295. package/src/stories/docs/frameworks/Vue.mdx +238 -0
  296. package/src/stories/docs/generative-ui-overview.mdx +186 -0
  297. package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
  298. package/src/stories/docs/recipes/Streaming.mdx +68 -0
  299. package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
  300. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  301. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  302. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  303. package/src/stories/docs/theme-editor/presets.ts +64 -0
  304. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  305. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  306. package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
  307. package/src/stories/docs/theme-tokens.tsx +174 -0
  308. package/src/stories/examples/ChoosingComponents.mdx +106 -0
  309. package/src/stories/examples/sample-data.ts +79 -0
  310. package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
  311. package/src/stories/examples/usage/context-usage.ts +591 -0
  312. package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
  313. package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
  314. package/src/stories/examples/usage/empty-state.ts +144 -0
  315. package/src/stories/examples/usage/full-chat-app.ts +277 -0
  316. package/src/stories/examples/usage/index.ts +63 -0
  317. package/src/stories/examples/usage/message-actions.ts +704 -0
  318. package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
  319. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
  320. package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
  321. package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
  322. package/src/stories/examples/usage/streaming-response.ts +687 -0
  323. package/src/stories/examples/usage/types.ts +33 -0
  324. package/src/stories/full-chat.stories.tsx +18 -0
  325. package/src/stories/message-actions.stories.tsx +230 -0
  326. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  327. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  328. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  329. package/src/stories/prompt-input-variants.stories.tsx +456 -0
  330. package/src/stories/streaming-response.stories.tsx +438 -0
  331. package/src/stories/theme-editor.stories.tsx +16 -0
  332. package/src/stories/token-reference.stories.tsx +18 -0
  333. package/src/stories/typography.stories.tsx +78 -0
  334. package/src/types.ts +47 -0
  335. package/src/ui/action-icons.ts +52 -0
  336. package/src/ui/avatar.stories.tsx +103 -0
  337. package/src/ui/avatar.tsx +23 -0
  338. package/src/ui/badge.stories.tsx +86 -0
  339. package/src/ui/badge.tsx +21 -0
  340. package/src/ui/button.stories.tsx +145 -0
  341. package/src/ui/button.tsx +38 -0
  342. package/src/ui/collapsible.stories.tsx +69 -0
  343. package/src/ui/collapsible.tsx +125 -0
  344. package/src/ui/dropdown.stories.tsx +59 -0
  345. package/src/ui/dropdown.tsx +196 -0
  346. package/src/ui/hover-card.stories.tsx +77 -0
  347. package/src/ui/hover-card.test.tsx +30 -0
  348. package/src/ui/hover-card.tsx +172 -0
  349. package/src/ui/overlay.stories.tsx +115 -0
  350. package/src/ui/overlay.tsx +158 -0
  351. package/src/ui/popover.stories.tsx +81 -0
  352. package/src/ui/popover.test.tsx +99 -0
  353. package/src/ui/popover.tsx +92 -0
  354. package/src/ui/resizable.stories.tsx +236 -0
  355. package/src/ui/resizable.tsx +576 -0
  356. package/src/ui/scroll-area.stories.tsx +50 -0
  357. package/src/ui/scroll-area.tsx +15 -0
  358. package/src/ui/separator.stories.tsx +81 -0
  359. package/src/ui/separator.tsx +10 -0
  360. package/src/ui/skeleton.stories.tsx +337 -0
  361. package/src/ui/skeleton.tsx +16 -0
  362. package/src/ui/switch.stories.tsx +51 -0
  363. package/src/ui/switch.test.tsx +59 -0
  364. package/src/ui/switch.tsx +62 -0
  365. package/src/ui/textarea.stories.tsx +76 -0
  366. package/src/ui/textarea.tsx +21 -0
  367. package/src/ui/tooltip.stories.tsx +74 -0
  368. package/src/ui/tooltip.tsx +68 -0
  369. package/src/utils/cn.ts +24 -0
  370. package/theme.css +187 -0
@@ -0,0 +1,137 @@
1
+ import { createSignal, onMount, onCleanup } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import { Artifact, type ArtifactFile, type ArtifactTab } from '../components/artifact';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ /** URL the preview iframe frames. Consumer-controlled. */
7
+ src?: string;
8
+ /** Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). */
9
+ files: ArtifactFile[];
10
+ /** Active tab: `preview` (default) or `code`. */
11
+ tab?: ArtifactTab;
12
+ /** Selected file path — syncs the tree highlight, Code source, and preview. */
13
+ activeFile?: string;
14
+ /** iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). */
15
+ sandbox?: string;
16
+ /** Accessible title for the preview iframe. */
17
+ iframeTitle?: string;
18
+ /** Reflects the artifact's own maximized view-state (usually driven by the protocol). */
19
+ maximized?: boolean;
20
+ /** Show the expand-to-fill button (OPT-IN). */
21
+ expandable?: boolean;
22
+ /** Show the open-in-new-tab button (OPT-IN). */
23
+ openInTab?: boolean;
24
+ /** Hide back/forward. */
25
+ noNav?: boolean;
26
+ /** Hide reload. */
27
+ noReload?: boolean;
28
+ /** Hide home. */
29
+ noHome?: boolean;
30
+ /** Hide the address field. */
31
+ noPathField?: boolean;
32
+ /** Hide the Preview|Code toggle. */
33
+ noTabs?: boolean;
34
+ /** Standalone chrome: rounded corners + border (else square, borderless in-panel). */
35
+ standalone?: boolean;
36
+ /** Show the address but make it read-only (visible, nav-tracking, non-editable). */
37
+ readonlyPath?: boolean;
38
+ }
39
+
40
+ interface Events extends Record<string, unknown> {
41
+ /** Fired when the preview navigates. `detail.url` = the new location. */
42
+ 'kai-navigate': { url: string };
43
+ /** Fired when the Preview|Code tab changes. `detail.tab`. */
44
+ 'kai-tab-change': { tab: ArtifactTab };
45
+ /** Fired when a file is selected. `detail.path`. */
46
+ 'kai-file-select': { path: string };
47
+ /** Artifact's own maximize button toggled (consumer-observable; non-bubbling). */
48
+ 'kai-maximize-change': { maximized: boolean };
49
+ }
50
+
51
+ /**
52
+ * `<kai-artifact>` — a framed, switchable generated-artifact viewer: a sandboxed
53
+ * preview iframe with a functional nav toolbar (back · forward · reload · home +
54
+ * editable path field) and a Preview|Code toggle; the Code tab shows a file tree
55
+ * (`<kai-file-tree>`) + the active file's source via `<kai-code-block>`. The
56
+ * component self-navigates the iframe and emits `kai-navigate` / `kai-tab-change` /
57
+ * `kai-file-select`. Designed to FILL its container (e.g. a `<kai-resizable>` panel).
58
+ */
59
+ defineWebComponent<Props, Events>('kai-artifact', {
60
+ src: undefined,
61
+ files: [],
62
+ tab: 'preview',
63
+ activeFile: undefined,
64
+ sandbox: 'allow-scripts allow-forms',
65
+ iframeTitle: undefined,
66
+ maximized: false,
67
+ expandable: false,
68
+ openInTab: false,
69
+ noNav: false,
70
+ noReload: false,
71
+ noHome: false,
72
+ noPathField: false,
73
+ noTabs: false,
74
+ standalone: false,
75
+ readonlyPath: false,
76
+ }, (props, { element, dispatch, flag }) => {
77
+ const [maximized, setMaximized] = createSignal(flag('maximized'));
78
+
79
+ const onMaximizeChange = (next: boolean) => {
80
+ setMaximized(next);
81
+ // 1) The PROTOCOL intent — raw, bubbling + composed (NOT via dispatch()).
82
+ element.dispatchEvent(
83
+ new CustomEvent('kai-maximize-intent', { detail: { requested: next }, bubbles: true, composed: true }),
84
+ );
85
+ // 2) The PUBLIC observable event (non-bubbling, on the host).
86
+ dispatch('kai-maximize-change', { maximized: next });
87
+ };
88
+
89
+ // Authoritative reconcile: the resizable tells us the effective state.
90
+ onMount(() => {
91
+ const onState = (e: Event) => setMaximized((e as CustomEvent<{ maximized: boolean }>).detail.maximized);
92
+ element.addEventListener('kai-maximize-state', onState);
93
+ onCleanup(() => element.removeEventListener('kai-maximize-state', onState));
94
+ });
95
+
96
+ return (
97
+ <>
98
+ {/* The artifact fills its container; the internal column flex (toolbar
99
+ flex-shrink:0, body flex:1/min-height:0) is in the Solid component. Wrap
100
+ in a definite `1fr` grid cell (NOT :host) so the facade's sibling
101
+ portal-mount div can't steal a grid track — see resizable.tsx. */}
102
+ <style>{':host{display:block;height:100%;min-height:0}'}</style>
103
+ <div
104
+ style={{
105
+ display: 'grid',
106
+ 'grid-template-rows': 'minmax(0, 1fr)',
107
+ 'grid-template-columns': 'minmax(0, 1fr)',
108
+ height: '100%',
109
+ 'min-height': '0',
110
+ }}
111
+ >
112
+ <Artifact
113
+ src={props.src}
114
+ files={props.files}
115
+ tab={props.tab}
116
+ activeFile={props.activeFile}
117
+ sandbox={props.sandbox}
118
+ iframeTitle={props.iframeTitle}
119
+ maximized={maximized()}
120
+ expandable={flag('expandable')}
121
+ openInTab={flag('openInTab')}
122
+ showNav={!flag('noNav')}
123
+ showReload={!flag('noReload')}
124
+ showHome={!flag('noHome')}
125
+ showPathField={!flag('noPathField')}
126
+ showTabs={!flag('noTabs')}
127
+ standalone={flag('standalone')}
128
+ readonlyPath={flag('readonlyPath')}
129
+ onMaximizeChange={onMaximizeChange}
130
+ onNavigate={(url) => dispatch('kai-navigate', { url })}
131
+ onTabChange={(tab) => dispatch('kai-tab-change', { tab })}
132
+ onFileSelect={(path) => dispatch('kai-file-select', { path })}
133
+ />
134
+ </div>
135
+ </>
136
+ );
137
+ });
@@ -0,0 +1,190 @@
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 { AttachmentData } from '../components/attachments';
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-attachments': JSX.HTMLAttributes<HTMLElement> & {
13
+ variant?: string;
14
+ 'hover-card'?: boolean | string;
15
+ removable?: boolean | string;
16
+ 'show-media-type'?: boolean | string;
17
+ };
18
+ }
19
+ }
20
+ }
21
+
22
+ // Mixed file/source items (icons + labels) — mirrors Components/Attachments.
23
+ const sampleItems: AttachmentData[] = [
24
+ {
25
+ id: '1',
26
+ type: 'file',
27
+ filename: 'mountain-landscape.jpg',
28
+ mediaType: 'image/jpeg',
29
+ url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
30
+ },
31
+ {
32
+ id: '2',
33
+ type: 'file',
34
+ filename: 'sunset-beach.png',
35
+ mediaType: 'image/png',
36
+ url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
37
+ },
38
+ { id: '3', type: 'file', filename: 'architecture-report.pdf', mediaType: 'application/pdf' },
39
+ { id: '4', type: 'file', filename: 'demo-recording.mp4', mediaType: 'video/mp4' },
40
+ { id: '5', type: 'file', filename: 'podcast-episode.mp3', mediaType: 'audio/mpeg' },
41
+ { id: '6', type: 'source-document', filename: 'SolidJS Documentation', title: 'SolidJS Reactivity Guide', url: 'https://solidjs.com/docs' },
42
+ ];
43
+
44
+ // Image-only items — used for the grid thumbnails + hover-card preview stories.
45
+ const imageItems: AttachmentData[] = [
46
+ {
47
+ id: 'img-1',
48
+ type: 'file',
49
+ filename: 'mountain-landscape.jpg',
50
+ mediaType: 'image/jpeg',
51
+ url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
52
+ },
53
+ {
54
+ id: 'img-2',
55
+ type: 'file',
56
+ filename: 'sunset-beach.png',
57
+ mediaType: 'image/png',
58
+ url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
59
+ },
60
+ ];
61
+
62
+ /** Render `<kai-attachments>` with an `items` property and the given flags. */
63
+ function AttachmentsElement(props: {
64
+ items: AttachmentData[];
65
+ variant?: string;
66
+ removable?: boolean;
67
+ hoverCard?: boolean;
68
+ showMediaType?: boolean;
69
+ }) {
70
+ let el: (HTMLElement & { items?: AttachmentData[] }) | undefined;
71
+ onMount(() => {
72
+ if (!el) return;
73
+ el.items = props.items;
74
+ if (props.variant) el.setAttribute('variant', props.variant);
75
+ if (props.hoverCard) el.setAttribute('hover-card', '');
76
+ if (props.showMediaType) el.setAttribute('show-media-type', '');
77
+ if (props.removable) {
78
+ el.setAttribute('removable', '');
79
+ el.addEventListener('kai-remove', ((e: CustomEvent<{ id: string }>) => {
80
+ el!.items = (el!.items ?? []).filter((x) => x.id !== e.detail.id);
81
+ }) as EventListener);
82
+ }
83
+ });
84
+ return (
85
+ <kai-attachments
86
+ ref={(e) => (el = e as HTMLElement)}
87
+ style={{ display: 'block', padding: '24px', 'max-width': '720px' }}
88
+ />
89
+ );
90
+ }
91
+
92
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
93
+ <kai-attachments id="att" variant="grid" removable></kai-attachments>
94
+
95
+ <script type="module">
96
+ import '@kitn.ai/ui/elements'; // registers the custom elements
97
+
98
+ const att = document.getElementById('att');
99
+ att.items = [
100
+ { id: '1', type: 'file', filename: 'mountain-landscape.jpg',
101
+ mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
102
+ { id: '2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
103
+ ];
104
+
105
+ // events are CustomEvents on the element (they do not bubble)
106
+ att.addEventListener('kai-remove', (e) => {
107
+ att.items = att.items.filter((x) => x.id !== e.detail.id);
108
+ });
109
+ </script>`;
110
+
111
+ const HOVER_SNIPPET = `<!-- inline/list chips with a hover-card image preview -->
112
+ <kai-attachments id="att" variant="inline" hover-card></kai-attachments>
113
+
114
+ <script type="module">
115
+ import '@kitn.ai/ui/elements';
116
+
117
+ const att = document.getElementById('att');
118
+ // image attachments show their thumbnail in the hover card on hover;
119
+ // non-image attachments show their label + media type instead.
120
+ att.items = [
121
+ { id: '1', type: 'file', filename: 'mountain-landscape.jpg',
122
+ mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
123
+ ];
124
+ </script>`;
125
+
126
+ const meta = {
127
+ title: 'Components/Attachments',
128
+ tags: ['autodocs'],
129
+ argTypes: argTypesFor('kai-attachments'),
130
+ parameters: {
131
+ layout: 'fullscreen',
132
+ docs: {
133
+ description: specDescription('kai-attachments', [
134
+ '`<kai-attachments>` is the framework-agnostic **web component** for a set of file/source attachments, and the exemplar for the "collapse a compound primitive to ONE configurable element" pattern: the sub-parts the SolidJS layer composes become attributes here. Isolated in **Shadow DOM**.',
135
+ '**When to use:** rendering attachment chips/tiles in a non-Solid app. In SolidJS, compose the `Attachment*` primitives for fully custom layouts.',
136
+ '**Placement:** beneath message content (for received/sent files) or above the prompt input (for staged uploads); fills its container width.',
137
+ "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the data via the `items` **property**, pick a layout with `variant` (`grid` | `inline` | `list`), add `removable` to get per-item remove buttons (emits a `kai-remove` **CustomEvent** with `{ id }`), and `hover-card` for inline/list previews (image attachments preview their thumbnail).",
138
+ 'See the **Code** tab for HTML usage.',
139
+ ]),
140
+ },
141
+ },
142
+ } satisfies Meta;
143
+
144
+ export default meta;
145
+ type Story = StoryObj;
146
+
147
+ /** Visual tiles (the default `grid` variant) — image attachments render as real
148
+ * `<img>` thumbnails; non-image files fall back to a media-type icon. */
149
+ export const Grid: Story = {
150
+ render: () => <AttachmentsElement items={sampleItems} variant="grid" />,
151
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
152
+ };
153
+
154
+ /** Icon + label chips (`inline`) — files shown as a small preview + filename. */
155
+ export const Inline: Story = {
156
+ render: () => <AttachmentsElement items={sampleItems} variant="inline" />,
157
+ };
158
+
159
+ /** Detailed rows (`list`) with the media type beneath each filename. */
160
+ export const List: Story = {
161
+ render: () => <AttachmentsElement items={sampleItems} variant="list" showMediaType />,
162
+ };
163
+
164
+ /** Removable rows — clicking the remove button fires a `remove` event. */
165
+ export const RemovableList: Story = {
166
+ name: 'Removable List',
167
+ render: () => <AttachmentsElement items={sampleItems} variant="list" removable />,
168
+ };
169
+
170
+ /** Image thumbnails in a grid (real `<img>` previews). */
171
+ export const ImageGrid: Story = {
172
+ name: 'Image Grid',
173
+ render: () => <AttachmentsElement items={imageItems} variant="grid" />,
174
+ };
175
+
176
+ /** Inline chips with a hover-card preview — hover an image chip to see its
177
+ * thumbnail enlarge in the hover card. */
178
+ export const WithHoverCard: Story = {
179
+ name: 'With Hover Card',
180
+ render: () => <AttachmentsElement items={imageItems} variant="inline" hoverCard />,
181
+ parameters: { docs: { source: { code: HOVER_SNIPPET, language: 'html' } } },
182
+ };
183
+
184
+ /** Grid tiles with a hover-card preview — the tile shows only the image; the
185
+ * filename + media type surface in the hover card. (Grid previously skipped
186
+ * the hover card entirely.) */
187
+ export const GridWithHoverCard: Story = {
188
+ name: 'Grid With Hover Card',
189
+ render: () => <AttachmentsElement items={imageItems} variant="grid" hoverCard />,
190
+ };
@@ -0,0 +1,136 @@
1
+ import { For, Show } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import {
4
+ Attachments,
5
+ Attachment,
6
+ AttachmentPreview,
7
+ AttachmentInfo,
8
+ AttachmentRemove,
9
+ AttachmentHoverCard,
10
+ AttachmentHoverCardTrigger,
11
+ AttachmentHoverCardContent,
12
+ AttachmentEmpty,
13
+ getAttachmentLabel,
14
+ getMediaCategory,
15
+ type AttachmentData,
16
+ type AttachmentVariant,
17
+ } from '../components/attachments';
18
+
19
+ interface Props extends Record<string, unknown> {
20
+ /** The attachments to render. Set as a JS property (array). */
21
+ items: AttachmentData[];
22
+ /** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
23
+ variant?: AttachmentVariant;
24
+ /** Wrap each item in a hover card that previews its details. */
25
+ hoverCard?: boolean;
26
+ /** Show a remove button per item; clicking it fires a `kai-remove` event. */
27
+ removable?: boolean;
28
+ /** Also show the media type beneath the filename (non-grid variants). */
29
+ showMediaType?: boolean;
30
+ /** Text shown when `items` is empty. */
31
+ emptyText?: string;
32
+ }
33
+
34
+ /** Events fired by `<kai-attachments>`. */
35
+ interface Events {
36
+ /** A remove button was clicked. */
37
+ 'kai-remove': { id: string };
38
+ }
39
+
40
+ /**
41
+ * `<kai-attachments>` — the exemplar for the "collapse a compound primitive to
42
+ * ONE configurable element" pattern (Route 1). The presentation knobs that the
43
+ * SolidJS layer expresses by composing sub-parts (`<AttachmentPreview>`,
44
+ * `<AttachmentInfo>`, `<AttachmentHoverCard>`, `<AttachmentRemove>`) become
45
+ * attributes/flags here:
46
+ *
47
+ * - icon + label .......... `variant="inline"`
48
+ * - visual + hover card .... `variant="grid" hover-card`
49
+ * - removable chips ........ add `removable` (emits `kai-remove` → { id })
50
+ *
51
+ * Data in via the `items` property; the only interaction (`remove`) comes back
52
+ * as an event. For fully-custom hover content, the SolidJS primitives remain the
53
+ * escape hatch (a templated slot — "Route 2" — is a deliberate future add).
54
+ */
55
+ defineWebComponent<Props, Events>('kai-attachments', {
56
+ items: [],
57
+ variant: 'grid',
58
+ hoverCard: false,
59
+ removable: false,
60
+ showMediaType: false,
61
+ emptyText: undefined,
62
+ }, (props, { dispatch, flag }) => {
63
+ const variant = () => props.variant ?? 'grid';
64
+ const hoverCard = () => flag('hoverCard');
65
+ const removable = () => flag('removable');
66
+ const showMediaType = () => flag('showMediaType');
67
+
68
+ return (
69
+ <Show
70
+ when={props.items.length}
71
+ fallback={<Show when={props.emptyText}><AttachmentEmpty>{props.emptyText}</AttachmentEmpty></Show>}
72
+ >
73
+ <Attachments variant={variant()}>
74
+ <For each={props.items}>
75
+ {(item) => (
76
+ <Attachment
77
+ data={item}
78
+ onRemove={removable() ? () => dispatch('kai-remove', { id: item.id }) : undefined}
79
+ >
80
+ <Show
81
+ when={hoverCard()}
82
+ fallback={
83
+ <>
84
+ <AttachmentPreview />
85
+ {/* Info only for non-grid; grid is a self-contained visual tile. */}
86
+ <Show when={variant() !== 'grid'}>
87
+ <AttachmentInfo showMediaType={showMediaType()} />
88
+ </Show>
89
+ </>
90
+ }
91
+ >
92
+ {/* The trigger carries the layout itself — a bare inline <span>
93
+ collapses inline/list rows. Grid wraps just the tile (the
94
+ label/details surface in the hover card instead). */}
95
+ <AttachmentHoverCard>
96
+ <AttachmentHoverCardTrigger
97
+ class={variant() === 'grid'
98
+ ? 'block size-full'
99
+ : `flex items-center gap-1.5${variant() === 'list' ? ' w-full' : ''}`}
100
+ >
101
+ <AttachmentPreview />
102
+ <Show when={variant() !== 'grid'}>
103
+ <AttachmentInfo showMediaType={showMediaType()} />
104
+ </Show>
105
+ </AttachmentHoverCardTrigger>
106
+ <AttachmentHoverCardContent>
107
+ {/* For image attachments, preview the actual thumbnail;
108
+ otherwise fall back to the label + media-type details. */}
109
+ <Show
110
+ when={getMediaCategory(item) === 'image' && item.type === 'file' && item.url}
111
+ fallback={
112
+ <>
113
+ <div class="text-body font-medium">{getAttachmentLabel(item)}</div>
114
+ <Show when={item.mediaType}>
115
+ <div class="text-muted-foreground text-caption">{item.mediaType}</div>
116
+ </Show>
117
+ </>
118
+ }
119
+ >
120
+ <img
121
+ src={item.url}
122
+ alt={getAttachmentLabel(item)}
123
+ class="block max-h-64 max-w-xs rounded object-contain"
124
+ />
125
+ </Show>
126
+ </AttachmentHoverCardContent>
127
+ </AttachmentHoverCard>
128
+ </Show>
129
+ <AttachmentRemove />
130
+ </Attachment>
131
+ )}
132
+ </For>
133
+ </Attachments>
134
+ </Show>
135
+ );
136
+ });
@@ -0,0 +1,113 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import type { JSX } from 'solid-js';
3
+ import './card';
4
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
+
6
+ declare module 'solid-js' {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'kai-card': JSX.HTMLAttributes<HTMLElement> & {
11
+ heading?: string;
12
+ description?: string;
13
+ 'error-message'?: string;
14
+ dense?: boolean | string;
15
+ };
16
+ }
17
+ }
18
+ }
19
+
20
+ /** A bordered box the card sits inside (gives it a sensible width). */
21
+ function Frame(props: { children: JSX.Element }) {
22
+ return <div style={{ 'max-width': '420px' }}>{props.children}</div>;
23
+ }
24
+
25
+ const CONTENT_SNIPPET = `<!-- kai-card is the chrome/container. Slot in plain content — not interactive controls.
26
+ For data-driven interactive cards use the typed cards instead (see below). -->
27
+ <kai-card heading="Workspace summary" description="Generated just now.">
28
+ <p>Your repository has 3 open pull requests and 12 passing checks.</p>
29
+ </kai-card>
30
+
31
+ <script type="module">
32
+ import '@kitn.ai/ui/elements'; // registers the custom elements
33
+ </script>`;
34
+
35
+ const MEDIA_SNIPPET = `<kai-card heading="Quarterly report" description="Generated just now.">
36
+ <img slot="media" src="/preview.png" alt="Report preview" />
37
+ <p>Your Q2 numbers are ready to review.</p>
38
+ </kai-card>`;
39
+
40
+ const ERROR_SNIPPET = `<!-- The one consistent inline error every card uses. -->
41
+ <kai-card heading="Share your feedback" error-message="This card couldn't be displayed."></kai-card>`;
42
+
43
+ const meta = {
44
+ title: 'Generative UI/Cards/kai-card',
45
+ tags: ['autodocs'],
46
+ argTypes: argTypesFor('kai-card'),
47
+ parameters: {
48
+ layout: 'padded',
49
+ docs: {
50
+ description: specDescription('kai-card', [
51
+ '`<kai-card>` is the shared, presentational **card chrome** every native generative-UI card composes from: an optional **media** region (`slot="media"`), a **heading** + **description**, a **body** (default slot), an **actions** footer (`slot="actions"`), and one consistent inline **error** state (`error-message`).',
52
+ '**Chrome only — it emits no events and reads no context.** Raw light-DOM content slotted into `kai-card` (such as plain `<button>` elements) is unstyled because light DOM cannot reach shadow CSS. The cards that compose it own the Card-contract interaction.',
53
+ '**For interactive, data-driven cards use the typed cards:** `<kai-confirm>` (approval actions — see that story for the canonical data-in → event-out example), `<kai-form>` (input/submit), `<kai-choice>` (single-select), `<kai-tasks>` (progress), `<kai-link-preview>` (rich link), `<kai-embed>` (media). Use `kai-card` only as a **plain themed surface** for presentational content.',
54
+ "**How to use as a surface:** register once with `import '@kitn.ai/ui/elements'`, set `heading`/`description` attributes, drop body markup in the default slot, and optionally a media region in `slot=\"media\"`. Set `error-message` to render the standard inline error (it replaces the body/actions).",
55
+ 'See the **Code** tab for HTML usage.',
56
+ ]),
57
+ },
58
+ },
59
+ } satisfies Meta;
60
+
61
+ export default meta;
62
+ type Story = StoryObj;
63
+
64
+ /** `kai-card` as a plain themed surface: heading + description + slotted text content. This is the correct use of `kai-card` — presentational chrome around passive content. For interactive cards (actions, forms, choices) use the typed cards such as `kai-confirm`. */
65
+ export const ContentSurface: Story = {
66
+ name: 'Content surface',
67
+ render: () => (
68
+ <Frame>
69
+ <kai-card heading="Workspace summary" description="Generated just now.">
70
+ <p style={{ margin: 0 }}>Your repository has 3 open pull requests and 12 passing checks.</p>
71
+ </kai-card>
72
+ </Frame>
73
+ ),
74
+ parameters: { docs: { source: { code: CONTENT_SNIPPET, language: 'html' } } },
75
+ };
76
+
77
+ /** An image media region above the heading. */
78
+ export const WithMedia: Story = {
79
+ render: () => (
80
+ <Frame>
81
+ <kai-card heading="Quarterly report" description="Generated just now.">
82
+ <div
83
+ slot="media"
84
+ style={{
85
+ height: '120px',
86
+ display: 'grid',
87
+ 'place-items': 'center',
88
+ background: 'var(--color-muted, #f4f4f5)',
89
+ color: 'var(--color-muted-foreground, #71717a)',
90
+ 'font-size': '13px',
91
+ }}
92
+ >
93
+ media slot
94
+ </div>
95
+ <p style={{ margin: 0 }}>Your Q2 numbers are ready to review.</p>
96
+ </kai-card>
97
+ </Frame>
98
+ ),
99
+ parameters: { docs: { source: { code: MEDIA_SNIPPET, language: 'html' } } },
100
+ };
101
+
102
+ /** The standard inline error state (replaces the body/actions). */
103
+ export const ErrorState: Story = {
104
+ render: () => (
105
+ <Frame>
106
+ <kai-card
107
+ heading="Share your feedback"
108
+ error-message="This card couldn't be displayed."
109
+ />
110
+ </Frame>
111
+ ),
112
+ parameters: { docs: { source: { code: ERROR_SNIPPET, language: 'html' } } },
113
+ };
@@ -0,0 +1,40 @@
1
+ import { defineWebComponent } from './define';
2
+ import { Card } from '../components/card';
3
+
4
+ interface Props extends Record<string, unknown> {
5
+ /** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
6
+ heading?: string;
7
+ /** Supporting text under the heading. Attribute: `description`. */
8
+ description?: string;
9
+ /** When set, the card renders its inline error state instead of the body.
10
+ * Attribute: `error-message`. */
11
+ errorMessage?: string;
12
+ /** Compact spacing for dense lists. Attribute: `dense`. */
13
+ dense?: boolean;
14
+ }
15
+
16
+ /**
17
+ * `<kai-card>` — the shared, presentational card chrome every native card composes
18
+ * from: an optional media region (`slot="media"`), a heading + description, a body
19
+ * (default slot), an actions footer (`slot="actions"`), and one consistent inline
20
+ * **error** state (`error-message`). It emits **no** events and reads no context —
21
+ * it is chrome only; the cards that compose it (e.g. `<kai-form>`) own the contract
22
+ * interaction. Isolated in Shadow DOM; theme-aware via the shared kit tokens.
23
+ */
24
+ defineWebComponent<Props>('kai-card', {
25
+ heading: undefined,
26
+ description: undefined,
27
+ errorMessage: undefined,
28
+ dense: false,
29
+ }, (props, { flag }) => (
30
+ <Card
31
+ heading={props.heading}
32
+ description={props.description}
33
+ errorMessage={props.errorMessage}
34
+ dense={flag('dense')}
35
+ media={<slot name="media" />}
36
+ actions={<slot name="actions" />}
37
+ >
38
+ <slot />
39
+ </Card>
40
+ ));
@@ -0,0 +1,59 @@
1
+ // src/elements/cards.stories.tsx
2
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
3
+ import { createSignal, onMount } from 'solid-js';
4
+ import './register'; // registers all kai-* incl. kai-cards
5
+ import type { CardEnvelope, CardEvent } from '../primitives/card-contract';
6
+
7
+ declare module 'solid-js' {
8
+ // eslint-disable-next-line @typescript-eslint/no-namespace
9
+ namespace JSX {
10
+ interface IntrinsicElements { 'kai-cards': JSX.HTMLAttributes<HTMLElement>; }
11
+ }
12
+ }
13
+
14
+ const CARDS: CardEnvelope[] = [
15
+ { type: 'confirm', id: 'deploy', title: 'Deploy to production?',
16
+ data: { body: 'Apply 3 migrations and deploy?', tone: 'warning',
17
+ actions: [{ id: 'go', label: 'Deploy', style: 'primary', default: true }, { id: 'no', label: 'Cancel' }] } },
18
+ { type: 'tasks', id: 'plan', title: 'Pick the steps',
19
+ data: { tasks: [{ id: 'a', label: 'Run tests', checked: true }, { id: 'b', label: 'Tag release' }], confirmLabel: 'Run selected' } },
20
+ { type: 'choice', id: 'plan-pick', title: 'Choose a plan',
21
+ data: { prompt: 'Which plan fits your team?', options: [
22
+ { id: 'free', label: 'Free', meta: '$0' },
23
+ { id: 'pro', label: 'Pro', meta: '$12/seat', recommended: true, payload: { plan: 'pro' } },
24
+ ] } },
25
+ { type: 'link', id: 'doc', data: { url: 'https://kitn.dev', title: 'kitn.dev', description: 'Generative UI cards', domain: 'kitn.dev' } },
26
+ ];
27
+
28
+ /** A host renders a stream of envelopes via <kai-cards> and handles events via `.policy`. */
29
+ function Demo() {
30
+ let el: HTMLElement | undefined;
31
+ const [log, setLog] = createSignal<string[]>([]);
32
+ onMount(() => {
33
+ if (!el) return;
34
+ (el as unknown as { cards: CardEnvelope[] }).cards = CARDS;
35
+ (el as unknown as { policy: unknown }).policy = {
36
+ onAction: (id: string, action: string) => setLog((l) => [`action: ${id} → ${action}`, ...l]),
37
+ onSubmit: (id: string, data: unknown) => setLog((l) => [`submit: ${id} → ${JSON.stringify(data)}`, ...l]),
38
+ onOpen: (url: string) => setLog((l) => [`open: ${url}`, ...l]),
39
+ onError: (id: string, msg: string) => setLog((l) => [`error: ${id} → ${msg}`, ...l]),
40
+ };
41
+ });
42
+ return (
43
+ <div style={{ display: 'grid', 'grid-template-columns': '1fr 18rem', gap: '1rem', padding: '1rem' }}>
44
+ <kai-cards ref={(e) => (el = e as HTMLElement)} />
45
+ <pre style={{ 'font-size': '12px', background: '#f4f4f5', padding: '8px', 'border-radius': '6px', 'min-height': '6rem' }}>
46
+ {log().join('\n') || 'events appear here…'}
47
+ </pre>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ const meta = {
53
+ title: 'Generative UI/SDK/kai-cards',
54
+ parameters: { layout: 'fullscreen' },
55
+ } satisfies Meta;
56
+ export default meta;
57
+ type Story = StoryObj;
58
+
59
+ export const CardStream: Story = { render: () => <Demo /> };