@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,138 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { fn } from 'storybook/test';
3
+ import { FeedbackBar } from './feedback-bar';
4
+ import { componentDescription } from '../stories/docs/element-controls';
5
+
6
+ const meta = {
7
+ title: 'Solid (Advanced)/Elements/FeedbackBar',
8
+ component: FeedbackBar,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ layout: 'padded',
12
+ docs: {
13
+ controls: { exclude: ['use:eventListener'] },
14
+ description: componentDescription([
15
+ 'An inline bar that prompts the user to rate a response. It owns its own flow — it asks, optionally collects a category + comment on a not-helpful vote, then confirms with a thank-you, all in place (the way ChatGPT/Claude do it). It does **not** disappear on a vote; only the close (X) button dismisses it.',
16
+ '**When to use:** after an assistant message, to collect quick helpful / not-helpful feedback (optionally with a reason).',
17
+ '**How to use:** set a `title`; for the richer flow set `collect-detail` and pass `categories`. The vote fires `onFeedback` immediately; the optional detail fires `onSubmitDetail`; `onClose` dismisses the bar.',
18
+ '**Placement:** directly beneath a completed assistant message, or in a message action row.',
19
+ ]),
20
+ },
21
+ },
22
+ argTypes: {
23
+ title: {
24
+ control: 'text',
25
+ description: 'Prompt text shown next to the rating buttons.',
26
+ },
27
+ icon: {
28
+ control: false,
29
+ description: 'Optional leading icon element shown before the title.',
30
+ },
31
+ collectDetail: {
32
+ control: 'boolean',
33
+ description: 'When on, a not-helpful vote opens an optional detail form (category chips + comment) before the thank-you.',
34
+ },
35
+ categories: {
36
+ control: 'object',
37
+ description: 'Category chips offered in the detail form.',
38
+ },
39
+ thanksMessage: {
40
+ control: 'text',
41
+ description: 'Confirmation copy shown after a vote/submit.',
42
+ },
43
+ class: {
44
+ control: 'text',
45
+ description: 'Additional CSS classes for the root element.',
46
+ },
47
+ onFeedback: {
48
+ action: 'feedback',
49
+ description: 'Fired with `value: "helpful" | "not-helpful"` immediately when a rating button is clicked (the vote is recorded even if the detail form is skipped).',
50
+ table: { category: 'Events' },
51
+ },
52
+ onSubmitDetail: {
53
+ action: 'submitDetail',
54
+ description: 'Fired with `{ value, category?, comment? }` when the optional detail form is submitted.',
55
+ table: { category: 'Events' },
56
+ },
57
+ onClose: {
58
+ action: 'close',
59
+ description: 'Fired when the close (X) button is clicked.',
60
+ table: { category: 'Events' },
61
+ },
62
+ },
63
+ args: {
64
+ title: 'Was this response helpful?',
65
+ onFeedback: fn(),
66
+ onSubmitDetail: fn(),
67
+ onClose: fn(),
68
+ },
69
+ render: (args) => <FeedbackBar {...args} />,
70
+ } satisfies Meta<typeof FeedbackBar>;
71
+
72
+ export default meta;
73
+ type Story = StoryObj<typeof meta>;
74
+
75
+ const IMPORT = `import { FeedbackBar } from '@kitn.ai/ui';`;
76
+ const src = (code: string) => ({
77
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
78
+ });
79
+
80
+ /** Interactive playground — tweak the controls to explore the feedback bar. */
81
+ export const Playground: Story = {
82
+ ...src(`<FeedbackBar
83
+ title="Was this response helpful?"
84
+ onFeedback={(value) => console.log('feedback:', value)}
85
+ onClose={() => {}}
86
+ />`),
87
+ };
88
+
89
+ export const CustomTitle: Story = {
90
+ args: { title: 'Rate this answer' },
91
+ ...src(`<FeedbackBar title="Rate this answer" onFeedback={(value) => console.log('feedback:', value)} />`),
92
+ };
93
+
94
+ const SmileyIcon = () => (
95
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-muted-foreground">
96
+ <circle cx="12" cy="12" r="10" />
97
+ <path d="M8 14s1.5 2 4 2 4-2 4-2" />
98
+ <line x1="9" y1="9" x2="9.01" y2="9" />
99
+ <line x1="15" y1="9" x2="15.01" y2="9" />
100
+ </svg>
101
+ );
102
+
103
+ export const WithIcon: Story = {
104
+ args: { title: 'How did I do?', icon: <SmileyIcon /> },
105
+ ...src(`<FeedbackBar
106
+ title="How did I do?"
107
+ icon={<SmileyIcon />}
108
+ onFeedback={(value) => console.log('feedback:', value)}
109
+ />`),
110
+ };
111
+
112
+ /** The full flow: a not-helpful vote opens an optional detail form (category
113
+ * chips + comment) before the thank-you. A helpful vote skips straight to thanks.
114
+ * Click 👎 to see the detail step. */
115
+ export const WithDetail: Story = {
116
+ args: {
117
+ title: 'Was this response helpful?',
118
+ collectDetail: true,
119
+ categories: ['Inaccurate', 'Not helpful', 'Unsafe', 'Other'],
120
+ },
121
+ ...src(`<FeedbackBar
122
+ title="Was this response helpful?"
123
+ collectDetail
124
+ categories={['Inaccurate', 'Not helpful', 'Unsafe', 'Other']}
125
+ onFeedback={(value) => console.log('vote:', value)}
126
+ onSubmitDetail={(d) => console.log('detail:', d)} // { value, category?, comment? }
127
+ />`),
128
+ };
129
+
130
+ /** A custom confirmation message shown after the vote. */
131
+ export const CustomThanks: Story = {
132
+ args: { title: 'Rate this answer', thanksMessage: 'Got it — thanks for helping us improve!' },
133
+ ...src(`<FeedbackBar
134
+ title="Rate this answer"
135
+ thanksMessage="Got it — thanks for helping us improve!"
136
+ onFeedback={(value) => console.log('feedback:', value)}
137
+ />`),
138
+ };
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import '@testing-library/jest-dom/vitest';
3
+ import { render, cleanup, fireEvent } from '@solidjs/testing-library';
4
+ import { FeedbackBar } from './feedback-bar';
5
+
6
+ afterEach(cleanup);
7
+
8
+ describe('FeedbackBar', () => {
9
+ it('shows the prompt and rating buttons in the ask phase', () => {
10
+ const { getByText, getByLabelText } = render(() => <FeedbackBar title="Helpful?" />);
11
+ expect(getByText('Helpful?')).toBeInTheDocument();
12
+ expect(getByLabelText('Helpful')).toBeInTheDocument();
13
+ expect(getByLabelText('Not helpful')).toBeInTheDocument();
14
+ });
15
+
16
+ it('fires onFeedback immediately and confirms in place (no detail by default)', () => {
17
+ const onFeedback = vi.fn();
18
+ const { getByLabelText, getByText, queryByLabelText } = render(() => (
19
+ <FeedbackBar title="Helpful?" onFeedback={onFeedback} />
20
+ ));
21
+ fireEvent.click(getByLabelText('Helpful'));
22
+ expect(onFeedback).toHaveBeenCalledWith('helpful');
23
+ // Confirms in place — does not disappear; the prompt is replaced by thanks.
24
+ expect(getByText('Thanks for your feedback')).toBeInTheDocument();
25
+ expect(queryByLabelText('Helpful')).not.toBeInTheDocument();
26
+ });
27
+
28
+ it('a not-helpful vote goes straight to thanks when collectDetail is off', () => {
29
+ const { getByLabelText, getByText, queryByText } = render(() => <FeedbackBar title="Helpful?" />);
30
+ fireEvent.click(getByLabelText('Not helpful'));
31
+ expect(getByText('Thanks for your feedback')).toBeInTheDocument();
32
+ expect(queryByText('What went wrong?')).not.toBeInTheDocument();
33
+ });
34
+
35
+ it('opens the detail form on a not-helpful vote when collectDetail is set', () => {
36
+ const onFeedback = vi.fn();
37
+ const { getByLabelText, getByText } = render(() => (
38
+ <FeedbackBar title="Helpful?" collectDetail categories={['Inaccurate', 'Unhelpful']} onFeedback={onFeedback} />
39
+ ));
40
+ fireEvent.click(getByLabelText('Not helpful'));
41
+ // Vote still recorded immediately, AND the detail form appears.
42
+ expect(onFeedback).toHaveBeenCalledWith('not-helpful');
43
+ expect(getByText('What went wrong?')).toBeInTheDocument();
44
+ expect(getByText('Inaccurate')).toBeInTheDocument();
45
+ });
46
+
47
+ it('a helpful vote skips the detail form even when collectDetail is set', () => {
48
+ const { getByLabelText, getByText, queryByText } = render(() => (
49
+ <FeedbackBar title="Helpful?" collectDetail categories={['Inaccurate']} />
50
+ ));
51
+ fireEvent.click(getByLabelText('Helpful'));
52
+ expect(getByText('Thanks for your feedback')).toBeInTheDocument();
53
+ expect(queryByText('What went wrong?')).not.toBeInTheDocument();
54
+ });
55
+
56
+ it('submits the detail form with the selected category and comment', () => {
57
+ const onSubmitDetail = vi.fn();
58
+ const { getByLabelText, getByText, getByPlaceholderText } = render(() => (
59
+ <FeedbackBar
60
+ title="Helpful?"
61
+ collectDetail
62
+ categories={['Inaccurate', 'Unhelpful']}
63
+ onSubmitDetail={onSubmitDetail}
64
+ />
65
+ ));
66
+ fireEvent.click(getByLabelText('Not helpful'));
67
+ fireEvent.click(getByText('Inaccurate'));
68
+ fireEvent.input(getByPlaceholderText('Tell us more (optional)'), { target: { value: 'wrong answer' } });
69
+ fireEvent.click(getByText('Submit'));
70
+ expect(onSubmitDetail).toHaveBeenCalledWith({
71
+ value: 'not-helpful',
72
+ category: 'Inaccurate',
73
+ comment: 'wrong answer',
74
+ });
75
+ expect(getByText('Thanks for your feedback')).toBeInTheDocument();
76
+ });
77
+
78
+ it('Skip dismisses the detail form to thanks without firing onSubmitDetail', () => {
79
+ const onSubmitDetail = vi.fn();
80
+ const { getByLabelText, getByText } = render(() => (
81
+ <FeedbackBar title="Helpful?" collectDetail categories={['Inaccurate']} onSubmitDetail={onSubmitDetail} />
82
+ ));
83
+ fireEvent.click(getByLabelText('Not helpful'));
84
+ fireEvent.click(getByText('Skip'));
85
+ expect(onSubmitDetail).not.toHaveBeenCalled();
86
+ expect(getByText('Thanks for your feedback')).toBeInTheDocument();
87
+ });
88
+
89
+ it('fires onClose when dismissed from the ask phase', () => {
90
+ const onClose = vi.fn();
91
+ const { getByLabelText } = render(() => <FeedbackBar title="Helpful?" onClose={onClose} />);
92
+ fireEvent.click(getByLabelText('Close'));
93
+ expect(onClose).toHaveBeenCalled();
94
+ });
95
+
96
+ it('honors a custom thanksMessage', () => {
97
+ const { getByLabelText, getByText } = render(() => (
98
+ <FeedbackBar title="Helpful?" thanksMessage="Got it — thank you!" />
99
+ ));
100
+ fireEvent.click(getByLabelText('Helpful'));
101
+ expect(getByText('Got it — thank you!')).toBeInTheDocument();
102
+ });
103
+ });
@@ -0,0 +1,166 @@
1
+ import { type JSX, Switch, Match, For, Show, createSignal } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+ import { ThumbsUp, ThumbsDown, X, Check } from 'lucide-solid';
4
+ import { Button } from '../ui/button';
5
+ import { Textarea } from '../ui/textarea';
6
+
7
+ export type FeedbackValue = 'helpful' | 'not-helpful';
8
+
9
+ /** Payload of the optional detail form (phase 2). */
10
+ export interface FeedbackDetail {
11
+ value: FeedbackValue;
12
+ /** The selected category chip, if any. */
13
+ category?: string;
14
+ /** The free-text comment, if entered. */
15
+ comment?: string;
16
+ }
17
+
18
+ export interface FeedbackBarProps {
19
+ class?: string;
20
+ /** Prompt shown in the initial "ask" state. */
21
+ title?: string;
22
+ /** Optional leading icon shown before the title. */
23
+ icon?: JSX.Element;
24
+ /** When set, a not-helpful vote opens an optional detail form before the
25
+ * thank-you confirmation. Off → a vote goes straight to thanks. */
26
+ collectDetail?: boolean;
27
+ /** Optional category chips offered in the detail form. */
28
+ categories?: string[];
29
+ /** Heading for the detail form. */
30
+ detailTitle?: string;
31
+ /** Placeholder for the detail comment box. */
32
+ detailPlaceholder?: string;
33
+ /** Submit button label in the detail form. */
34
+ submitLabel?: string;
35
+ /** Confirmation copy shown after a vote/submit. */
36
+ thanksMessage?: string;
37
+ /** Fired immediately when a rating button is clicked (the vote is recorded
38
+ * even if the user never fills in the detail form). */
39
+ onFeedback?: (value: FeedbackValue) => void;
40
+ /** Fired when the optional detail form is submitted. */
41
+ onSubmitDetail?: (detail: FeedbackDetail) => void;
42
+ /** Fired when the bar is dismissed via the close (X) button. */
43
+ onClose?: () => void;
44
+ }
45
+
46
+ type Phase = 'ask' | 'detail' | 'thanks';
47
+
48
+ /**
49
+ * An inline thumbs up/down feedback bar that owns its own flow: it asks, then
50
+ * (optionally, on a not-helpful vote) collects a category + comment, then
51
+ * confirms with a thank-you — all in place, the way ChatGPT/Claude do it. The
52
+ * vote fires `onFeedback` immediately; the optional detail fires
53
+ * `onSubmitDetail`; `onClose` dismisses the bar.
54
+ */
55
+ export function FeedbackBar(props: FeedbackBarProps) {
56
+ const [phase, setPhase] = createSignal<Phase>('ask');
57
+ const [vote, setVote] = createSignal<FeedbackValue>();
58
+ const [category, setCategory] = createSignal<string>();
59
+ const [comment, setComment] = createSignal('');
60
+
61
+ const rate = (value: FeedbackValue) => {
62
+ setVote(value);
63
+ props.onFeedback?.(value);
64
+ setPhase(value === 'not-helpful' && props.collectDetail ? 'detail' : 'thanks');
65
+ };
66
+
67
+ const submitDetail = () => {
68
+ props.onSubmitDetail?.({ value: vote()!, category: category(), comment: comment() || undefined });
69
+ setPhase('thanks');
70
+ };
71
+
72
+ const iconBtn =
73
+ 'text-muted-foreground hover:text-foreground flex size-8 items-center justify-center rounded-md transition-colors';
74
+
75
+ return (
76
+ <div
77
+ class={cn('bg-background border-border inline-flex rounded-[12px] border text-sm', props.class)}
78
+ >
79
+ <Switch>
80
+ {/* ── Ask ── */}
81
+ <Match when={phase() === 'ask'}>
82
+ <div class="flex w-full items-center justify-between">
83
+ <div class="flex flex-1 items-center justify-start gap-4 py-3 pl-4">
84
+ <Show when={props.icon}>{props.icon}</Show>
85
+ <span class="text-foreground font-medium">{props.title}</span>
86
+ </div>
87
+ <div class="flex items-center justify-center gap-0.5 px-3 py-0">
88
+ <button type="button" class={iconBtn} aria-label="Helpful" onClick={() => rate('helpful')}>
89
+ <ThumbsUp class="size-4" />
90
+ </button>
91
+ <button type="button" class={iconBtn} aria-label="Not helpful" onClick={() => rate('not-helpful')}>
92
+ <ThumbsDown class="size-4" />
93
+ </button>
94
+ </div>
95
+ <div class="border-border flex items-center justify-center border-l">
96
+ <button
97
+ type="button"
98
+ onClick={() => props.onClose?.()}
99
+ class="text-muted-foreground hover:text-foreground flex items-center justify-center rounded-md p-3"
100
+ aria-label="Close"
101
+ >
102
+ <X class="size-5" />
103
+ </button>
104
+ </div>
105
+ </div>
106
+ </Match>
107
+
108
+ {/* ── Detail (phase 2, not-helpful only) ── */}
109
+ <Match when={phase() === 'detail'}>
110
+ <div class="flex w-[min(22rem,80vw)] flex-col gap-2.5 p-3">
111
+ <div class="flex items-center justify-between">
112
+ <span class="text-foreground font-medium">{props.detailTitle ?? 'What went wrong?'}</span>
113
+ <button
114
+ type="button"
115
+ aria-label="Skip"
116
+ onClick={() => setPhase('thanks')}
117
+ class="text-muted-foreground hover:text-foreground"
118
+ >
119
+ <X class="size-4" />
120
+ </button>
121
+ </div>
122
+ <Show when={props.categories?.length}>
123
+ <div class="flex flex-wrap gap-1.5">
124
+ <For each={props.categories}>
125
+ {(c) => (
126
+ <button
127
+ type="button"
128
+ aria-pressed={category() === c}
129
+ onClick={() => setCategory(category() === c ? undefined : c)}
130
+ class={cn(
131
+ 'rounded-full border px-2.5 py-1 text-xs transition-colors',
132
+ category() === c
133
+ ? 'border-primary bg-primary text-primary-foreground'
134
+ : 'border-border text-muted-foreground hover:text-foreground',
135
+ )}
136
+ >
137
+ {c}
138
+ </button>
139
+ )}
140
+ </For>
141
+ </div>
142
+ </Show>
143
+ <Textarea
144
+ value={comment()}
145
+ onInput={(e) => setComment(e.currentTarget.value)}
146
+ placeholder={props.detailPlaceholder ?? 'Tell us more (optional)'}
147
+ class="border-border min-h-[3rem] rounded-md border px-2 py-1.5"
148
+ />
149
+ <div class="flex justify-end gap-2">
150
+ <Button variant="ghost" size="sm" onClick={() => setPhase('thanks')}>Skip</Button>
151
+ <Button variant="default" size="sm" onClick={submitDetail}>{props.submitLabel ?? 'Submit'}</Button>
152
+ </div>
153
+ </div>
154
+ </Match>
155
+
156
+ {/* ── Thanks ── */}
157
+ <Match when={phase() === 'thanks'}>
158
+ <div class="flex items-center gap-2 px-4 py-3" role="status">
159
+ <Check class="size-4 text-emerald-500" />
160
+ <span class="text-foreground font-medium">{props.thanksMessage ?? 'Thanks for your feedback'}</span>
161
+ </div>
162
+ </Match>
163
+ </Switch>
164
+ </div>
165
+ );
166
+ }
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal } from 'solid-js';
3
+ import { FileTree, type FileTreeFile } from './file-tree';
4
+ import { componentDescription } from '../stories/docs/element-controls';
5
+
6
+ const FILES: FileTreeFile[] = [
7
+ { path: 'index.html', type: 'html' },
8
+ { path: 'about.html', type: 'html' },
9
+ { path: 'css/site.css', type: 'other', language: 'css' },
10
+ { path: 'src/app.ts', type: 'other', language: 'ts' },
11
+ { path: 'src/lib/format.ts', type: 'other', language: 'ts' },
12
+ { path: 'src/lib/parse.ts', type: 'other', language: 'ts' },
13
+ { path: 'assets/logo.svg', type: 'image' },
14
+ { path: 'assets/report.pdf', type: 'pdf' },
15
+ ];
16
+
17
+ const meta = {
18
+ title: 'Solid (Advanced)/Elements/FileTree',
19
+ component: FileTree,
20
+ tags: ['autodocs'],
21
+ parameters: {
22
+ layout: 'padded',
23
+ docs: {
24
+ description: componentDescription([
25
+ 'A collapsible, keyboard-navigable file explorer built from a flat list of `/`-delimited paths — nested folders are derived automatically. ARIA `tree`/`treeitem`.',
26
+ '**When to use:** any file/folder tree — the Code tab of `Artifact` uses it, and it is exported for standalone reuse (project explorers, attachment browsers, doc outlines).',
27
+ '**How to use:** pass `files` (`{ path, url?, code?, language?, type? }[]`); folders come from `/` in each path and `type` picks the icon. Control highlight via `activeFile`, initial open folders via `defaultExpanded`, and handle `onSelect(path, file)`. Arrow keys navigate; Enter/Space selects or toggles.',
28
+ '**Placement:** a sidebar/explorer column.',
29
+ ]),
30
+ },
31
+ },
32
+ argTypes: {
33
+ files: { control: false },
34
+ activeFile: { control: 'text' },
35
+ },
36
+ args: { files: FILES, activeFile: 'src/app.ts' },
37
+ render: (args) => (
38
+ <div class="w-64 h-80 overflow-auto rounded-lg border border-border scrollbar-thin">
39
+ <FileTree files={args.files} activeFile={args.activeFile} />
40
+ </div>
41
+ ),
42
+ } satisfies Meta<typeof FileTree>;
43
+
44
+ export default meta;
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ const IMPORT = `import { FileTree } from '@kitn.ai/ui';`;
48
+ const src = (code: string) => ({
49
+ parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
50
+ });
51
+
52
+ export const Playground: Story = {
53
+ ...src(`<FileTree files={files} activeFile="src/app.ts" onSelect={(p) => set(p)} />`),
54
+ };
55
+
56
+ /** Drives selection from `onSelect` — the controlled pattern. */
57
+ export const Interactive: Story = {
58
+ render: () => {
59
+ const [active, setActive] = createSignal('src/lib/format.ts');
60
+ return (
61
+ <div class="flex flex-col gap-2">
62
+ <div class="w-64 h-80 overflow-auto rounded-lg border border-border scrollbar-thin">
63
+ <FileTree files={FILES} activeFile={active()} onSelect={(p) => setActive(p)} />
64
+ </div>
65
+ <span class="text-sm text-muted-foreground">
66
+ Selected: <code>{active()}</code>
67
+ </span>
68
+ </div>
69
+ );
70
+ },
71
+ ...src(`const [active, setActive] = createSignal('src/lib/format.ts');
72
+ <FileTree files={files} activeFile={active()} onSelect={setActive} />`),
73
+ };