@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,89 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { Image } from './image';
3
+ import { componentDescription } from '../stories/docs/element-controls';
4
+
5
+ // Compact SVG chat typing icon as base64
6
+ const chatIconBase64 =
7
+ 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4Ij48cmVjdCB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHJ4PSIxMCIgZmlsbD0iIzdjM2FlZCIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMjQiIHI9IjQiIGZpbGw9IiNmZmYiLz48Y2lyY2xlIGN4PSIyNCIgY3k9IjI0IiByPSI0IiBmaWxsPSIjZmZmIi8+PGNpcmNsZSBjeD0iMzIiIGN5PSIyNCIgcj0iNCIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==';
8
+
9
+ const meta = {
10
+ title: 'Solid (Advanced)/Elements/Image',
11
+ component: Image,
12
+ tags: ['autodocs'],
13
+ parameters: {
14
+ layout: 'padded',
15
+ docs: {
16
+ controls: { exclude: ['use:eventListener'] },
17
+ description: componentDescription([
18
+ 'Renders an image from a `base64` string or a `uint8Array`, building a data URL or object URL automatically; shows a pulsing placeholder until a source is available.',
19
+ '**When to use:** to display model-generated or attached images supplied as raw bytes / base64 (the `GeneratedImageLike` shape) rather than a remote URL.',
20
+ '**How to use:** pass `base64` + `mediaType` (or `uint8Array` + `mediaType`) and an `alt` description; size and style via `class`.',
21
+ '**Placement:** inside assistant messages, attachment previews, or anywhere a generated image needs to be shown.',
22
+ ]),
23
+ },
24
+ },
25
+ argTypes: {
26
+ base64: {
27
+ control: 'text',
28
+ description: 'Base64-encoded image data. Combined with `mediaType` to form a data URL.',
29
+ },
30
+ uint8Array: {
31
+ control: 'object',
32
+ description: 'Raw image bytes. Combined with `mediaType` to form an object URL.',
33
+ },
34
+ mediaType: {
35
+ control: 'text',
36
+ description: 'MIME type of the image data, e.g. `image/png` or `image/svg+xml`.',
37
+ table: { defaultValue: { summary: 'image/png' } },
38
+ },
39
+ alt: {
40
+ control: 'text',
41
+ description: 'Alternative text describing the image (also used on the placeholder).',
42
+ },
43
+ class: {
44
+ control: 'text',
45
+ description: 'Additional CSS classes for the image / placeholder element.',
46
+ },
47
+ },
48
+ args: {
49
+ base64: chatIconBase64,
50
+ mediaType: 'image/svg+xml',
51
+ alt: 'Compact gradient chat icon',
52
+ class: 'h-24 w-24 rounded-md',
53
+ },
54
+ render: (args) => <Image {...args} />,
55
+ } satisfies Meta<typeof Image>;
56
+
57
+ export default meta;
58
+ type Story = StoryObj<typeof meta>;
59
+
60
+ const IMPORT = `import { Image } from '@kitn.ai/ui';`;
61
+ const src = (code: string) => ({
62
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
63
+ });
64
+
65
+ /** Interactive playground — swap the base64 data, media type, and sizing classes. */
66
+ export const Playground: Story = {
67
+ ...src(`<Image
68
+ base64={chatIconBase64}
69
+ mediaType="image/svg+xml"
70
+ alt="Compact gradient chat icon"
71
+ class="h-24 w-24 rounded-md"
72
+ />`),
73
+ };
74
+
75
+ export const Basic: Story = {
76
+ args: { class: 'h-24 w-24 rounded-md' },
77
+ ...src(`<Image base64={chatIconBase64} mediaType="image/svg+xml" alt="Compact gradient chat icon" class="h-24 w-24 rounded-md" />`),
78
+ };
79
+
80
+ export const CustomSize: Story = {
81
+ args: { alt: 'Large preview', class: 'h-64 w-64 rounded-lg' },
82
+ ...src(`<Image base64={chatIconBase64} mediaType="image/svg+xml" alt="Large preview" class="h-64 w-64 rounded-lg" />`),
83
+ };
84
+
85
+ /** Placeholder state shown while no `base64`/`uint8Array` source is available (showcase). */
86
+ export const Placeholder: Story = {
87
+ render: () => <Image alt="Loading image" class="h-24 w-24 rounded-md" />,
88
+ ...src(`<Image alt="Loading image" class="h-24 w-24 rounded-md" />`),
89
+ };
@@ -0,0 +1,67 @@
1
+ import { type JSX, splitProps, createSignal, createEffect, onCleanup, Show } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface GeneratedImageLike {
5
+ base64?: string;
6
+ uint8Array?: Uint8Array;
7
+ mediaType?: string;
8
+ }
9
+
10
+ export interface ImageProps extends GeneratedImageLike {
11
+ alt: string;
12
+ class?: string;
13
+ }
14
+
15
+ function getImageSrc(base64?: string, mediaType?: string): string | undefined {
16
+ if (base64 && mediaType) {
17
+ return `data:${mediaType};base64,${base64}`;
18
+ }
19
+ return undefined;
20
+ }
21
+
22
+ function Image(props: ImageProps) {
23
+ const [local, rest] = splitProps(props, ['base64', 'uint8Array', 'mediaType', 'class', 'alt']);
24
+ const [objectUrl, setObjectUrl] = createSignal<string | undefined>(undefined);
25
+
26
+ const mediaType = () => local.mediaType ?? 'image/png';
27
+
28
+ createEffect(() => {
29
+ const arr = local.uint8Array;
30
+ const mt = mediaType();
31
+ if (arr && mt) {
32
+ const blob = new Blob([arr as BlobPart], { type: mt });
33
+ const url = URL.createObjectURL(blob);
34
+ setObjectUrl(url);
35
+ onCleanup(() => URL.revokeObjectURL(url));
36
+ } else {
37
+ setObjectUrl(undefined);
38
+ }
39
+ });
40
+
41
+ const src = () => getImageSrc(local.base64, mediaType()) ?? objectUrl();
42
+
43
+ return (
44
+ <Show
45
+ when={src()}
46
+ fallback={
47
+ <div
48
+ aria-label={local.alt}
49
+ role="img"
50
+ class={cn(
51
+ 'h-auto max-w-full animate-pulse overflow-hidden rounded-md bg-muted',
52
+ local.class
53
+ )}
54
+ />
55
+ }
56
+ >
57
+ <img
58
+ src={src()}
59
+ alt={local.alt}
60
+ class={cn('h-auto max-w-full overflow-hidden rounded-md', local.class)}
61
+ role="img"
62
+ />
63
+ </Show>
64
+ );
65
+ }
66
+
67
+ export { Image };
@@ -0,0 +1,194 @@
1
+ import {
2
+ type JSX,
3
+ splitProps,
4
+ createSignal,
5
+ createEffect,
6
+ createMemo,
7
+ on,
8
+ onMount,
9
+ onCleanup,
10
+ Show,
11
+ } from 'solid-js';
12
+ import { cn } from '../utils/cn';
13
+ import { Link as LinkIcon } from 'lucide-solid';
14
+ import type { CardEvent } from '../primitives/card-contract';
15
+ import {
16
+ type LinkPreviewData,
17
+ deriveDomain,
18
+ isRenderableLink,
19
+ hasLinkPreviewFetcher,
20
+ resolveLinkMetadata,
21
+ } from '../primitives/link-preview';
22
+
23
+ export interface LinkPreviewProps {
24
+ /** The card id correlating every emitted event. */
25
+ cardId: string;
26
+ /** The link payload (data-down). */
27
+ data: LinkPreviewData;
28
+ /** Emit a contract CardEvent up (host routes it). */
29
+ onEmit?: (event: CardEvent) => void;
30
+ /** Extra classes for the card root. */
31
+ class?: string;
32
+ }
33
+
34
+ /** True when the payload already carries renderable metadata (the pure path). */
35
+ function hasMetadata(data: LinkPreviewData): boolean {
36
+ return Boolean(data.title || data.description || data.image);
37
+ }
38
+
39
+ /**
40
+ * `LinkPreview` — a pure, themed, accessible rich link / OG preview. Renders from the
41
+ * supplied metadata; it never fetches the network itself. When the payload is a
42
+ * bare `{ url }` and an app has registered a `configureLinkPreview` fetcher, it
43
+ * shows a skeleton, calls the hook, merges the result, and renders. Activating the
44
+ * card (click / Enter / Space) emits the contract `open` verb (`target:'tab'`); the
45
+ * host policy performs the navigation so it can veto/redirect.
46
+ */
47
+ export function LinkPreview(props: LinkPreviewProps): JSX.Element {
48
+ const [local] = splitProps(props, ['cardId', 'data', 'onEmit', 'class']);
49
+
50
+ const emit = (event: CardEvent) => local.onEmit?.(event);
51
+
52
+ // Bare-URL resolution state (only used when the payload lacks metadata + a fetcher exists).
53
+ const [fetched, setFetched] = createSignal<Partial<LinkPreviewData> | undefined>();
54
+ const [loading, setLoading] = createSignal(false);
55
+ const [imageBroken, setImageBroken] = createSignal(false);
56
+
57
+ const url = () => local.data.url;
58
+ const valid = createMemo(() => isRenderableLink(url()));
59
+
60
+ // The effective payload = supplied data merged with any fetched metadata.
61
+ const effective = createMemo<LinkPreviewData>(() => ({ ...local.data, ...fetched() }));
62
+
63
+ // Lifecycle `ready` once on mount.
64
+ onMount(() => emit({ kind: 'ready', cardId: local.cardId }));
65
+
66
+ // Invalid URL → emit a single `error` (belt-and-suspenders; host also rejects bad schemes on open).
67
+ createEffect(
68
+ on(valid, (ok) => {
69
+ if (!ok) emit({ kind: 'error', cardId: local.cardId, message: `Invalid link url: ${url()}` });
70
+ }),
71
+ );
72
+
73
+ // Bare-URL path: when there's no metadata AND a fetcher is configured, resolve once.
74
+ createEffect(
75
+ on(
76
+ () => [local.data, valid()] as const,
77
+ ([data, ok]) => {
78
+ setImageBroken(false);
79
+ if (!ok || hasMetadata(data) || !hasLinkPreviewFetcher()) return;
80
+ let cancelled = false;
81
+ // Cancel a stale in-flight fetch if `data` changes before it resolves.
82
+ onCleanup(() => {
83
+ cancelled = true;
84
+ });
85
+ setLoading(true);
86
+ resolveLinkMetadata(data.url)
87
+ .then((meta) => {
88
+ if (!cancelled) setFetched(meta);
89
+ })
90
+ .catch(() => {
91
+ // Reject → fall back to the bare link chip; the URL itself is still usable.
92
+ if (!cancelled) setFetched({});
93
+ })
94
+ .finally(() => {
95
+ if (!cancelled) setLoading(false);
96
+ });
97
+ },
98
+ ),
99
+ );
100
+
101
+ const domain = createMemo(() => effective().domain ?? deriveDomain(url()) ?? url());
102
+ const heading = createMemo(() => effective().siteName ?? domain());
103
+ const titleText = createMemo(() => effective().title ?? domain());
104
+ const showImage = createMemo(() => Boolean(effective().image) && !imageBroken());
105
+
106
+ const activate = () => {
107
+ if (!valid()) return;
108
+ emit({ kind: 'open', cardId: local.cardId, url: url(), target: 'tab' });
109
+ };
110
+
111
+ const onClick = (e: MouseEvent) => {
112
+ // Intercept the anchor's default navigation so it routes through host policy.
113
+ // (Middle-click / cmd-click still open the real href in a new tab — acceptable + safe.)
114
+ e.preventDefault();
115
+ activate();
116
+ };
117
+ const onKeyDown = (e: KeyboardEvent) => {
118
+ if (e.key === 'Enter' || e.key === ' ') {
119
+ e.preventDefault();
120
+ activate();
121
+ }
122
+ };
123
+
124
+ // --- Invalid-link error chip ------------------------------------------
125
+ return (
126
+ <Show
127
+ when={valid()}
128
+ fallback={
129
+ <div
130
+ role="img"
131
+ aria-label="Invalid link"
132
+ class={cn(
133
+ 'flex items-center gap-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive dark:border-red-400/50 dark:bg-destructive/20 dark:text-red-400',
134
+ local.class,
135
+ )}
136
+ >
137
+ <LinkIcon size={16} aria-hidden="true" />
138
+ <span class="truncate">Invalid link</span>
139
+ </div>
140
+ }
141
+ >
142
+ <a
143
+ href={url()}
144
+ rel="noopener noreferrer"
145
+ aria-label={`Open ${titleText()} on ${domain()}`}
146
+ class={cn(
147
+ 'group block overflow-hidden rounded-xl border border-border bg-card text-card-foreground no-underline outline-none transition-shadow',
148
+ 'cursor-pointer hover:shadow-md focus-visible:ring-2 focus-visible:ring-ring',
149
+ local.class,
150
+ )}
151
+ onClick={onClick}
152
+ onKeyDown={onKeyDown}
153
+ >
154
+ <Show when={loading()}>
155
+ {/* Skeleton while the bare-URL fetcher resolves. */}
156
+ <div class="aspect-[16/9] w-full animate-pulse bg-muted" />
157
+ <div class="space-y-2 p-3">
158
+ <div class="h-3 w-1/3 animate-pulse rounded bg-muted" />
159
+ <div class="h-4 w-2/3 animate-pulse rounded bg-muted" />
160
+ <div class="h-3 w-full animate-pulse rounded bg-muted" />
161
+ </div>
162
+ </Show>
163
+ <Show when={!loading()}>
164
+ <Show when={showImage()}>
165
+ <img
166
+ src={effective().image}
167
+ alt={effective().imageAlt ?? ''}
168
+ role="img"
169
+ class="aspect-[16/9] w-full object-cover"
170
+ onError={() => setImageBroken(true)}
171
+ />
172
+ </Show>
173
+ <div class="space-y-1 p-3">
174
+ <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
175
+ <Show
176
+ when={effective().favicon}
177
+ fallback={<LinkIcon size={12} class="shrink-0 opacity-70" aria-hidden="true" />}
178
+ >
179
+ <img src={effective().favicon} alt="" class="h-3.5 w-3.5 shrink-0 rounded-sm" />
180
+ </Show>
181
+ <span class="truncate">{heading()}</span>
182
+ </div>
183
+ <div class="line-clamp-2 text-sm font-semibold text-foreground">{titleText()}</div>
184
+ <Show when={effective().description}>
185
+ <div class="line-clamp-3 text-xs text-muted-foreground">{effective().description}</div>
186
+ </Show>
187
+ </div>
188
+ </Show>
189
+ </a>
190
+ </Show>
191
+ );
192
+ }
193
+
194
+ export type { LinkPreviewData } from '../primitives/link-preview';
@@ -0,0 +1,181 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { For } from 'solid-js';
3
+ import { Loader, type LoaderVariant, type LoaderSize } from './loader';
4
+ import { componentDescription } from '../stories/docs/element-controls';
5
+
6
+ const meta = {
7
+ title: 'Solid (Advanced)/Elements/Loader',
8
+ component: Loader,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ layout: 'padded',
12
+ docs: {
13
+ controls: { exclude: ['use:eventListener'] },
14
+ description: componentDescription([
15
+ 'A loading indicator with twelve animated **variants** (spinners, dots, bars, terminal, and text-based) in three **sizes**.',
16
+ '**When to use:** while waiting on an async operation — a streaming response, a tool call, or any pending state.',
17
+ '**How to use:** pick a `variant` and `size`. Text variants (`text-blink`, `text-shimmer`, `loading-dots`) display the `text` prop (defaults to "Thinking").',
18
+ '**Placement:** inside assistant message bubbles, send-button states, empty states, or anywhere an in-progress signal is needed.',
19
+ ]),
20
+ },
21
+ },
22
+ argTypes: {
23
+ variant: {
24
+ control: 'select',
25
+ options: [
26
+ 'circular', 'classic', 'pulse', 'pulse-dot', 'dots', 'typing',
27
+ 'wave', 'bars', 'terminal', 'text-blink', 'text-shimmer', 'loading-dots',
28
+ ],
29
+ description: 'Animation style of the loader.',
30
+ table: { defaultValue: { summary: 'circular' } },
31
+ },
32
+ size: {
33
+ control: 'select',
34
+ options: ['sm', 'md', 'lg'],
35
+ description: 'Loader size preset.',
36
+ table: { defaultValue: { summary: 'md' } },
37
+ },
38
+ text: {
39
+ control: 'text',
40
+ description: 'Label shown by the text variants (`text-blink`, `text-shimmer`, `loading-dots`).',
41
+ table: { defaultValue: { summary: 'Thinking' } },
42
+ },
43
+ class: {
44
+ control: 'text',
45
+ description: 'Additional CSS classes for the loader element.',
46
+ },
47
+ },
48
+ args: {
49
+ variant: 'circular',
50
+ size: 'md',
51
+ text: 'Thinking',
52
+ },
53
+ render: (args) => <Loader {...args} />,
54
+ } satisfies Meta<typeof Loader>;
55
+
56
+ export default meta;
57
+ type Story = StoryObj<typeof meta>;
58
+
59
+ const IMPORT = `import { Loader } from '@kitn.ai/ui';`;
60
+ const src = (code: string) => ({
61
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
62
+ });
63
+
64
+ /** Interactive playground — switch variant, size, and text to explore every loader. */
65
+ export const Playground: Story = {
66
+ ...src(`<Loader variant="circular" size="md" />`),
67
+ };
68
+
69
+ export const Classic: Story = {
70
+ args: { variant: 'classic' },
71
+ ...src(`<Loader variant="classic" size="md" />`),
72
+ };
73
+
74
+ export const Pulse: Story = {
75
+ args: { variant: 'pulse' },
76
+ ...src(`<Loader variant="pulse" size="md" />`),
77
+ };
78
+
79
+ export const PulseDot: Story = {
80
+ args: { variant: 'pulse-dot' },
81
+ ...src(`<Loader variant="pulse-dot" size="md" />`),
82
+ };
83
+
84
+ export const Dots: Story = {
85
+ args: { variant: 'dots' },
86
+ ...src(`<Loader variant="dots" size="md" />`),
87
+ };
88
+
89
+ export const Typing: Story = {
90
+ args: { variant: 'typing' },
91
+ ...src(`<Loader variant="typing" size="md" />`),
92
+ };
93
+
94
+ export const Wave: Story = {
95
+ args: { variant: 'wave' },
96
+ ...src(`<Loader variant="wave" size="md" />`),
97
+ };
98
+
99
+ export const Bars: Story = {
100
+ args: { variant: 'bars' },
101
+ ...src(`<Loader variant="bars" size="md" />`),
102
+ };
103
+
104
+ export const Terminal: Story = {
105
+ args: { variant: 'terminal' },
106
+ ...src(`<Loader variant="terminal" size="md" />`),
107
+ };
108
+
109
+ export const TextBlink: Story = {
110
+ args: { variant: 'text-blink', text: 'Thinking' },
111
+ ...src(`<Loader variant="text-blink" text="Thinking" size="md" />`),
112
+ };
113
+
114
+ export const TextShimmer: Story = {
115
+ args: { variant: 'text-shimmer', text: 'Analyzing' },
116
+ ...src(`<Loader variant="text-shimmer" text="Analyzing" size="md" />`),
117
+ };
118
+
119
+ export const LoadingDots: Story = {
120
+ args: { variant: 'loading-dots', text: 'Processing' },
121
+ ...src(`<Loader variant="loading-dots" text="Processing" size="md" />`),
122
+ };
123
+
124
+ const allVariants: LoaderVariant[] = [
125
+ 'circular', 'classic', 'pulse', 'pulse-dot', 'dots', 'typing',
126
+ 'wave', 'bars', 'terminal', 'text-blink', 'text-shimmer', 'loading-dots',
127
+ ];
128
+
129
+ const allSizes: LoaderSize[] = ['sm', 'md', 'lg'];
130
+
131
+ /** Every variant across every size (showcase — not driven by controls). */
132
+ export const AllVariantsGrid: Story = {
133
+ render: () => (
134
+ <div class="space-y-6">
135
+ <For each={allVariants}>
136
+ {(variant) => (
137
+ <div class="flex items-center gap-6">
138
+ <span class="w-28 text-sm text-muted-foreground font-mono">{variant}</span>
139
+ <For each={allSizes}>
140
+ {(size) => (
141
+ <div class="flex items-center justify-center w-24 h-10">
142
+ <Loader
143
+ variant={variant}
144
+ size={size}
145
+ text={['text-blink', 'text-shimmer', 'loading-dots'].includes(variant) ? 'Loading' : undefined}
146
+ />
147
+ </div>
148
+ )}
149
+ </For>
150
+ </div>
151
+ )}
152
+ </For>
153
+ </div>
154
+ ),
155
+ ...src(`<Loader variant="circular" size="sm" />
156
+ <Loader variant="circular" size="md" />
157
+ <Loader variant="circular" size="lg" />`),
158
+ };
159
+
160
+ /** A single variant rendered at all three sizes (showcase). */
161
+ export const AllSizes: Story = {
162
+ render: () => (
163
+ <div class="flex items-end gap-6">
164
+ <div class="text-center space-y-2">
165
+ <Loader variant="circular" size="sm" />
166
+ <p class="text-xs text-muted-foreground">Small</p>
167
+ </div>
168
+ <div class="text-center space-y-2">
169
+ <Loader variant="circular" size="md" />
170
+ <p class="text-xs text-muted-foreground">Medium</p>
171
+ </div>
172
+ <div class="text-center space-y-2">
173
+ <Loader variant="circular" size="lg" />
174
+ <p class="text-xs text-muted-foreground">Large</p>
175
+ </div>
176
+ </div>
177
+ ),
178
+ ...src(`<Loader variant="circular" size="sm" />
179
+ <Loader variant="circular" size="md" />
180
+ <Loader variant="circular" size="lg" />`),
181
+ };