@kitnai/chat 0.6.0 → 0.8.0

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 (211) hide show
  1. package/README.md +9 -9
  2. package/dist/custom-elements.json +1676 -881
  3. package/dist/kitn-chat.es.js +36 -36
  4. package/dist/llms/llms-full.txt +316 -155
  5. package/dist/llms/llms.txt +18 -18
  6. package/dist/schemas/card-envelope.schema.json +14 -0
  7. package/dist/schemas/card-event.schema.json +12 -0
  8. package/dist/schemas/confirm.schema.json +65 -0
  9. package/dist/schemas/embed.schema.json +65 -0
  10. package/dist/schemas/form.result.schema.json +7 -0
  11. package/dist/schemas/form.schema.json +33 -0
  12. package/dist/schemas/link.schema.json +56 -0
  13. package/dist/schemas/task-list.result.schema.json +16 -0
  14. package/dist/schemas/task-list.schema.json +78 -0
  15. package/dist/theme.tokens.css +65 -65
  16. package/dist/tsx-B8rCNbgL.js +1 -0
  17. package/dist/typescript-RycA9KXf.js +1 -0
  18. package/frameworks/react/index.tsx +382 -193
  19. package/frameworks/react/runtime.tsx +2 -2
  20. package/llms-full.txt +316 -155
  21. package/llms.txt +18 -18
  22. package/package.json +5 -2
  23. package/src/components/artifact.stories.tsx +138 -0
  24. package/src/components/artifact.tsx +581 -0
  25. package/src/components/attachments.stories.tsx +7 -8
  26. package/src/components/attachments.tsx +2 -2
  27. package/src/components/card.tsx +110 -0
  28. package/src/components/chain-of-thought.stories.tsx +7 -8
  29. package/src/components/chat-container.stories.tsx +7 -8
  30. package/src/components/chat-container.tsx +4 -0
  31. package/src/components/checkpoint.stories.tsx +7 -8
  32. package/src/components/code-block.stories.tsx +8 -9
  33. package/src/components/component-meta.json +3411 -0
  34. package/src/components/confirm-card.stories.tsx +74 -0
  35. package/src/components/confirm-card.tsx +299 -0
  36. package/src/components/context.stories.tsx +7 -8
  37. package/src/components/conversation-item.stories.tsx +7 -8
  38. package/src/components/conversation-item.tsx +2 -2
  39. package/src/components/conversation-list.stories.tsx +7 -8
  40. package/src/components/conversation-list.tsx +1 -1
  41. package/src/components/embed.tsx +196 -0
  42. package/src/components/empty.stories.tsx +8 -9
  43. package/src/components/feedback-bar.stories.tsx +7 -8
  44. package/src/components/file-tree.stories.tsx +73 -0
  45. package/src/components/file-tree.tsx +383 -0
  46. package/src/components/file-upload.stories.tsx +7 -8
  47. package/src/components/form-widgets.tsx +461 -0
  48. package/src/components/form.tsx +796 -0
  49. package/src/components/image.stories.tsx +7 -8
  50. package/src/components/link-card.tsx +194 -0
  51. package/src/components/loader.stories.tsx +7 -8
  52. package/src/components/markdown.stories.tsx +7 -8
  53. package/src/components/message-narrow.stories.tsx +12 -13
  54. package/src/components/message-skills.stories.tsx +16 -17
  55. package/src/components/message.stories.tsx +17 -18
  56. package/src/components/model-switcher.stories.tsx +7 -8
  57. package/src/components/prompt-input.stories.tsx +8 -9
  58. package/src/components/prompt-suggestion.stories.tsx +7 -8
  59. package/src/components/prompt-suggestion.tsx +3 -3
  60. package/src/components/reasoning.stories.tsx +7 -8
  61. package/src/components/scroll-button.stories.tsx +7 -8
  62. package/src/components/slash-command.stories.tsx +8 -9
  63. package/src/components/slash-command.tsx +2 -2
  64. package/src/components/source.stories.tsx +7 -8
  65. package/src/components/source.tsx +1 -1
  66. package/src/components/task-list-card.stories.tsx +78 -0
  67. package/src/components/task-list-card.tsx +388 -0
  68. package/src/components/text-shimmer.stories.tsx +7 -8
  69. package/src/components/thinking-bar.stories.tsx +7 -8
  70. package/src/components/tool.stories.tsx +7 -8
  71. package/src/components/tool.tsx +2 -2
  72. package/src/components/voice-input.stories.tsx +7 -8
  73. package/src/elements/artifact.stories.tsx +291 -0
  74. package/src/elements/artifact.tsx +72 -0
  75. package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -11
  76. package/src/elements/attachments.tsx +4 -4
  77. package/src/elements/card.stories.tsx +118 -0
  78. package/src/elements/card.tsx +40 -0
  79. package/src/elements/catalog.stories.tsx +491 -0
  80. package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -13
  81. package/src/elements/chain-of-thought.tsx +3 -3
  82. package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -10
  83. package/src/elements/chat-scope-picker.tsx +4 -4
  84. package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +71 -29
  85. package/src/elements/chat-workspace.tsx +29 -3
  86. package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +61 -16
  87. package/src/elements/chat.tsx +23 -2
  88. package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -11
  89. package/src/elements/checkpoint.tsx +4 -4
  90. package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -10
  91. package/src/elements/code-block.tsx +3 -3
  92. package/src/elements/compiled.css +1 -1
  93. package/src/elements/composed-shell.stories.tsx +316 -0
  94. package/src/elements/confirm-card.stories.tsx +186 -0
  95. package/src/elements/confirm-card.tsx +45 -0
  96. package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -10
  97. package/src/elements/context-meter.tsx +3 -3
  98. package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +35 -22
  99. package/src/elements/conversation-list.tsx +11 -2
  100. package/src/elements/css.ts +1 -1
  101. package/src/elements/define.tsx +10 -10
  102. package/src/elements/element-meta.json +2649 -0
  103. package/src/elements/element-types.d.ts +251 -125
  104. package/src/elements/embed.stories.tsx +197 -0
  105. package/src/elements/embed.tsx +35 -0
  106. package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -12
  107. package/src/elements/empty.tsx +3 -3
  108. package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -11
  109. package/src/elements/feedback-bar.tsx +4 -4
  110. package/src/elements/file-tree.stories.tsx +133 -0
  111. package/src/elements/file-tree.tsx +52 -0
  112. package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -12
  113. package/src/elements/file-upload.tsx +4 -4
  114. package/src/elements/form.stories.tsx +204 -0
  115. package/src/elements/form.tsx +37 -0
  116. package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -10
  117. package/src/elements/image.tsx +3 -3
  118. package/src/elements/link-card.stories.tsx +193 -0
  119. package/src/elements/link-card.tsx +34 -0
  120. package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -11
  121. package/src/elements/loader.tsx +3 -3
  122. package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -10
  123. package/src/elements/markdown.tsx +3 -3
  124. package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -10
  125. package/src/elements/message-skills.tsx +3 -3
  126. package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -12
  127. package/src/elements/message.tsx +5 -5
  128. package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -10
  129. package/src/elements/model-switcher.tsx +5 -5
  130. package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +41 -19
  131. package/src/elements/prompt-input.tsx +5 -5
  132. package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -13
  133. package/src/elements/prompt-suggestions.tsx +4 -4
  134. package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -10
  135. package/src/elements/reasoning.tsx +4 -4
  136. package/src/elements/register.ts +11 -1
  137. package/src/elements/resizable.stories.tsx +200 -0
  138. package/src/elements/resizable.tsx +264 -0
  139. package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -10
  140. package/src/elements/response-stream.tsx +4 -4
  141. package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -11
  142. package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -12
  143. package/src/elements/source.tsx +5 -5
  144. package/src/elements/styles.css +140 -1
  145. package/src/elements/task-list-card.stories.tsx +194 -0
  146. package/src/elements/task-list-card.tsx +40 -0
  147. package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -10
  148. package/src/elements/text-shimmer.tsx +3 -3
  149. package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -11
  150. package/src/elements/thinking-bar.tsx +5 -5
  151. package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -10
  152. package/src/elements/tool.tsx +3 -3
  153. package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -10
  154. package/src/elements/voice-input.tsx +4 -4
  155. package/src/index.ts +94 -2
  156. package/src/primitives/card-contract.ts +60 -0
  157. package/src/primitives/card-host.tsx +35 -0
  158. package/src/primitives/card-routing.ts +79 -0
  159. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  160. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  161. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  162. package/src/primitives/card-schemas/embed.schema.json +65 -0
  163. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  164. package/src/primitives/card-schemas/form.schema.json +33 -0
  165. package/src/primitives/card-schemas/link.schema.json +56 -0
  166. package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
  167. package/src/primitives/card-schemas/task-list.schema.json +78 -0
  168. package/src/primitives/card-validate.ts +95 -0
  169. package/src/primitives/embed-providers.ts +254 -0
  170. package/src/primitives/highlighter.ts +4 -0
  171. package/src/primitives/link-preview.ts +87 -0
  172. package/src/primitives/pdf-preview.ts +121 -0
  173. package/src/stories/chat-panel-layout.stories.tsx +2 -1
  174. package/src/stories/chat-scene.tsx +22 -21
  175. package/src/stories/checkpoint-restore.stories.tsx +10 -10
  176. package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
  177. package/src/stories/conversation-with-sources.stories.tsx +7 -7
  178. package/src/stories/docs/Accessibility.mdx +2 -2
  179. package/src/stories/docs/ForAIAgents.mdx +3 -3
  180. package/src/stories/docs/GettingStarted.mdx +2 -2
  181. package/src/stories/docs/Installation.mdx +2 -2
  182. package/src/stories/docs/Integrations.mdx +29 -29
  183. package/src/stories/docs/Introduction.mdx +3 -3
  184. package/src/stories/docs/Theming.mdx +2 -2
  185. package/src/stories/docs/element-controls.ts +60 -0
  186. package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
  187. package/src/stories/examples/ChoosingComponents.mdx +94 -0
  188. package/src/stories/examples/sample-data.ts +79 -0
  189. package/src/stories/message-actions.stories.tsx +13 -13
  190. package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
  191. package/src/stories/pattern-docked-widget.stories.tsx +1 -1
  192. package/src/stories/pattern-empty-state.stories.tsx +3 -3
  193. package/src/stories/prompt-input-variants.stories.tsx +13 -13
  194. package/src/stories/streaming-response.stories.tsx +3 -3
  195. package/src/stories/typography.stories.tsx +4 -4
  196. package/src/ui/avatar.stories.tsx +7 -8
  197. package/src/ui/badge.stories.tsx +7 -8
  198. package/src/ui/button.stories.tsx +8 -9
  199. package/src/ui/button.tsx +1 -0
  200. package/src/ui/collapsible.stories.tsx +6 -7
  201. package/src/ui/dropdown.stories.tsx +6 -7
  202. package/src/ui/hover-card.stories.tsx +6 -7
  203. package/src/ui/resizable.stories.tsx +74 -9
  204. package/src/ui/resizable.tsx +351 -71
  205. package/src/ui/scroll-area.stories.tsx +6 -7
  206. package/src/ui/scroll-area.tsx +3 -1
  207. package/src/ui/separator.stories.tsx +7 -8
  208. package/src/ui/skeleton.stories.tsx +7 -8
  209. package/src/ui/textarea.stories.tsx +6 -7
  210. package/src/ui/tooltip.stories.tsx +8 -9
  211. package/theme.css +65 -65
@@ -0,0 +1,94 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+
3
+ <Meta title="Examples/Choosing components" />
4
+
5
+ # Choosing components
6
+
7
+ `@kitnai/chat` ships ~32 `kc-*` web components across **three tiers**. This page is
8
+ the mental model for **which tier to reach for** — so you don't have to read all 32
9
+ per-element pages to know where to start.
10
+
11
+ > The per-element **When / How / Placement** notes live on each element's own story
12
+ > (under **Web Components / kc-\***). This page is the missing *overview* — the
13
+ > decision guide, not the per-element prose.
14
+
15
+ ## The three tiers
16
+
17
+ ### 1. Flagship / shells — _drop-in, the 90% path_
18
+
19
+ `<kc-chat>` · `<kc-workspace>`
20
+
21
+ A **whole surface in one tag**. Set `messages`, listen for `submit`, and you have a
22
+ working chat in minutes. `<kc-workspace>` adds the conversation sidebar + header
23
+ around it. These pre-wire the leaf components for you.
24
+
25
+ **Reach for these first.** Only step down a tier when the flagship doesn't fit.
26
+
27
+ ```html
28
+ <kc-chat id="chat" chat-title="Assistant" style="display:block;height:560px"></kc-chat>
29
+ <script type="module">
30
+ import '@kitnai/chat/elements';
31
+ const chat = document.getElementById('chat');
32
+ chat.messages = [{ id: '1', role: 'assistant', content: 'Hi!' }];
33
+ chat.addEventListener('submit', (e) => {/* call your model */});
34
+ </script>
35
+ ```
36
+
37
+ ### 2. Leaf features — _compose your own layout_
38
+
39
+ `<kc-conversations>` · `<kc-message>` · `<kc-tool>` · `<kc-reasoning>` ·
40
+ `<kc-chain-of-thought>` · `<kc-thinking-bar>` · `<kc-response-stream>` ·
41
+ `<kc-source>` / `<kc-sources>` · `<kc-skills>` · `<kc-prompt-input>` ·
42
+ `<kc-suggestions>` · `<kc-file-upload>` · `<kc-voice-input>` · `<kc-attachments>` ·
43
+ `<kc-model-switcher>` · `<kc-context>` · `<kc-scope-picker>` · `<kc-checkpoint>` ·
44
+ `<kc-feedback-bar>` · `<kc-artifact>` · `<kc-resizable>`
45
+
46
+ The **chat-specific building blocks**. Reach for these when the flagship layout
47
+ doesn't fit — you want a custom three-up shell, an inspector/canvas panel, a bespoke
48
+ header, or to render messages your own way. You own the data flow and the event
49
+ wiring; the leaves render.
50
+
51
+ `<kc-resizable>` is the **layout spine** for compose-your-own shells; `<kc-artifact>`
52
+ is the **preview/canvas** panel. See the worked example in
53
+ **Examples / Composed chat shell**.
54
+
55
+ ### 3. Primitives — _reusable anywhere_
56
+
57
+ `<kc-code-block>` · `<kc-markdown>` · `<kc-loader>` · `<kc-text-shimmer>` ·
58
+ `<kc-image>` · `<kc-empty>` · `<kc-file-tree>`
59
+
60
+ **Not chat-specific.** A code viewer, a markdown renderer, loaders, an image with a
61
+ skeleton, an empty-state, a file explorer — useful in any app. They happen to be
62
+ used inside the chat components, but ship public so you can reuse them standalone.
63
+
64
+ ## The decision test
65
+
66
+ > **"Would a non-chat app reuse this?"**
67
+ >
68
+ > - **Yes** → it's a **primitive** (tier 3). A docs site wants `<kc-markdown>`; a
69
+ > dashboard wants `<kc-loader>`; a file browser wants `<kc-file-tree>`.
70
+ > - **No, but it's a chat building block** → it's a **leaf feature** (tier 2).
71
+ > `<kc-message>`, `<kc-tool>`, `<kc-conversations>` only make sense in a chat.
72
+ > - **I just want a working chat** → use the **flagship** (tier 1). `<kc-chat>`.
73
+
74
+ ## How they fit together
75
+
76
+ ```
77
+ flagship <kc-chat> / <kc-workspace> ← pre-wires the leaves
78
+ ▲ composed from
79
+ leaf features <kc-conversations> <kc-message> <kc-prompt-input> <kc-artifact> …
80
+ ▲ composed from / laid out by
81
+ primitives + layout <kc-markdown> <kc-code-block> <kc-loader> … inside <kc-resizable>
82
+ ```
83
+
84
+ Start at the top. Drop a tier each time you need more control.
85
+
86
+ ## Where to go next
87
+
88
+ - **Examples / Catalog** — every component with sample data + copy-pasteable
89
+ markup. Answers _"what exists?"_.
90
+ - **Examples / Composed chat shell** — a real chat assembled from leaves inside
91
+ `<kc-resizable>`, shown next to the `<kc-chat>` drop-in so the contrast is
92
+ explicit. Answers _"how do I wire it myself, and when should I?"_.
93
+ - **Web Components / kc-\*** — the per-element pages with full API + the When /
94
+ How / Placement notes.
@@ -0,0 +1,79 @@
1
+ // Shared sample data for the Examples/* web-component stories. Mirrors the data
2
+ // in examples/composable/main.js so the Storybook catalog matches the external
3
+ // showcase. Plain data only — no DOM wiring here.
4
+
5
+ export const models = [
6
+ { id: 'opus', name: 'Claude Opus', provider: 'Anthropic' },
7
+ { id: 'sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
8
+ { id: 'haiku', name: 'Claude Haiku', provider: 'Anthropic' },
9
+ ];
10
+
11
+ export const context = {
12
+ usedTokens: 48200,
13
+ maxTokens: 200000,
14
+ inputTokens: 31000,
15
+ outputTokens: 17200,
16
+ estimatedCost: 0.42,
17
+ };
18
+
19
+ /** An inline SVG data-URL — handy for attachment thumbnails without a server. */
20
+ export function imgData(fill: string, glyph: string): string {
21
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><rect width="96" height="96" rx="12" fill="${fill}"/><text x="48" y="60" font-size="42" text-anchor="middle" fill="white">${glyph}</text></svg>`;
22
+ return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
23
+ }
24
+
25
+ export const attachments = [
26
+ { id: '1', type: 'file', filename: 'architecture.png', mediaType: 'image/png', url: imgData('#7c3aed', '◆') },
27
+ { id: '2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
28
+ { id: '3', type: 'source-document', title: 'kitn.dev/docs', filename: 'kitn.dev' },
29
+ ];
30
+
31
+ export const conversations = [
32
+ { id: 'c1', title: 'Web component architecture', scope: { type: 'document' }, messageCount: 12, lastMessageAt: '2026-06-11T10:00:00Z', updatedAt: '2026-06-11T10:00:00Z' },
33
+ { id: 'c2', title: 'Theming & tokens', scope: { type: 'document' }, messageCount: 5, lastMessageAt: '2026-06-10T09:00:00Z', updatedAt: '2026-06-10T09:00:00Z' },
34
+ { id: 'c3', title: 'Publishing pipeline', scope: { type: 'document' }, messageCount: 8, lastMessageAt: '2026-06-09T09:00:00Z', updatedAt: '2026-06-09T09:00:00Z' },
35
+ ];
36
+
37
+ export const slashCommands = [
38
+ { id: 'summarize', label: '/summarize', description: 'Summarize the conversation', category: 'Actions' },
39
+ { id: 'translate', label: '/translate', description: 'Translate the last message', category: 'Actions' },
40
+ { id: 'image', label: '/image', description: 'Generate an image', category: 'Tools' },
41
+ ];
42
+
43
+ export const sources = [
44
+ { href: 'https://kitn.dev', title: 'kitn — the kit', description: 'Composable SolidJS + web-component chat UI.', showFavicon: true },
45
+ { href: 'https://solidjs.com', title: 'SolidJS', description: 'A reactive UI library.', showFavicon: true },
46
+ ];
47
+
48
+ /** A rich assistant turn — reasoning + a tool call + an attachment + actions. */
49
+ export const assistantMessage = {
50
+ id: 'm-a',
51
+ role: 'assistant',
52
+ content: "Here's the plan, with a quick code sample:\n```js\nconst kit = useKitn();\n```",
53
+ reasoning: { text: 'The user wants X, so I should do Y then Z.', label: 'Reasoning' },
54
+ tools: [{ type: 'search', state: 'output-available', input: { query: 'kitn docs' }, output: { hits: 3 } }],
55
+ attachments: [attachments[0]],
56
+ actions: ['copy', 'like', 'dislike', 'regenerate'],
57
+ };
58
+
59
+ export const userMessage = { id: 'm-u', role: 'user', content: 'How do I compose these myself?' };
60
+
61
+ export const cotSteps = [
62
+ { label: 'Understand the request', content: 'The user wants a composable set.' },
63
+ { label: 'Design the API', content: 'Route 1: variant + flags + events.' },
64
+ { label: 'Build & verify' },
65
+ ];
66
+
67
+ /** A short thread for the composed-shell + drop-in stories. */
68
+ export const thread = [
69
+ { id: '1', role: 'user', content: 'Can you sketch a composable chat shell?' },
70
+ {
71
+ id: '2',
72
+ role: 'assistant',
73
+ content:
74
+ 'Sure. A shell is just a layout (`<kc-resizable>`) wrapping leaf components:\n\n```html\n<kc-conversations></kc-conversations>\n<kc-message></kc-message>\n<kc-prompt-input></kc-prompt-input>\n```\n\nYou own the data + events; the leaves render.',
75
+ reasoning: { text: 'Lay out list | chat | artifact, then wire submit + select.', label: 'Reasoning' },
76
+ tools: [{ type: 'plan_layout', state: 'output-available', input: { panels: 3 }, output: { ok: true } }],
77
+ actions: ['copy', 'like', 'dislike', 'regenerate'],
78
+ },
79
+ ];
@@ -46,10 +46,10 @@ async fn fetch_user(id: u64) -> Result<User, AppError> {
46
46
  Use \`anyhow::Result\` for applications and \`thiserror\` for libraries to define custom error types.`}
47
47
  </MessageContent>
48
48
  <MessageActions class="opacity-0 group-hover:opacity-100 transition-opacity">
49
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
50
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
51
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
52
- <Button variant="ghost" size="icon-sm"><RefreshCw class="size-3.5" /></Button>
49
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
50
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
51
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
52
+ <Button variant="ghost" size="icon-sm" aria-label="Regenerate response"><RefreshCw class="size-3.5" /></Button>
53
53
  </MessageActions>
54
54
  </div>
55
55
  </Message>
@@ -88,12 +88,12 @@ Import Tailwind in your main CSS file:
88
88
  \`\`\``}
89
89
  </MessageContent>
90
90
  <MessageActions>
91
- <Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
92
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
93
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
94
- <Button variant="ghost" size="icon-sm"><RefreshCw class="size-3.5" /></Button>
95
- <Button variant="ghost" size="icon-sm"><Share class="size-3.5" /></Button>
96
- <Button variant="ghost" size="icon-sm"><Bookmark class="size-3.5" /></Button>
91
+ <Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
92
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
93
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
94
+ <Button variant="ghost" size="icon-sm" aria-label="Regenerate response"><RefreshCw class="size-3.5" /></Button>
95
+ <Button variant="ghost" size="icon-sm" aria-label="Share"><Share class="size-3.5" /></Button>
96
+ <Button variant="ghost" size="icon-sm" aria-label="Bookmark"><Bookmark class="size-3.5" /></Button>
97
97
  </MessageActions>
98
98
  </div>
99
99
  </Message>
@@ -120,11 +120,11 @@ export const WithCopyConfirmation: Story = {
120
120
  The current LTS version of Node.js is 22.x, which includes built-in support for the fetch API, test runner, and watch mode.
121
121
  </MessageContent>
122
122
  <MessageActions>
123
- <Button variant="ghost" size="icon-sm" onClick={handleCopy}>
123
+ <Button variant="ghost" size="icon-sm" aria-label={copied() ? 'Copied' : 'Copy message'} onClick={handleCopy}>
124
124
  {copied() ? <Check class="size-3.5 text-green-500" /> : <Copy class="size-3.5" />}
125
125
  </Button>
126
- <Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
127
- <Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
126
+ <Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
127
+ <Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
128
128
  </MessageActions>
129
129
  </div>
130
130
  </Message>
@@ -64,8 +64,8 @@ export const Focused: Story = {
64
64
  {answer}
65
65
  </MessageContent>
66
66
  <MessageActions class="-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
67
- <Button variant="ghost" size="icon-sm" class="rounded-full"><Copy class="size-3.5" /></Button>
68
- <Button variant="ghost" size="icon-sm" class="rounded-full"><ThumbsUp class="size-3.5" /></Button>
67
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message"><Copy class="size-3.5" /></Button>
68
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
69
69
  </MessageActions>
70
70
  </div>
71
71
  </Message>
@@ -79,7 +79,7 @@ export const Focused: Story = {
79
79
  <PromptInput value={input()} onValueChange={setInput} onSubmit={() => setInput('')}>
80
80
  <PromptInputTextarea placeholder="Reply…" class="min-h-[44px] pt-3 pl-4" />
81
81
  <PromptInputActions class="mt-2 flex w-full items-center justify-end gap-2 px-3 pb-3">
82
- <Button size="icon-sm" class="rounded-full" disabled={!input().trim()}>
82
+ <Button size="icon-sm" class="rounded-full" disabled={!input().trim()} aria-label="Send message">
83
83
  <ArrowUp class="size-4" />
84
84
  </Button>
85
85
  </PromptInputActions>
@@ -79,7 +79,7 @@ export const SupportAssistant: Story = {
79
79
  <PromptInput value={input()} onValueChange={setInput} onSubmit={() => setInput('')}>
80
80
  <PromptInputTextarea placeholder="Message support…" class="min-h-[40px] pt-2.5 pl-3.5" />
81
81
  <PromptInputActions class="mt-1.5 flex w-full items-center justify-end gap-2 px-2.5 pb-2.5">
82
- <Button size="icon-sm" class="rounded-full" disabled={!input().trim()}>
82
+ <Button size="icon-sm" class="rounded-full" disabled={!input().trim()} aria-label="Send message">
83
83
  <ArrowUp class="size-4" />
84
84
  </Button>
85
85
  </PromptInputActions>
@@ -59,10 +59,10 @@ export const NewChat: Story = {
59
59
  <PromptInputTextarea placeholder="Ask anything…" class="min-h-[44px] pt-3 pl-4" />
60
60
  <PromptInputActions class="mt-2 flex w-full items-center justify-between gap-2 px-3 pb-3">
61
61
  <div class="flex items-center gap-2">
62
- <Button variant="outline" size="icon-sm" class="rounded-full"><Plus class="size-4" /></Button>
63
- <Button variant="outline" size="sm" class="rounded-full gap-1"><Globe class="size-4" />Search</Button>
62
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Add"><Plus class="size-4" /></Button>
63
+ <Button variant="outline" size="sm" class="rounded-full gap-1" aria-label="Search the web"><Globe class="size-4" />Search</Button>
64
64
  </div>
65
- <Button size="icon-sm" class="rounded-full" disabled={!input().trim()}>
65
+ <Button size="icon-sm" class="rounded-full" disabled={!input().trim()} aria-label="Send message">
66
66
  <ArrowUp class="size-4" />
67
67
  </Button>
68
68
  </PromptInputActions>
@@ -24,7 +24,7 @@ export const BasicInput: Story = {
24
24
  <PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
25
25
  <PromptInputTextarea placeholder="Ask anything..." />
26
26
  <PromptInputActions class="justify-end">
27
- <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
27
+ <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()} aria-label="Send message">
28
28
  <ArrowUp class="size-4" />
29
29
  </Button>
30
30
  </PromptInputActions>
@@ -72,7 +72,7 @@ export const WithSuggestions: Story = {
72
72
  <PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
73
73
  <PromptInputTextarea placeholder="Ask about this document..." />
74
74
  <PromptInputActions class="justify-end">
75
- <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
75
+ <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()} aria-label="Send message">
76
76
  <ArrowUp class="size-4" />
77
77
  </Button>
78
78
  </PromptInputActions>
@@ -93,12 +93,12 @@ export const WithActionButtons: Story = {
93
93
  <PromptInputTextarea placeholder="Message..." />
94
94
  <PromptInputActions class="justify-between">
95
95
  <div class="flex items-center gap-1">
96
- <Button variant="ghost" size="icon-sm"><Paperclip class="size-4 text-muted-foreground" /></Button>
97
- <Button variant="ghost" size="icon-sm"><Globe class="size-4 text-muted-foreground" /></Button>
98
- <Button variant="ghost" size="icon-sm"><Mic class="size-4 text-muted-foreground" /></Button>
99
- <Button variant="ghost" size="icon-sm"><Sparkles class="size-4 text-muted-foreground" /></Button>
96
+ <Button variant="ghost" size="icon-sm" aria-label="Attach file"><Paperclip class="size-4 text-muted-foreground" /></Button>
97
+ <Button variant="ghost" size="icon-sm" aria-label="Search the web"><Globe class="size-4 text-muted-foreground" /></Button>
98
+ <Button variant="ghost" size="icon-sm" aria-label="Voice input"><Mic class="size-4 text-muted-foreground" /></Button>
99
+ <Button variant="ghost" size="icon-sm" aria-label="AI suggestions"><Sparkles class="size-4 text-muted-foreground" /></Button>
100
100
  </div>
101
- <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
101
+ <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()} aria-label="Send message">
102
102
  <ArrowUp class="size-4" />
103
103
  </Button>
104
104
  </PromptInputActions>
@@ -119,9 +119,9 @@ export const StreamingState: Story = {
119
119
  <PromptInputActions class="justify-between">
120
120
  <div class="flex items-center gap-2">
121
121
  <Loader variant="typing" size="sm" />
122
- <span class="text-xs text-muted-foreground">Generating...</span>
122
+ <span class="text-xs text-foreground">Generating...</span>
123
123
  </div>
124
- <Button variant="outline" size="icon-sm" class="rounded-full">
124
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Stop">
125
125
  <Square class="size-3" />
126
126
  </Button>
127
127
  </PromptInputActions>
@@ -135,9 +135,9 @@ export const StreamingState: Story = {
135
135
  <PromptInputActions class="justify-between">
136
136
  <div class="flex items-center gap-2">
137
137
  <Loader variant="dots" size="sm" />
138
- <span class="text-xs text-muted-foreground">Thinking...</span>
138
+ <span class="text-xs text-foreground">Thinking...</span>
139
139
  </div>
140
- <Button variant="outline" size="icon-sm" class="rounded-full">
140
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Stop">
141
141
  <Square class="size-3" />
142
142
  </Button>
143
143
  </PromptInputActions>
@@ -166,8 +166,8 @@ export const WithModelSelector: Story = {
166
166
  <PromptInputActions class="justify-between">
167
167
  <ModelSwitcher models={models} currentModelId={modelId()} onModelChange={setModelId} />
168
168
  <div class="flex items-center gap-1">
169
- <Button variant="ghost" size="icon-sm"><Paperclip class="size-4 text-muted-foreground" /></Button>
170
- <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
169
+ <Button variant="ghost" size="icon-sm" aria-label="Attach file"><Paperclip class="size-4 text-muted-foreground" /></Button>
170
+ <Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()} aria-label="Send message">
171
171
  <ArrowUp class="size-4" />
172
172
  </Button>
173
173
  </div>
@@ -107,7 +107,7 @@ export const TypewriterStream: Story = {
107
107
  <PromptInput onSubmit={startStream}>
108
108
  <PromptInputTextarea placeholder="Click send to start streaming..." />
109
109
  <PromptInputActions class="justify-end">
110
- <Button variant="default" size="icon-sm" class="rounded-full" onClick={startStream}>
110
+ <Button variant="default" size="icon-sm" class="rounded-full" onClick={startStream} aria-label="Send message">
111
111
  <ArrowUp class="size-4" />
112
112
  </Button>
113
113
  </PromptInputActions>
@@ -167,8 +167,8 @@ export const WaitingForFirstToken: Story = {
167
167
  <PromptInput disabled isLoading>
168
168
  <PromptInputTextarea placeholder="Waiting..." />
169
169
  <PromptInputActions class="justify-between">
170
- <span class="text-xs text-muted-foreground">Waiting for response...</span>
171
- <Button variant="outline" size="icon-sm" class="rounded-full">
170
+ <span class="text-xs text-foreground">Waiting for response...</span>
171
+ <Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Stop">
172
172
  <Square class="size-3" />
173
173
  </Button>
174
174
  </PromptInputActions>
@@ -35,8 +35,8 @@ function TypographyScale() {
35
35
  <p class="text-muted-foreground mb-6 text-sm">
36
36
  Defined once in <code class="text-code-foreground">theme.css</code>. Each token generates a Tailwind utility
37
37
  (<code class="text-code-foreground">text-meta</code>, …). To restyle the kit's typography globally, override the
38
- namespaced <code class="text-code-foreground">--kitn-text-*</code> token on <code class="text-code-foreground">:root</code> —
39
- it pierces the Shadow&nbsp;DOM exactly like the <code class="text-code-foreground">--kitn-color-*</code> tokens. (The bare
38
+ namespaced <code class="text-code-foreground">--kc-text-*</code> token on <code class="text-code-foreground">:root</code> —
39
+ it pierces the Shadow&nbsp;DOM exactly like the <code class="text-code-foreground">--kc-color-*</code> tokens. (The bare
40
40
  <code class="text-code-foreground"> --text-*</code> names stay internal, so a host's own tokens can't collide.)
41
41
  </p>
42
42
 
@@ -60,8 +60,8 @@ function TypographyScale() {
60
60
 
61
61
  <h3 class="mt-8 mb-2 text-sm font-semibold">Override example</h3>
62
62
  <pre class="bg-muted text-foreground overflow-auto rounded-lg p-3 font-mono text-xs">{`:root {
63
- --kitn-text-body: 0.9375rem; /* bump the reading size to 15px */
64
- --kitn-text-meta: 0.8125rem; /* and the control size to 13px */
63
+ --kc-text-body: 0.9375rem; /* bump the reading size to 15px */
64
+ --kc-text-meta: 0.8125rem; /* and the control size to 13px */
65
65
  }`}</pre>
66
66
  <p class="text-muted-foreground mt-2 text-xs">
67
67
  Reading text in messages / input / markdown additionally scales with the
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Avatar } from './avatar';
3
+ import { componentDescription } from '../stories/docs/element-controls';
3
4
 
4
5
  const meta = {
5
6
  title: 'UI/Avatar',
@@ -9,14 +10,12 @@ const meta = {
9
10
  layout: 'padded',
10
11
  docs: {
11
12
  controls: { exclude: ['use:eventListener'] },
12
- description: {
13
- component: [
14
- 'A small, rounded **avatar** that renders an image when `src` is provided and falls back to short initials otherwise.',
15
- '**When to use:** to identify the author of a message, a conversation participant, or the current user in a header or list.',
16
- '**How to use:** always pass a `fallback` (1–2 initials) and a `size`. Provide `src`/`alt` when you have an image; if the image is missing or fails, the `fallback` text shows instead.',
17
- '**Placement:** message rows (next to assistant/user content), conversation list items, account menus, and headers.',
18
- ].join('\n\n'),
19
- },
13
+ description: componentDescription([
14
+ 'A small, rounded **avatar** that renders an image when `src` is provided and falls back to short initials otherwise.',
15
+ '**When to use:** to identify the author of a message, a conversation participant, or the current user in a header or list.',
16
+ '**How to use:** always pass a `fallback` (1–2 initials) and a `size`. Provide `src`/`alt` when you have an image; if the image is missing or fails, the `fallback` text shows instead.',
17
+ '**Placement:** message rows (next to assistant/user content), conversation list items, account menus, and headers.',
18
+ ]),
20
19
  },
21
20
  },
22
21
  argTypes: {
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Badge } from './badge';
3
+ import { componentDescription } from '../stories/docs/element-controls';
3
4
 
4
5
  const meta = {
5
6
  title: 'UI/Badge',
@@ -9,14 +10,12 @@ const meta = {
9
10
  layout: 'padded',
10
11
  docs: {
11
12
  controls: { exclude: ['use:eventListener'] },
12
- description: {
13
- component: [
14
- 'A compact pill **badge** for short status text, counts, or citation markers, rendered as an inline `<span>`.',
15
- '**When to use:** to annotate a count (unread, results), tag a status/label, or mark an inline source citation in assistant output.',
16
- '**How to use:** choose a `variant` (`default` label, `count` numeric pill, `citation` clickable source marker) and pass the text or number as children.',
17
- '**Placement:** inline with message text (citations), next to titles or list items (labels), and on toolbar/icon buttons (counts).',
18
- ].join('\n\n'),
19
- },
13
+ description: componentDescription([
14
+ 'A compact pill **badge** for short status text, counts, or citation markers, rendered as an inline `<span>`.',
15
+ '**When to use:** to annotate a count (unread, results), tag a status/label, or mark an inline source citation in assistant output.',
16
+ '**How to use:** choose a `variant` (`default` label, `count` numeric pill, `citation` clickable source marker) and pass the text or number as children.',
17
+ '**Placement:** inline with message text (citations), next to titles or list items (labels), and on toolbar/icon buttons (counts).',
18
+ ]),
20
19
  },
21
20
  },
22
21
  argTypes: {
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { fn } from 'storybook/test';
3
3
  import { Button } from './button';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  /**
6
7
  * Convention exemplar — every component story should follow this shape:
@@ -15,14 +16,12 @@ const meta = {
15
16
  parameters: {
16
17
  layout: 'padded',
17
18
  docs: {
18
- description: {
19
- component: [
20
- 'A clickable button with style **variants** and **sizes**, built on a native `<button>` (all standard button attributes pass through).',
21
- '**When to use:** any user-triggered action submitting input, toolbar/icon actions, confirming or dismissing. Use `default` for the primary action, `ghost`/`outline` for secondary or low-emphasis actions.',
22
- '**How to use:** set `variant` and `size`, pass label or an icon as children, and wire `onClick`. For icon-only buttons use `size="icon"` / `"icon-sm"` and include an `aria-label`.',
23
- '**Placement:** prompt action bars, message action rows, dialogs, toolbars, and empty-state CTAs.',
24
- ].join('\n\n'),
25
- },
19
+ description: componentDescription([
20
+ 'A clickable button with style **variants** and **sizes**, built on a native `<button>` (all standard button attributes pass through).',
21
+ '**When to use:** any user-triggered action submitting input, toolbar/icon actions, confirming or dismissing. Use `default` for the primary action, `ghost`/`outline` for secondary or low-emphasis actions.',
22
+ '**How to use:** set `variant` and `size`, pass label or an icon as children, and wire `onClick`. For icon-only buttons use `size="icon"` / `"icon-sm"` and include an `aria-label`.',
23
+ '**Placement:** prompt action bars, message action rows, dialogs, toolbars, and empty-state CTAs.',
24
+ ]),
26
25
  },
27
26
  },
28
27
  argTypes: {
@@ -73,7 +72,7 @@ type Story = StoryObj<typeof meta>;
73
72
  *
74
73
  * Note: `Button` is a SolidJS *component* (a scoped import), not a global custom
75
74
  * element, so the unprefixed name can't conflict with anything — alias on import
76
- * if a host already has a `Button`. Only the web components (`<kitn-chat>`, …)
75
+ * if a host already has a `Button`. Only the web components (`<kc-chat>`, …)
77
76
  * are prefixed, because those claim global custom-element tag names.
78
77
  */
79
78
  const IMPORT = `import { Button } from '@kitnai/chat';`;
package/src/ui/button.tsx CHANGED
@@ -10,6 +10,7 @@ const buttonVariants = cva(
10
10
  default: 'bg-primary text-primary-foreground hover:bg-primary/90',
11
11
  ghost: 'hover:bg-muted text-foreground',
12
12
  outline: 'bg-muted/50 text-foreground hover:bg-muted',
13
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
13
14
  },
14
15
  size: {
15
16
  sm: 'h-8 px-3 text-xs rounded-md',
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Collapsible, CollapsibleTrigger, CollapsibleContent } from './collapsible';
3
+ import { componentDescription } from '../stories/docs/element-controls';
3
4
 
4
5
  const meta = {
5
6
  title: 'UI/Collapsible',
@@ -8,13 +9,11 @@ const meta = {
8
9
  parameters: {
9
10
  layout: 'padded',
10
11
  docs: {
11
- description: {
12
- component: [
13
- 'A two-state disclosure that expands and collapses a region of content, animating height via a CSS `grid-template-rows` `0fr` → `1fr` transition (no JS measurement, no layout thrash). The trigger carries `aria-expanded`/`aria-controls` and the collapsed content is `inert`, so it is removed from tab order and the accessibility tree. Works controlled (`open` + `onOpenChange`) or uncontrolled (`defaultOpen`).',
14
- '**When to use:** to hide secondary detail behind a toggle a reasoning/"chain of thought" panel, a tool-call payload, an expandable conversation group, an FAQ row.',
15
- '**How to use:** wrap `CollapsibleTrigger` and `CollapsibleContent` in a `Collapsible`. The trigger renders a `<button>` by default; pass `as` to render a custom element.',
16
- ].join('\n\n'),
17
- },
12
+ description: componentDescription([
13
+ 'A two-state disclosure that expands and collapses a region of content, animating height via a CSS `grid-template-rows` `0fr` → `1fr` transition (no JS measurement, no layout thrash). The trigger carries `aria-expanded`/`aria-controls` and the collapsed content is `inert`, so it is removed from tab order and the accessibility tree. Works controlled (`open` + `onOpenChange`) or uncontrolled (`defaultOpen`).',
14
+ '**When to use:** to hide secondary detail behind a toggle a reasoning/"chain of thought" panel, a tool-call payload, an expandable conversation group, an FAQ row.',
15
+ '**How to use:** wrap `CollapsibleTrigger` and `CollapsibleContent` in a `Collapsible`. The trigger renders a `<button>` by default; pass `as` to render a custom element.',
16
+ ]),
18
17
  },
19
18
  },
20
19
  render: () => <CollapsibleDemo defaultOpen />,
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { createSignal } from 'solid-js';
3
3
  import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from './dropdown';
4
4
  import { buttonVariants } from './button';
5
+ import { componentDescription } from '../stories/docs/element-controls';
5
6
 
6
7
  const meta = {
7
8
  title: 'UI/Dropdown',
@@ -10,13 +11,11 @@ const meta = {
10
11
  parameters: {
11
12
  layout: 'padded',
12
13
  docs: {
13
- description: {
14
- component: [
15
- 'An accessible menu that opens from a trigger, built on the kit\'s DIY overlay core (no third-party dependency). Implements the WAI-ARIA menu-button pattern: `aria-haspopup`/`aria-expanded` wiring, roving focus with Arrow/Home/End, type-ahead, Escape/outside-click dismissal, and focus return to the trigger. Portals into the active shadow root and resolves focus through `getRootNode()` so roving focus works inside web components.',
16
- '**When to use:** for a list of *actions* or single-choice options triggered by a button overflow "⋯" menus, model pickers, scope selectors. For hover-only context use `HoverCard`; for a label use `Tooltip`.',
17
- '**How to use:** compose `Dropdown` › `DropdownTrigger` (the button) + `DropdownContent` › one `DropdownItem` per action. Give each item an `onSelect` handler — selecting also closes the menu.',
18
- ].join('\n\n'),
19
- },
14
+ description: componentDescription([
15
+ 'An accessible menu that opens from a trigger, built on the kit\'s DIY overlay core (no third-party dependency). Implements the WAI-ARIA menu-button pattern: `aria-haspopup`/`aria-expanded` wiring, roving focus with Arrow/Home/End, type-ahead, Escape/outside-click dismissal, and focus return to the trigger. Portals into the active shadow root and resolves focus through `getRootNode()` so roving focus works inside web components.',
16
+ '**When to use:** for a list of *actions* or single-choice options triggered by a button overflow "⋯" menus, model pickers, scope selectors. For hover-only context use `HoverCard`; for a label use `Tooltip`.',
17
+ '**How to use:** compose `Dropdown` `DropdownTrigger` (the button) + `DropdownContent` one `DropdownItem` per action. Give each item an `onSelect` handler selecting also closes the menu.',
18
+ ]),
20
19
  },
21
20
  },
22
21
  render: () => <DropdownDemo />,
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { HoverCard } from './hover-card';
3
3
  import { Button } from './button';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  const meta = {
6
7
  title: 'UI/HoverCard',
@@ -9,13 +10,11 @@ const meta = {
9
10
  parameters: {
10
11
  layout: 'padded',
11
12
  docs: {
12
- description: {
13
- component: [
14
- 'A floating card that opens when its trigger is hovered or focused, built on the kit\'s DIY overlay core (positioning + dismiss + presence no third-party dependency). Portals into the active shadow root so it never clips, and a transparent "safe bridge" keeps it open while the pointer travels from trigger to card.',
15
- '**When to use:** to reveal supplementary, non-essential context on hover a user/profile preview, a link or citation preview, an attachment summary. For an actionable menu use `Dropdown`; for a one-line label use `Tooltip`.',
16
- '**How to use:** pass the trigger element as `trigger` and the card body as `children`. Tune `openDelay` / `closeDelay` (ms) to taste.',
17
- ].join('\n\n'),
18
- },
13
+ description: componentDescription([
14
+ 'A floating card that opens when its trigger is hovered or focused, built on the kit\'s DIY overlay core (positioning + dismiss + presence — no third-party dependency). Portals into the active shadow root so it never clips, and a transparent "safe bridge" keeps it open while the pointer travels from trigger to card.',
15
+ '**When to use:** to reveal supplementary, non-essential context on hover a user/profile preview, a link or citation preview, an attachment summary. For an actionable menu use `Dropdown`; for a one-line label use `Tooltip`.',
16
+ '**How to use:** pass the trigger element as `trigger` and the card body as `children`. Tune `openDelay` / `closeDelay` (ms) to taste.',
17
+ ]),
19
18
  },
20
19
  },
21
20
  argTypes: {