@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,333 @@
1
+ import { Switch, Match, For, Show } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export type LoaderVariant =
5
+ | 'circular'
6
+ | 'classic'
7
+ | 'pulse'
8
+ | 'pulse-dot'
9
+ | 'dots'
10
+ | 'typing'
11
+ | 'wave'
12
+ | 'bars'
13
+ | 'terminal'
14
+ | 'text-blink'
15
+ | 'text-shimmer'
16
+ | 'loading-dots';
17
+
18
+ export type LoaderSize = 'sm' | 'md' | 'lg';
19
+
20
+ export interface LoaderProps {
21
+ variant?: LoaderVariant;
22
+ size?: LoaderSize;
23
+ text?: string;
24
+ class?: string;
25
+ }
26
+
27
+ // --- CircularLoader ---
28
+
29
+ export function CircularLoader(props: { class?: string; size?: LoaderSize }) {
30
+ const size = () => props.size ?? 'md';
31
+ const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
32
+ return (
33
+ <div
34
+ class={cn(
35
+ 'border-primary animate-spin rounded-full border-2 border-t-transparent',
36
+ sizeClasses[size()],
37
+ props.class
38
+ )}
39
+ >
40
+ <span class="sr-only">Loading</span>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ // --- ClassicLoader ---
46
+
47
+ export function ClassicLoader(props: { class?: string; size?: LoaderSize }) {
48
+ const size = () => props.size ?? 'md';
49
+ const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
50
+ const barSizes = {
51
+ sm: { height: '6px', width: '1.5px' },
52
+ md: { height: '8px', width: '2px' },
53
+ lg: { height: '10px', width: '2.5px' },
54
+ };
55
+ return (
56
+ <div class={cn('relative', sizeClasses[size()], props.class)}>
57
+ <div class="absolute h-full w-full">
58
+ <For each={Array.from({ length: 12 }, (_, i) => i)}>
59
+ {(i) => (
60
+ <div
61
+ class="bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full"
62
+ style={{
63
+ top: '0',
64
+ left: '50%',
65
+ 'margin-left': size() === 'sm' ? '-0.75px' : size() === 'lg' ? '-1.25px' : '-1px',
66
+ 'transform-origin': `${size() === 'sm' ? '0.75px' : size() === 'lg' ? '1.25px' : '1px'} ${size() === 'sm' ? '10px' : size() === 'lg' ? '14px' : '12px'}`,
67
+ transform: `rotate(${i * 30}deg)`,
68
+ opacity: 0,
69
+ 'animation-delay': `${i * 0.1}s`,
70
+ height: barSizes[size()].height,
71
+ width: barSizes[size()].width,
72
+ }}
73
+ />
74
+ )}
75
+ </For>
76
+ </div>
77
+ <span class="sr-only">Loading</span>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ // --- PulseLoader ---
83
+
84
+ export function PulseLoader(props: { class?: string; size?: LoaderSize }) {
85
+ const size = () => props.size ?? 'md';
86
+ const sizeClasses = { sm: 'size-4', md: 'size-5', lg: 'size-6' };
87
+ return (
88
+ <div class={cn('relative', sizeClasses[size()], props.class)}>
89
+ <div class="border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2" />
90
+ <span class="sr-only">Loading</span>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ // --- PulseDotLoader ---
96
+
97
+ export function PulseDotLoader(props: { class?: string; size?: LoaderSize }) {
98
+ const size = () => props.size ?? 'md';
99
+ const sizeClasses = { sm: 'size-1', md: 'size-2', lg: 'size-3' };
100
+ return (
101
+ <div
102
+ class={cn(
103
+ 'bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full',
104
+ sizeClasses[size()],
105
+ props.class
106
+ )}
107
+ >
108
+ <span class="sr-only">Loading</span>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ // --- DotsLoader ---
114
+
115
+ export function DotsLoader(props: { class?: string; size?: LoaderSize }) {
116
+ const size = () => props.size ?? 'md';
117
+ const dotSizes = { sm: 'h-1.5 w-1.5', md: 'h-2 w-2', lg: 'h-2.5 w-2.5' };
118
+ const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
119
+ return (
120
+ <div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
121
+ <For each={[0, 1, 2]}>
122
+ {(i) => (
123
+ <div
124
+ class={cn(
125
+ 'bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full',
126
+ dotSizes[size()]
127
+ )}
128
+ style={{ 'animation-delay': `${i * 160}ms` }}
129
+ />
130
+ )}
131
+ </For>
132
+ <span class="sr-only">Loading</span>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ // --- TypingLoader ---
138
+
139
+ export function TypingLoader(props: { class?: string; size?: LoaderSize }) {
140
+ const size = () => props.size ?? 'md';
141
+ const dotSizes = { sm: 'h-1 w-1', md: 'h-1.5 w-1.5', lg: 'h-2 w-2' };
142
+ const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
143
+ return (
144
+ <div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
145
+ <For each={[0, 1, 2]}>
146
+ {(i) => (
147
+ <div
148
+ class={cn(
149
+ 'bg-primary animate-[typing_1s_infinite] rounded-full',
150
+ dotSizes[size()]
151
+ )}
152
+ style={{ 'animation-delay': `${i * 250}ms` }}
153
+ />
154
+ )}
155
+ </For>
156
+ <span class="sr-only">Loading</span>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ // --- WaveLoader ---
162
+
163
+ export function WaveLoader(props: { class?: string; size?: LoaderSize }) {
164
+ const size = () => props.size ?? 'md';
165
+ const barWidths = { sm: 'w-0.5', md: 'w-0.5', lg: 'w-1' };
166
+ const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
167
+ const heights: Record<LoaderSize, string[]> = {
168
+ sm: ['6px', '9px', '12px', '9px', '6px'],
169
+ md: ['8px', '12px', '16px', '12px', '8px'],
170
+ lg: ['10px', '15px', '20px', '15px', '10px'],
171
+ };
172
+ return (
173
+ <div class={cn('flex items-center gap-0.5', containerSizes[size()], props.class)}>
174
+ <For each={[0, 1, 2, 3, 4]}>
175
+ {(i) => (
176
+ <div
177
+ class={cn(
178
+ 'bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full',
179
+ barWidths[size()]
180
+ )}
181
+ style={{
182
+ 'animation-delay': `${i * 100}ms`,
183
+ height: heights[size()][i],
184
+ }}
185
+ />
186
+ )}
187
+ </For>
188
+ <span class="sr-only">Loading</span>
189
+ </div>
190
+ );
191
+ }
192
+
193
+ // --- BarsLoader ---
194
+
195
+ export function BarsLoader(props: { class?: string; size?: LoaderSize }) {
196
+ const size = () => props.size ?? 'md';
197
+ const barWidths = { sm: 'w-1', md: 'w-1.5', lg: 'w-2' };
198
+ const containerSizes = { sm: 'h-4 gap-1', md: 'h-5 gap-1.5', lg: 'h-6 gap-2' };
199
+ return (
200
+ <div class={cn('flex', containerSizes[size()], props.class)}>
201
+ <For each={[0, 1, 2]}>
202
+ {(i) => (
203
+ <div
204
+ class={cn(
205
+ 'bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]',
206
+ barWidths[size()]
207
+ )}
208
+ style={{ 'animation-delay': `${i * 0.2}s` }}
209
+ />
210
+ )}
211
+ </For>
212
+ <span class="sr-only">Loading</span>
213
+ </div>
214
+ );
215
+ }
216
+
217
+ // --- TerminalLoader ---
218
+
219
+ export function TerminalLoader(props: { class?: string; size?: LoaderSize }) {
220
+ const size = () => props.size ?? 'md';
221
+ const cursorSizes = { sm: 'h-3 w-1.5', md: 'h-4 w-2', lg: 'h-5 w-2.5' };
222
+ const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
223
+ const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
224
+ return (
225
+ <div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
226
+ <span class={cn('text-primary font-mono', textSizes[size()])}>{'>'}</span>
227
+ <div class={cn('bg-primary animate-[blink_1s_step-end_infinite]', cursorSizes[size()])} />
228
+ <span class="sr-only">Loading</span>
229
+ </div>
230
+ );
231
+ }
232
+
233
+ // --- TextBlinkLoader ---
234
+
235
+ export function TextBlinkLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
236
+ const size = () => props.size ?? 'md';
237
+ const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
238
+ return (
239
+ <div
240
+ class={cn(
241
+ 'animate-[text-blink_2s_ease-in-out_infinite] font-medium',
242
+ textSizes[size()],
243
+ props.class
244
+ )}
245
+ >
246
+ {props.text ?? 'Thinking'}
247
+ </div>
248
+ );
249
+ }
250
+
251
+ // --- TextShimmerLoader ---
252
+
253
+ export function TextShimmerLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
254
+ const size = () => props.size ?? 'md';
255
+ const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
256
+ return (
257
+ <div
258
+ class={cn(
259
+ 'bg-[linear-gradient(to_right,var(--color-muted-foreground)_40%,var(--color-foreground)_60%,var(--color-muted-foreground)_80%)]',
260
+ 'bg-size-[200%_auto] bg-clip-text font-medium text-transparent',
261
+ 'animate-[shimmer_4s_infinite_linear]',
262
+ textSizes[size()],
263
+ props.class
264
+ )}
265
+ >
266
+ {props.text ?? 'Thinking'}
267
+ </div>
268
+ );
269
+ }
270
+
271
+ // --- TextDotsLoader ---
272
+
273
+ export function TextDotsLoader(props: { text?: string; class?: string; size?: LoaderSize }) {
274
+ const size = () => props.size ?? 'md';
275
+ const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
276
+ return (
277
+ <div class={cn('inline-flex items-center', props.class)}>
278
+ <span class={cn('text-primary font-medium', textSizes[size()])}>
279
+ {props.text ?? 'Thinking'}
280
+ </span>
281
+ <span class="inline-flex">
282
+ <span class="text-primary animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
283
+ <span class="text-primary animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
284
+ <span class="text-primary animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
285
+ </span>
286
+ </div>
287
+ );
288
+ }
289
+
290
+ // --- Loader (variant dispatcher) ---
291
+
292
+ export function Loader(props: LoaderProps) {
293
+ return (
294
+ <Switch fallback={<CircularLoader size={props.size} class={props.class} />}>
295
+ <Match when={props.variant === 'circular'}>
296
+ <CircularLoader size={props.size} class={props.class} />
297
+ </Match>
298
+ <Match when={props.variant === 'classic'}>
299
+ <ClassicLoader size={props.size} class={props.class} />
300
+ </Match>
301
+ <Match when={props.variant === 'pulse'}>
302
+ <PulseLoader size={props.size} class={props.class} />
303
+ </Match>
304
+ <Match when={props.variant === 'pulse-dot'}>
305
+ <PulseDotLoader size={props.size} class={props.class} />
306
+ </Match>
307
+ <Match when={props.variant === 'dots'}>
308
+ <DotsLoader size={props.size} class={props.class} />
309
+ </Match>
310
+ <Match when={props.variant === 'typing'}>
311
+ <TypingLoader size={props.size} class={props.class} />
312
+ </Match>
313
+ <Match when={props.variant === 'wave'}>
314
+ <WaveLoader size={props.size} class={props.class} />
315
+ </Match>
316
+ <Match when={props.variant === 'bars'}>
317
+ <BarsLoader size={props.size} class={props.class} />
318
+ </Match>
319
+ <Match when={props.variant === 'terminal'}>
320
+ <TerminalLoader size={props.size} class={props.class} />
321
+ </Match>
322
+ <Match when={props.variant === 'text-blink'}>
323
+ <TextBlinkLoader text={props.text} size={props.size} class={props.class} />
324
+ </Match>
325
+ <Match when={props.variant === 'text-shimmer'}>
326
+ <TextShimmerLoader text={props.text} size={props.size} class={props.class} />
327
+ </Match>
328
+ <Match when={props.variant === 'loading-dots'}>
329
+ <TextDotsLoader text={props.text} size={props.size} class={props.class} />
330
+ </Match>
331
+ </Switch>
332
+ );
333
+ }
@@ -0,0 +1,180 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { Markdown } from './markdown';
3
+ import { componentDescription } from '../stories/docs/element-controls';
4
+
5
+ const meta = {
6
+ title: 'Solid (Advanced)/Elements/Markdown',
7
+ component: Markdown,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ controls: { exclude: ['use:eventListener'] },
13
+ description: componentDescription([
14
+ 'Renders a Markdown string to styled HTML (GFM enabled — tables, lists, blockquotes), splitting fenced code into highlighted `CodeBlock`s.',
15
+ '**When to use:** to display assistant message content, documentation, or any rich text authored in Markdown.',
16
+ '**How to use:** pass the Markdown string as `content`; optionally set `codeTheme` for code fences and `class` for the prose container. Prose sizing follows the surrounding `ChatConfig`.',
17
+ '**Placement:** inside `MessageContent`, response panels, or anywhere formatted text is shown.',
18
+ ]),
19
+ },
20
+ },
21
+ argTypes: {
22
+ content: {
23
+ control: 'text',
24
+ description: 'The Markdown source string to render.',
25
+ },
26
+ id: {
27
+ control: 'text',
28
+ description: 'Optional stable id used for block keys (auto-generated if omitted).',
29
+ },
30
+ codeTheme: {
31
+ control: 'text',
32
+ description: 'Shiki theme name applied to fenced code blocks (falls back to the chat config theme).',
33
+ },
34
+ class: {
35
+ control: 'text',
36
+ description: 'Additional CSS classes for the prose container.',
37
+ },
38
+ },
39
+ args: {
40
+ content: 'This is a simple paragraph of text rendered through the **Markdown** component.',
41
+ },
42
+ render: (args) => <Markdown {...args} />,
43
+ } satisfies Meta<typeof Markdown>;
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof meta>;
47
+
48
+ const IMPORT = `import { Markdown } from '@kitn.ai/ui';`;
49
+ const src = (code: string) => ({
50
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
51
+ });
52
+
53
+ /** Interactive playground — edit the `content` control to render any Markdown. */
54
+ export const Playground: Story = {
55
+ ...src(`<Markdown content="This is a simple paragraph of **Markdown** text." />`),
56
+ };
57
+
58
+ export const PlainText: Story = {
59
+ args: {
60
+ content: 'This is a simple paragraph of text rendered through the Markdown component.',
61
+ },
62
+ ...src(`<Markdown content="This is a simple paragraph of text rendered through the Markdown component." />`),
63
+ };
64
+
65
+ export const Headings: Story = {
66
+ args: {
67
+ content: `# Heading 1
68
+ ## Heading 2
69
+ ### Heading 3
70
+ #### Heading 4
71
+
72
+ Some text below the headings.`,
73
+ },
74
+ ...src(`<Markdown content={\`# Heading 1
75
+ ## Heading 2
76
+ ### Heading 3
77
+
78
+ Some text below the headings.\`} />`),
79
+ };
80
+
81
+ export const CodeBlocks: Story = {
82
+ args: {
83
+ content: `Here is some inline \`code\` in a paragraph.
84
+
85
+ \`\`\`typescript
86
+ const x: number = 42;
87
+ console.log(x);
88
+ \`\`\`
89
+
90
+ And another block:
91
+
92
+ \`\`\`python
93
+ print("hello world")
94
+ \`\`\``,
95
+ },
96
+ ...src(`<Markdown content={\`Here is some inline \\\`code\\\` in a paragraph.
97
+
98
+ \\\`\\\`\\\`typescript
99
+ const x: number = 42;
100
+ console.log(x);
101
+ \\\`\\\`\\\`\`} />`),
102
+ };
103
+
104
+ export const Lists: Story = {
105
+ args: {
106
+ content: `### Unordered List
107
+ - First item
108
+ - Second item
109
+ - Nested item
110
+ - Another nested
111
+ - Third item
112
+
113
+ ### Ordered List
114
+ 1. Step one
115
+ 2. Step two
116
+ 3. Step three`,
117
+ },
118
+ ...src(`<Markdown content={\`### Unordered List
119
+ - First item
120
+ - Second item
121
+ - Nested item
122
+
123
+ ### Ordered List
124
+ 1. Step one
125
+ 2. Step two\`} />`),
126
+ };
127
+
128
+ export const GFMTable: Story = {
129
+ args: {
130
+ content: `### Comparison Table
131
+
132
+ | Feature | SolidJS | React | Svelte |
133
+ |---------|---------|-------|--------|
134
+ | Reactivity | Fine-grained | Virtual DOM | Compiler |
135
+ | Bundle Size | ~7KB | ~40KB | ~2KB |
136
+ | Performance | Excellent | Good | Excellent |
137
+ | Learning Curve | Moderate | Moderate | Easy |`,
138
+ },
139
+ ...src(`<Markdown content={\`| Feature | SolidJS | React |
140
+ |---------|---------|-------|
141
+ | Reactivity | Fine-grained | Virtual DOM |
142
+ | Bundle Size | ~7KB | ~40KB |\`} />`),
143
+ };
144
+
145
+ export const RichContent: Story = {
146
+ args: {
147
+ class: 'max-w-2xl',
148
+ content: `# Project Overview
149
+
150
+ This is a **comprehensive** guide to building modern web applications.
151
+
152
+ ## Key Technologies
153
+
154
+ - **SolidJS** -- Reactive UI framework
155
+ - **TypeScript** -- Type-safe JavaScript
156
+ - **Tailwind CSS** -- Utility-first styling
157
+
158
+ ## Getting Started
159
+
160
+ \`\`\`bash
161
+ pnpm create solid
162
+ cd my-app
163
+ pnpm install
164
+ pnpm dev
165
+ \`\`\`
166
+
167
+ > **Note:** Make sure you have Node.js 18+ installed.
168
+
169
+ ## Architecture
170
+
171
+ | Layer | Technology | Purpose |
172
+ |-------|-----------|---------|
173
+ | UI | SolidJS | Components |
174
+ | State | Signals | Reactivity |
175
+ | Styling | Tailwind | Design |
176
+
177
+ For more info, visit [solidjs.com](https://solidjs.com).`,
178
+ },
179
+ ...src(`<Markdown class="max-w-2xl" content={richMarkdown} />`),
180
+ };
@@ -0,0 +1,81 @@
1
+ import { splitProps, createMemo, createUniqueId, For, Show, Switch, Match } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+ import { marked } from 'marked';
4
+ import { CodeBlock, CodeBlockCode } from './code-block';
5
+ import { useChatConfig, textClass } from '../primitives/chat-config';
6
+
7
+ marked.setOptions({ gfm: true, breaks: true });
8
+
9
+ export interface MarkdownProps {
10
+ content: string;
11
+ id?: string;
12
+ class?: string;
13
+ codeTheme?: string;
14
+ }
15
+
16
+ interface ParsedBlock {
17
+ type: 'markdown' | 'code';
18
+ content: string;
19
+ language?: string;
20
+ }
21
+
22
+ function parseMarkdownIntoBlocks(markdown: string): ParsedBlock[] {
23
+ const tokens = marked.lexer(markdown);
24
+ return tokens.map((token) => {
25
+ if (token.type === 'code') {
26
+ return {
27
+ type: 'code' as const,
28
+ content: token.text,
29
+ language: token.lang || undefined,
30
+ };
31
+ }
32
+ return {
33
+ type: 'markdown' as const,
34
+ content: token.raw,
35
+ };
36
+ });
37
+ }
38
+
39
+ function MarkdownBlock(props: { content: string }) {
40
+ const html = createMemo(() => {
41
+ try {
42
+ return marked.parse(props.content, { async: false }) as string;
43
+ } catch {
44
+ return props.content;
45
+ }
46
+ });
47
+
48
+ return <div innerHTML={html()} />;
49
+ }
50
+
51
+ function Markdown(props: MarkdownProps) {
52
+ const [local] = splitProps(props, ['content', 'id', 'class', 'codeTheme']);
53
+ const config = useChatConfig();
54
+ const blockId = () => local.id ?? createUniqueId();
55
+ const blocks = createMemo(() => parseMarkdownIntoBlocks(local.content));
56
+
57
+ return (
58
+ <div class={cn('chat-markdown max-w-none break-words whitespace-normal [&>div:first-child>p:first-child]:mt-0 [&>div:last-child>p:last-child]:mb-0', textClass(config.proseSize()), local.class)}>
59
+ <For each={blocks()}>
60
+ {(block) => (
61
+ <Switch>
62
+ <Match when={block.type === 'code'}>
63
+ <CodeBlock class="my-4">
64
+ <CodeBlockCode
65
+ code={block.content}
66
+ language={block.language}
67
+ theme={local.codeTheme ?? config.codeTheme()}
68
+ />
69
+ </CodeBlock>
70
+ </Match>
71
+ <Match when={block.type === 'markdown'}>
72
+ <MarkdownBlock content={block.content} />
73
+ </Match>
74
+ </Switch>
75
+ )}
76
+ </For>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ export { Markdown };