@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,134 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal, onMount, type JSX } from 'solid-js';
3
+ import './register'; // side effect: registers the custom elements
4
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
+ import type { FileTreeFile } from '../components/file-tree';
6
+
7
+ declare module 'solid-js' {
8
+ // eslint-disable-next-line @typescript-eslint/no-namespace
9
+ namespace JSX {
10
+ interface IntrinsicElements {
11
+ 'kai-file-tree': JSX.HTMLAttributes<HTMLElement> & {
12
+ 'active-file'?: string;
13
+ ref?: (el: HTMLElement) => void;
14
+ };
15
+ }
16
+ }
17
+ }
18
+
19
+ const FILES: FileTreeFile[] = [
20
+ { path: 'index.html', type: 'html' },
21
+ { path: 'about.html', type: 'html' },
22
+ { path: 'css/site.css', type: 'other', language: 'css' },
23
+ { path: 'src/app.ts', type: 'other', language: 'ts' },
24
+ { path: 'src/lib/format.ts', type: 'other', language: 'ts' },
25
+ { path: 'src/lib/parse.ts', type: 'other', language: 'ts' },
26
+ { path: 'assets/logo.svg', type: 'image' },
27
+ { path: 'assets/report.pdf', type: 'pdf' },
28
+ ];
29
+
30
+ /** A bordered, sized box the tree fills + scrolls within. */
31
+ function Frame(props: { children: JSX.Element }) {
32
+ return (
33
+ <div
34
+ style={{
35
+ height: '320px',
36
+ width: '280px',
37
+ border: '1px solid var(--color-border, #e4e4e7)',
38
+ 'border-radius': '10px',
39
+ overflow: 'hidden',
40
+ }}
41
+ >
42
+ {props.children}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
48
+ <kai-file-tree style="display:block;height:320px"></kai-file-tree>
49
+
50
+ <script type="module">
51
+ import '@kitn.ai/ui/elements'; // registers the custom elements
52
+
53
+ const tree = document.querySelector('kai-file-tree');
54
+ // \`files\` is a JS property (array). Folders are derived from \`/\` in each path.
55
+ tree.files = [
56
+ { path: 'index.html', type: 'html' },
57
+ { path: 'src/app.ts', type: 'other' },
58
+ { path: 'src/lib/format.ts', type: 'other' },
59
+ { path: 'assets/logo.svg', type: 'image' },
60
+ ];
61
+ tree.setAttribute('active-file', 'src/app.ts');
62
+ tree.addEventListener('kai-select', (e) => console.log('selected', e.detail.path));
63
+ </script>`;
64
+
65
+ const meta = {
66
+ title: 'Components/FileTree',
67
+ tags: ['autodocs'],
68
+ argTypes: argTypesFor('kai-file-tree'),
69
+ parameters: {
70
+ layout: 'padded',
71
+ docs: {
72
+ description: specDescription('kai-file-tree', [
73
+ '`<kai-file-tree>` is the framework-agnostic **web component** for a collapsible, keyboard-navigable **file explorer** built from a flat list of `/`-delimited paths (nested folders are derived automatically). ARIA `tree`/`treeitem`. Isolated in **Shadow DOM**.',
74
+ '**When to use:** any time you need a file/folder tree — the Code tab of `<kai-artifact>` uses it, but it ships public so you can reuse it standalone (a project explorer, an attachments browser, a doc outline).',
75
+ "**How to use:** register once with `import '@kitn.ai/ui/elements'`, then set the `files` **property** (a JS array of `{ path, url?, code?, language?, type? }`). Folders come from the `/` in each `path`; `type` picks the icon. Set `active-file` to highlight a row, `default-expanded` to control which folders open. Listen for **`select`** (`detail.path`). Arrow keys navigate; Enter/Space selects or toggles.",
76
+ '**Anatomy:** a scrollable ARIA `tree` of **folder nodes** (collapsible `treeitem` groups, derived from `/`-delimited path segments) and **file leaf nodes** (selectable `treeitem` rows with a type icon; the `active-file` row is highlighted). Folders auto-expand on first render; click or arrow-key to collapse/expand.',
77
+ '**Placement:** a sidebar/explorer column — inside `<kai-artifact>`, a `<kai-resizable>` panel, or any file-picker surface. It **fills** its container, so give the parent (or the element) a height.',
78
+ 'See the **Code** tab for HTML usage.',
79
+ ]),
80
+ },
81
+ },
82
+ } satisfies Meta;
83
+
84
+ export default meta;
85
+ type Story = StoryObj;
86
+
87
+ /** Interactive playground — click rows, use arrow keys / Enter to navigate. */
88
+ export const Playground: Story = {
89
+ render: () => {
90
+ const [selected, setSelected] = createSignal('src/app.ts');
91
+ let el: HTMLElement & { files?: FileTreeFile[] };
92
+ onMount(() => {
93
+ if (!el) return;
94
+ el.files = FILES;
95
+ el.addEventListener('kai-select', (e) => {
96
+ const path = (e as CustomEvent).detail.path as string;
97
+ setSelected(path);
98
+ el.setAttribute('active-file', path);
99
+ });
100
+ });
101
+ return (
102
+ <div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
103
+ <Frame>
104
+ <kai-file-tree
105
+ ref={(e) => (el = e as HTMLElement & { files?: FileTreeFile[] })}
106
+ active-file={selected()}
107
+ />
108
+ </Frame>
109
+ <span style={{ 'font-size': '13px', color: 'var(--color-muted-foreground, #71717a)' }}>
110
+ Selected: <code>{selected()}</code>
111
+ </span>
112
+ </div>
113
+ );
114
+ },
115
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
116
+ };
117
+
118
+ /** A deeper, nested layout with mixed file types. */
119
+ export const Nested: Story = {
120
+ render: () => {
121
+ let el: HTMLElement & { files?: FileTreeFile[] };
122
+ onMount(() => {
123
+ if (el) el.files = FILES;
124
+ });
125
+ return (
126
+ <Frame>
127
+ <kai-file-tree
128
+ ref={(e) => (el = e as HTMLElement & { files?: FileTreeFile[] })}
129
+ active-file="src/lib/format.ts"
130
+ />
131
+ </Frame>
132
+ );
133
+ },
134
+ };
@@ -0,0 +1,52 @@
1
+ import { defineWebComponent } from './define';
2
+ import { FileTree, type FileTreeFile } from '../components/file-tree';
3
+
4
+ interface Props extends Record<string, unknown> {
5
+ /** The files to render. Set as a JS property (array of `{ path, url?, code?, language?, type? }`). */
6
+ files: FileTreeFile[];
7
+ /** Selected file path — highlighted in the tree. */
8
+ activeFile?: string;
9
+ /** Folder paths expanded initially. Omit to start with all folders open. */
10
+ defaultExpanded?: string[];
11
+ }
12
+
13
+ interface Events extends Record<string, unknown> {
14
+ /** Fired when a file is selected. `detail.path` = the file's path. */
15
+ 'kai-select': { path: string };
16
+ }
17
+
18
+ /**
19
+ * `<kai-file-tree>` — a collapsible, keyboard-navigable file explorer built from a
20
+ * flat list of `/`-delimited paths (folders are derived). ARIA `tree`/`treeitem`.
21
+ * Selecting a file fires a `select` event (`detail.path`). Fills its container.
22
+ */
23
+ defineWebComponent<Props, Events>('kai-file-tree', {
24
+ files: [],
25
+ activeFile: undefined,
26
+ defaultExpanded: undefined,
27
+ }, (props, { dispatch }) => (
28
+ <>
29
+ {/* Fill the container + own the scroll (see resizable.tsx fill technique:
30
+ a definite `1fr` grid cell scoped to a wrapper, NOT :host, so the facade's
31
+ sibling portal-mount div can't steal a grid track). */}
32
+ <style>{':host{display:block;height:100%;min-height:0}'}</style>
33
+ <div
34
+ style={{
35
+ display: 'grid',
36
+ 'grid-template-rows': 'minmax(0, 1fr)',
37
+ 'grid-template-columns': 'minmax(0, 1fr)',
38
+ height: '100%',
39
+ 'min-height': '0',
40
+ }}
41
+ >
42
+ <div class="overflow-auto scrollbar-thin py-1.5" style={{ 'min-height': '0' }}>
43
+ <FileTree
44
+ files={props.files}
45
+ activeFile={props.activeFile}
46
+ defaultExpanded={props.defaultExpanded}
47
+ onSelect={(path) => dispatch('kai-select', { path })}
48
+ />
49
+ </div>
50
+ </div>
51
+ </>
52
+ ));
@@ -0,0 +1,82 @@
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-file-upload': JSX.HTMLAttributes<HTMLElement> & {
11
+ multiple?: boolean | string;
12
+ accept?: string;
13
+ disabled?: boolean | string;
14
+ label?: string;
15
+ 'on:kai-files-added'?: (e: CustomEvent<{ files: File[] }>) => void;
16
+ };
17
+ }
18
+ }
19
+ }
20
+
21
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
22
+ <kai-file-upload accept="image/*" label="Drop images here"></kai-file-upload>
23
+
24
+ <script type="module">
25
+ import '@kitn.ai/ui/elements'; // registers the custom elements
26
+
27
+ document.querySelector('kai-file-upload')
28
+ .addEventListener('kai-files-added', (e) =>
29
+ console.log(e.detail.files.map((f) => f.name)));
30
+ </script>`;
31
+
32
+ const meta = {
33
+ title: 'Components/FileUpload',
34
+ tags: ['autodocs'],
35
+ argTypes: argTypesFor('kai-file-upload'),
36
+ parameters: {
37
+ layout: 'fullscreen',
38
+ docs: {
39
+ description: specDescription('kai-file-upload', [
40
+ '`<kai-file-upload>` is the framework-agnostic **web component** for a click / drag-and-drop file dropzone — isolated in **Shadow DOM**.',
41
+ '**When to use:** accepting file or image uploads in a non-Solid app. In SolidJS, compose the `FileUpload` primitives.',
42
+ '**Placement:** above or replacing the prompt input, or in a dedicated upload panel; fills its container width (set a `max-width` on the parent to constrain the dropzone).',
43
+ "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the `accept` / `multiple` / `label` attributes, and listen for the `kai-files-added` **CustomEvent** (`e.detail.files` is a `File[]`). The default dropzone label can be replaced with your own markup via the default `<slot>`.",
44
+ 'See the **Code** tab for HTML usage.',
45
+ ]),
46
+ },
47
+ },
48
+ } satisfies Meta;
49
+
50
+ export default meta;
51
+ type Story = StoryObj;
52
+
53
+ /** A dropzone accepting any files. */
54
+ export const Default: Story = {
55
+ render: () => (
56
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
57
+ <kai-file-upload
58
+ on:kai-files-added={(e: CustomEvent<{ files: File[] }>) =>
59
+ console.log(e.detail.files.map((f) => f.name))}
60
+ />
61
+ </div>
62
+ ),
63
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
64
+ };
65
+
66
+ /** Restricted to images, single file, with a custom label. */
67
+ export const ImagesOnly: Story = {
68
+ render: () => (
69
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
70
+ <kai-file-upload accept="image/*" multiple={false} label="Click or drop an image" />
71
+ </div>
72
+ ),
73
+ };
74
+
75
+ /** A disabled dropzone (non-interactive). */
76
+ export const Disabled: Story = {
77
+ render: () => (
78
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
79
+ <kai-file-upload disabled label="Uploads are disabled" />
80
+ </div>
81
+ ),
82
+ };
@@ -0,0 +1,44 @@
1
+ import { defineWebComponent } from './define';
2
+ import { FileUpload, FileUploadTrigger } from '../components/file-upload';
3
+ import { Upload } from 'lucide-solid';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ /** Allow selecting multiple files (default true). */
7
+ multiple?: boolean;
8
+ /** `accept` attribute for the file picker (e.g. `image/*`). */
9
+ accept?: string;
10
+ /** Disable the dropzone — no clicking, no drag-and-drop. */
11
+ disabled?: boolean;
12
+ /** Default dropzone label (overridable via the default slot). */
13
+ label?: string;
14
+ }
15
+
16
+ /** Events fired by `<kai-file-upload>`. */
17
+ interface Events {
18
+ /** Files were picked or dropped. */
19
+ 'kai-files-added': { files: File[] };
20
+ }
21
+
22
+ /**
23
+ * `<kai-file-upload>` — a click/drag-drop dropzone. Emits `filesadded`. The
24
+ * default dropzone label can be replaced with your own markup via the default
25
+ * `<slot>` (a "Route 2" custom-content slot).
26
+ */
27
+ defineWebComponent<Props, Events>('kai-file-upload', {
28
+ multiple: true,
29
+ accept: undefined,
30
+ disabled: false,
31
+ label: 'Click or drop files to upload',
32
+ }, (props, { dispatch, flag }) => (
33
+ <FileUpload
34
+ multiple={flag('multiple')}
35
+ accept={props.accept}
36
+ disabled={flag('disabled')}
37
+ onFilesAdded={(files) => dispatch('kai-files-added', { files })}
38
+ >
39
+ <FileUploadTrigger class="border-border bg-muted/30 hover:bg-muted/60 text-muted-foreground flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-6 py-8 text-sm transition-colors">
40
+ <Upload class="size-5" />
41
+ <slot>{props.label}</slot>
42
+ </FileUploadTrigger>
43
+ </FileUpload>
44
+ ));
@@ -0,0 +1,248 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal, onMount, type JSX } from 'solid-js';
3
+ import './form';
4
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
+ import type { FormDefinition } from '../components/form';
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-form': JSX.HTMLAttributes<HTMLElement> & {
13
+ heading?: string;
14
+ 'card-id'?: string;
15
+ ref?: (el: HTMLElement) => void;
16
+ };
17
+ }
18
+ }
19
+ }
20
+
21
+ type FormEl = HTMLElement & { data?: FormDefinition; resolution?: Record<string, unknown> };
22
+
23
+ /** A bordered box the form sits inside. */
24
+ function Frame(props: { children: JSX.Element }) {
25
+ return <div style={{ 'max-width': '460px' }}>{props.children}</div>;
26
+ }
27
+
28
+ /** Mounts a <kai-form>, sets `.data`, logs the emitted CardEvent under the render. */
29
+ function FormDemo(props: { def: FormDefinition; cardId: string }) {
30
+ const [log, setLog] = createSignal<CardEvent[]>([]);
31
+ let el: FormEl | undefined;
32
+ onMount(() => {
33
+ if (!el) return;
34
+ el.data = props.def;
35
+ el.addEventListener('kai-card', (e) => {
36
+ const detail = (e as CustomEvent<CardEvent>).detail;
37
+ setLog((prev) => [...prev, detail]);
38
+ });
39
+ });
40
+ return (
41
+ <Frame>
42
+ <div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
43
+ <kai-form ref={(e) => (el = e as FormEl)} card-id={props.cardId} />
44
+ <pre
45
+ style={{
46
+ margin: 0,
47
+ 'max-height': '180px',
48
+ overflow: 'auto',
49
+ background: 'var(--color-muted, #f4f4f5)',
50
+ 'border-radius': '8px',
51
+ padding: '8px',
52
+ 'font-size': '12px',
53
+ }}
54
+ >
55
+ {log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
56
+ </pre>
57
+ </div>
58
+ </Frame>
59
+ );
60
+ }
61
+
62
+ const FEEDBACK: FormDefinition = {
63
+ type: 'object',
64
+ title: 'How did we do?',
65
+ description: 'Two quick questions.',
66
+ required: ['rating', 'contactOk'],
67
+ 'x-kai-order': ['rating', 'comments', 'plan', 'contactOk'],
68
+ 'x-kai-submitLabel': 'Send feedback',
69
+ 'x-kai-actions': [{ id: 'skip', label: 'Skip', variant: 'ghost' }],
70
+ properties: {
71
+ rating: { type: 'integer', title: 'Overall rating', minimum: 1, maximum: 5, 'x-kai-widget': 'rating' },
72
+ comments: {
73
+ type: 'string',
74
+ title: 'Comments',
75
+ maxLength: 500,
76
+ 'x-kai-widget': 'textarea',
77
+ 'x-kai-placeholder': "What worked, what didn't…",
78
+ },
79
+ plan: { type: 'string', title: 'Your plan', enum: ['free', 'pro', 'team'], default: 'free' },
80
+ contactOk: { type: 'boolean', title: 'OK to contact me about this', default: false },
81
+ },
82
+ };
83
+
84
+ const ALL_WIDGETS: FormDefinition = {
85
+ type: 'object',
86
+ title: 'Every widget',
87
+ 'x-kai-submitLabel': 'Submit all',
88
+ properties: {
89
+ name: { type: 'string', title: 'Name' },
90
+ bio: { type: 'string', title: 'Bio', maxLength: 300 },
91
+ email: { type: 'string', title: 'Email', format: 'email' },
92
+ website: { type: 'string', title: 'Website', format: 'uri' },
93
+ birthday: { type: 'string', title: 'Birthday', format: 'date' },
94
+ secret: { type: 'string', title: 'Password', 'x-kai-widget': 'password' },
95
+ size: { type: 'string', title: 'Size', enum: ['S', 'M', 'L'] },
96
+ country: { type: 'string', title: 'Country', enum: ['US', 'UK', 'DE', 'FR', 'JP'] },
97
+ age: { type: 'integer', title: 'Age', minimum: 0, maximum: 120 },
98
+ volume: { type: 'integer', title: 'Volume', minimum: 0, maximum: 11, 'x-kai-widget': 'slider' },
99
+ stars: { type: 'integer', title: 'Stars', minimum: 1, maximum: 5, 'x-kai-widget': 'rating' },
100
+ notify: { type: 'boolean', title: 'Email me updates' },
101
+ agree: { type: 'boolean', title: 'I agree', 'x-kai-widget': 'checkbox' },
102
+ tags: { type: 'array', title: 'Tags', items: { type: 'string' } },
103
+ topics: { type: 'array', title: 'Topics', items: { enum: ['news', 'sports', 'tech'] } },
104
+ contacts: {
105
+ type: 'array',
106
+ title: 'Contacts',
107
+ items: {
108
+ type: 'object',
109
+ properties: { label: { type: 'string', title: 'Label' }, phone: { type: 'string', title: 'Phone' } },
110
+ },
111
+ },
112
+ address: {
113
+ type: 'object',
114
+ title: 'Address',
115
+ properties: { street: { type: 'string', title: 'Street' }, city: { type: 'string', title: 'City' } },
116
+ },
117
+ },
118
+ };
119
+
120
+ const VALIDATION: FormDefinition = {
121
+ type: 'object',
122
+ title: 'Create account',
123
+ required: ['username', 'email', 'age'],
124
+ properties: {
125
+ username: { type: 'string', title: 'Username', minLength: 3, maxLength: 12 },
126
+ email: { type: 'string', title: 'Email', format: 'email' },
127
+ age: { type: 'integer', title: 'Age', minimum: 13, maximum: 120 },
128
+ },
129
+ };
130
+
131
+ const HTML_SNIPPET = (def: FormDefinition) => `<kai-form></kai-form>
132
+ <script type="module">
133
+ import '@kitn.ai/ui/elements'; // registers the custom elements
134
+
135
+ const form = document.querySelector('kai-form');
136
+ // \`data\` is the CardEnvelope.data — a JSON Schema + x-kai-* UI hints (set as a property).
137
+ form.data = ${JSON.stringify(def, null, 2)};
138
+
139
+ // Cards bubble ONE \`kai-card\` CustomEvent carrying a typed CardEvent.
140
+ form.addEventListener('kai-card', (e) => {
141
+ const ev = e.detail; // { kind:'submit', cardId, data } | { kind:'action', ... } | ...
142
+ if (ev.kind === 'submit') console.log('submission', ev.data);
143
+ });
144
+ </script>`;
145
+
146
+ const meta = {
147
+ title: 'Generative UI/Cards/kai-form',
148
+ tags: ['autodocs'],
149
+ argTypes: argTypesFor('kai-form'),
150
+ parameters: {
151
+ layout: 'padded',
152
+ docs: {
153
+ description: specDescription('kai-form', [
154
+ '`<kai-form>` turns an agent\'s **JSON Schema** "shape" (set via the `data` **property**) into a themed, accessible, validated form inside `<kai-card>` chrome. A valid submission is emitted **up the Card contract** as a bubbling **`kai-card`** CustomEvent of `{ kind: \'submit\', cardId, data }`.',
155
+ '**Anatomy:** `<kai-card>` chrome (optional heading from `data.title` or `heading` attr) → **field rows** (one per schema property in `x-kai-order` / key order: label + widget; `object` properties become a `<fieldset>`, `array` items become a repeater/checkbox-group/tag-list) → **card footer** (`x-kai-submitLabel` submit `<Button>` + optional `x-kai-actions` secondary buttons; replaced by a read-only `<dl>` summary after a resolved submission).',
156
+ '**The mapping is deterministic:** `string`→text, `string`+`enum`→radio/select, `string`+`format`→typed inputs, `number`/`integer`→number (or `slider`/`rating` via `x-kai-widget`), `boolean`→switch, `array`→checkbox-group / multi-select / repeater / tag-list, nested `object`→fieldset. `x-kai-*` hints (`x-kai-widget`, `x-kai-order`, `x-kai-submitLabel`, `x-kai-actions`, `x-kai-dismissible`, …) refine the UI and live **inside** the schema, so one source of truth drives both the form and validation.',
157
+ "**Events** (all frozen Card-contract verbs): `ready` on mount, `submit` on a valid submit, `action` for secondary buttons (`x-kai-actions`), `dismiss` when dismissible, `error` for a malformed definition (renders the inline `kai-card` error). It **never invents events**.",
158
+ '**The same `CardEnvelope`/`CardEvent` shapes flow over the remote iframe transport unchanged** — this is the *native* card. See the **Code** tab for the full envelope JSON + the HTML wiring.',
159
+ ]),
160
+ },
161
+ },
162
+ } satisfies Meta;
163
+
164
+ export default meta;
165
+ type Story = StoryObj;
166
+
167
+ /** The worked example: a rating + textarea + enum radio + switch, with a Skip action. */
168
+ export const Feedback: Story = {
169
+ render: () => <FormDemo def={FEEDBACK} cardId="card-feedback-7f3" />,
170
+ parameters: { docs: { source: { code: HTML_SNIPPET(FEEDBACK), language: 'html' } } },
171
+ };
172
+
173
+ /** One form exercising every row of the mapping table. */
174
+ export const AllWidgets: Story = {
175
+ render: () => <FormDemo def={ALL_WIDGETS} cardId="card-all" />,
176
+ parameters: { docs: { source: { code: HTML_SNIPPET(ALL_WIDGETS), language: 'html' } } },
177
+ };
178
+
179
+ /** Required + min/max + format: try submitting empty (inline errors), then fill it in. */
180
+ export const Validation: Story = {
181
+ render: () => <FormDemo def={VALIDATION} cardId="card-signup" />,
182
+ parameters: { docs: { source: { code: HTML_SNIPPET(VALIDATION), language: 'html' } } },
183
+ };
184
+
185
+ const RESOLVED_FORM: FormDefinition = {
186
+ type: 'object',
187
+ title: 'Book a demo',
188
+ 'x-kai-order': ['name', 'optIn'],
189
+ properties: {
190
+ name: { type: 'string', title: 'Full name' },
191
+ optIn: { type: 'boolean', title: 'Email me' },
192
+ },
193
+ };
194
+
195
+ /** The form after a valid submission — read-only `<dl>` summary + "✓ Submitted", no inputs. */
196
+ export const Resolved: Story = {
197
+ name: 'Resolved (read-only)',
198
+ render: () => {
199
+ let el: FormEl | undefined;
200
+ onMount(() => {
201
+ if (!el) return;
202
+ el.data = RESOLVED_FORM;
203
+ el.resolution = { kind: 'submit', data: { name: 'Jane Cooper', optIn: true } };
204
+ });
205
+ return (
206
+ <Frame>
207
+ <kai-form ref={(e) => (el = e as FormEl)} card-id="card-resolved-form" />
208
+ </Frame>
209
+ );
210
+ },
211
+ parameters: {
212
+ docs: {
213
+ source: {
214
+ code: `<kai-form></kai-form>
215
+ <script type="module">
216
+ import '@kitn.ai/ui/elements';
217
+ const form = document.querySelector('kai-form');
218
+ form.data = ${JSON.stringify(RESOLVED_FORM, null, 2)};
219
+ // Setting .resolution renders the chromed read-only summary — no inputs or submit button.
220
+ form.resolution = { kind: 'submit', data: { name: 'Jane Cooper', optIn: true } };
221
+ </script>`,
222
+ language: 'html',
223
+ },
224
+ },
225
+ },
226
+ };
227
+
228
+ /** A malformed `data` → the inline error state + an `error` event (no form rendered). */
229
+ export const InvalidEnvelope: Story = {
230
+ render: () => <FormDemo def={{ type: 'array' } as unknown as FormDefinition} cardId="card-bad" />,
231
+ parameters: {
232
+ docs: {
233
+ source: {
234
+ code: `<kai-form></kai-form>
235
+ <script type="module">
236
+ import '@kitn.ai/ui/elements';
237
+ const form = document.querySelector('kai-form');
238
+ // A definition that isn't a JSON-Schema object → inline error + an \`error\` event.
239
+ form.data = { type: 'array' };
240
+ form.addEventListener('kai-card', (e) => {
241
+ if (e.detail.kind === 'error') console.warn('form error:', e.detail.message);
242
+ });
243
+ </script>`,
244
+ language: 'html',
245
+ },
246
+ },
247
+ },
248
+ };
@@ -0,0 +1,43 @@
1
+ import { defineWebComponent } from './define';
2
+ import { Form, type FormDefinition } from '../components/form';
3
+ import type { CardResolution } from '../primitives/card-contract';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ /** The form definition — a JSON Schema (`type:'object'`) + `x-kai-*` UI hints
7
+ * (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object',
8
+ * properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for
9
+ * the full shape (it is self-referential, so the element types it loosely). */
10
+ data?: Record<string, unknown>;
11
+ /** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
12
+ cardId?: string;
13
+ /** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
14
+ heading?: string;
15
+ /** Set when the user resolved this card; renders the read-only view. Property:
16
+ * `el.resolution = { kind:'submit', data:{…} }`. */
17
+ resolution?: Record<string, unknown>;
18
+ }
19
+
20
+ /**
21
+ * `<kai-form>` — renders a JSON-Schema **form definition** (set via the `data`
22
+ * property) into themed, accessible widgets inside `<kai-card>` chrome, validates
23
+ * input client-side, and emits the collected, coerced, validated object up the
24
+ * Card contract as a bubbling **`kai-card`** CustomEvent of `{ kind:'submit' }`.
25
+ * It also emits `ready` on mount, `action`/`dismiss` for secondary affordances, and
26
+ * `error` for a malformed definition. Routes through a `CardProvider` when present,
27
+ * else the bubbling `kai-card` event (so a bare `<kai-form>` works without a host).
28
+ * Isolated in Shadow DOM; theme-aware via the shared kit tokens.
29
+ */
30
+ defineWebComponent<Props>('kai-form', {
31
+ data: undefined,
32
+ cardId: undefined,
33
+ heading: undefined,
34
+ resolution: undefined,
35
+ }, (props, { element }) => (
36
+ <Form
37
+ data={props.data as FormDefinition | undefined}
38
+ cardId={props.cardId ?? (element.id || 'kai-form')}
39
+ heading={props.heading}
40
+ resolution={props.resolution as CardResolution | undefined}
41
+ hostElement={element}
42
+ />
43
+ ));