@kitn.ai/ui 0.16.0 → 0.18.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.
- package/README.md +55 -17
- package/dist/components/artifact.d.ts +39 -1
- package/dist/components/attachment-types.d.ts +12 -0
- package/dist/components/attachments.d.ts +2 -10
- package/dist/components/card.d.ts +27 -0
- package/dist/components/chain-of-thought.d.ts +78 -2
- package/dist/components/chat-scope-picker.d.ts +7 -0
- package/dist/components/chat-thread.d.ts +39 -16
- package/dist/components/choice-card.d.ts +27 -0
- package/dist/components/coachmark.d.ts +49 -0
- package/dist/components/composer-dom.d.ts +35 -0
- package/dist/components/composer-highlight.d.ts +48 -0
- package/dist/components/composer-history.d.ts +34 -0
- package/dist/components/composer.d.ts +86 -0
- package/dist/components/confirm-card.d.ts +17 -0
- package/dist/components/conversation-item.d.ts +9 -0
- package/dist/components/conversation-list.d.ts +36 -1
- package/dist/components/dismissed-stub.d.ts +22 -0
- package/dist/components/file-tree.d.ts +15 -0
- package/dist/components/form.d.ts +34 -0
- package/dist/components/markdown.d.ts +3 -0
- package/dist/components/message.d.ts +71 -2
- package/dist/components/model-switcher.d.ts +7 -0
- package/dist/components/prompt-input.d.ts +13 -4
- package/dist/components/prompt-suggestion.d.ts +9 -1
- package/dist/components/reasoning.d.ts +14 -1
- package/dist/components/response-compare-types.d.ts +66 -0
- package/dist/components/response-compare.d.ts +65 -0
- package/dist/components/screen.d.ts +46 -0
- package/dist/components/tasks-card.d.ts +50 -1
- package/dist/components/toast.d.ts +58 -0
- package/dist/components/tool-types.d.ts +10 -0
- package/dist/components/tool.d.ts +8 -8
- package/dist/components/use-card-resolution.d.ts +10 -6
- package/dist/components/voice-input.d.ts +27 -0
- package/dist/components/voice-output.d.ts +33 -0
- package/dist/core-C8fzo39E.js +12 -0
- package/dist/custom-elements.json +8472 -1691
- package/dist/elements/agent-card.d.ts +1 -0
- package/dist/elements/agent-card.js +1 -0
- package/dist/elements/artifact.js +1 -0
- package/dist/elements/attachments.js +1 -0
- package/dist/elements/autoloader.d.ts +2 -0
- package/dist/elements/autoloader.js +1 -0
- package/dist/elements/avatar.d.ts +1 -0
- package/dist/elements/avatar.js +1 -0
- package/dist/elements/badge.d.ts +1 -0
- package/dist/elements/badge.js +1 -0
- package/dist/elements/button.d.ts +1 -0
- package/dist/elements/button.js +1 -0
- package/dist/elements/card.js +1 -0
- package/dist/elements/cards.d.ts +1 -0
- package/dist/elements/cards.js +1 -0
- package/dist/elements/chain-of-thought.d.ts +0 -12
- package/dist/elements/chain-of-thought.js +1 -0
- package/dist/elements/chat-scope-picker.js +1 -0
- package/dist/elements/chat-types.d.ts +8 -2
- package/dist/elements/chat-workspace.js +1 -0
- package/dist/elements/chat.js +1 -0
- package/dist/elements/checkpoint.js +1 -0
- package/dist/elements/choice.js +1 -0
- package/dist/elements/chunks/Icon-DLLBeLG5.js +1 -0
- package/dist/elements/chunks/action-icons-UB4V5FNq.js +1 -0
- package/dist/elements/chunks/arrow-left-C-gwlUmx.js +1 -0
- package/dist/elements/chunks/attachments-CgbErf1r.js +1 -0
- package/dist/elements/chunks/badge-DTQpgB6N.js +1 -0
- package/dist/elements/chunks/bash-InADTalH.js +1 -0
- package/dist/elements/chunks/button-XDUM6lXs.js +1 -0
- package/dist/elements/chunks/card-CBlFZ-7I.js +1 -0
- package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
- package/dist/elements/chunks/chat-thread-BIhmqwNL.js +1 -0
- package/dist/elements/chunks/check-hGUoVIKb.js +1 -0
- package/dist/elements/chunks/chevron-down-DDoaSUtO.js +1 -0
- package/dist/elements/chunks/chevron-right-CdGEUakB.js +1 -0
- package/dist/elements/chunks/circle-BKAZdmzb.js +1 -0
- package/dist/elements/chunks/circle-check-CmYG-y3I.js +1 -0
- package/dist/elements/chunks/circle-x-DewgAEym.js +1 -0
- package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
- package/dist/elements/chunks/code-block-DedwxwFI.js +1 -0
- package/dist/elements/chunks/collapsible-FfgmTHtM.js +1 -0
- package/dist/elements/chunks/composer-Cfbw5HKT.js +65 -0
- package/dist/elements/chunks/context-DwJmxlRE.js +1 -0
- package/dist/elements/chunks/conversation-list-DsCQMfPq.js +1 -0
- package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
- package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
- package/dist/elements/chunks/default-input-BuqsoENC.js +1 -0
- package/dist/elements/chunks/define-rGsaWsLL.js +1 -0
- package/dist/elements/chunks/disclosure-CwoAkpMH.js +1 -0
- package/dist/elements/chunks/download-DJ06G3Bz.js +1 -0
- package/dist/elements/chunks/dropdown-BFg8ri_R.js +1 -0
- package/dist/elements/chunks/ellipsis-dlN6YvGw.js +1 -0
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/elements/chunks/external-link-DBX2lc_R.js +1 -0
- package/dist/elements/chunks/file-text-HfwUx4F9.js +1 -0
- package/dist/elements/chunks/file-tree-C6Toj71u.js +1 -0
- package/dist/elements/chunks/folder-MYuqa9Xp.js +1 -0
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
- package/dist/elements/chunks/hover-card-gE7iyXdW.js +1 -0
- package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
- package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
- package/dist/elements/chunks/index-BCRhi84a.js +1 -0
- package/dist/elements/chunks/info-B_toFS0l.js +1 -0
- package/dist/elements/chunks/input-gzbHYZvQ.js +1 -0
- package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
- package/dist/elements/chunks/json-DxJze_jm.js +1 -0
- package/dist/elements/chunks/kbd-n49c4KZm.js +1 -0
- package/dist/elements/chunks/link-CKPs9yM0.js +1 -0
- package/dist/elements/chunks/loader-q6MamGus.js +1 -0
- package/dist/elements/chunks/markdown-DBrcqq4S.js +64 -0
- package/dist/elements/chunks/message-CTqaYI0s.js +1 -0
- package/dist/elements/chunks/message-feedback-Cvzm03w0.js +1 -0
- package/dist/elements/chunks/message-square-CUg9eRCi.js +1 -0
- package/dist/elements/chunks/minimize-2-l2klxP6K.js +1 -0
- package/dist/elements/chunks/model-switcher-CmyjiHO1.js +1 -0
- package/dist/elements/chunks/overlay-C8sOYqjT.js +1 -0
- package/dist/elements/chunks/paperclip-42Ia7xHD.js +1 -0
- package/dist/elements/chunks/progress-bar-tzKMWAwO.js +1 -0
- package/dist/elements/chunks/prompt-suggestion-D3F6vP_A.js +1 -0
- package/dist/elements/chunks/reasoning-BJNmmyov.js +1 -0
- package/dist/elements/chunks/resizable-BRsk19qI.js +1 -0
- package/dist/elements/chunks/rotate-cw-BgcggLHC.js +1 -0
- package/dist/elements/chunks/scroll-area-CgGbFFbT.js +1 -0
- package/dist/elements/chunks/separator-8hM4GdHE.js +1 -0
- package/dist/elements/chunks/settings-CaUIYOg-.js +1 -0
- package/dist/elements/chunks/settings-group-CYjOVIZQ.js +1 -0
- package/dist/elements/chunks/share-BTYM3Ddt.js +1 -0
- package/dist/elements/chunks/skeleton-DsTYrPUe.js +1 -0
- package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
- package/dist/elements/chunks/star-9R3LBQ9n.js +1 -0
- package/dist/elements/chunks/store-CYl5Y0_u.js +1 -0
- package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
- package/dist/elements/chunks/text-shimmer-CaYGv8Se.js +1 -0
- package/dist/elements/chunks/textarea-NEr2SLcD.js +1 -0
- package/dist/elements/chunks/thumbs-up-DZYZkFfc.js +1 -0
- package/dist/elements/chunks/toast-store-ClxVZRcc.js +1 -0
- package/dist/elements/chunks/tool-CHZOmoe9.js +1 -0
- package/dist/elements/chunks/tooltip-WpHElELC.js +1 -0
- package/dist/elements/chunks/triangle-alert-BlS80PUg.js +1 -0
- package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
- package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
- package/dist/elements/chunks/use-card-resolution-VSVoxDeJ.js +1 -0
- package/dist/elements/chunks/video-DxTPZqT8.js +1 -0
- package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
- package/dist/elements/chunks/x-C1qtndxn.js +1 -0
- package/dist/elements/coachmark.d.ts +1 -0
- package/dist/elements/coachmark.js +1 -0
- package/dist/elements/code-block.js +1 -0
- package/dist/elements/command.d.ts +1 -0
- package/dist/elements/command.js +1 -0
- package/dist/elements/compare.d.ts +1 -0
- package/dist/elements/compare.js +1 -0
- package/dist/elements/composer.d.ts +1 -0
- package/dist/elements/composer.js +1 -0
- package/dist/elements/confirm-card.js +1 -0
- package/dist/elements/context-meter.js +1 -0
- package/dist/elements/conversation-list.d.ts +1 -11
- package/dist/elements/conversation-list.js +1 -0
- package/dist/elements/default-input.d.ts +20 -9
- package/dist/elements/define.d.ts +11 -0
- package/dist/elements/dialog.d.ts +1 -0
- package/dist/elements/dialog.js +1 -0
- package/dist/elements/disclosure.d.ts +37 -0
- package/dist/elements/editable-label.d.ts +1 -0
- package/dist/elements/editable-label.js +1 -0
- package/dist/elements/embed.js +1 -0
- package/dist/elements/empty.js +1 -0
- package/dist/elements/feedback-bar.js +1 -0
- package/dist/elements/file-tree.js +1 -0
- package/dist/elements/file-upload.js +1 -0
- package/dist/elements/form.js +1 -0
- package/dist/elements/hover-card.d.ts +1 -0
- package/dist/elements/hover-card.js +1 -0
- package/dist/elements/icon.d.ts +1 -0
- package/dist/elements/icon.js +1 -0
- package/dist/elements/image.js +1 -0
- package/dist/elements/input.d.ts +1 -0
- package/dist/elements/input.js +1 -0
- package/dist/elements/kbd.d.ts +1 -0
- package/dist/elements/kbd.js +1 -0
- package/dist/elements/link-preview.js +1 -0
- package/dist/elements/loader.js +1 -0
- package/dist/elements/markdown.js +1 -0
- package/dist/elements/menu.d.ts +1 -0
- package/dist/elements/menu.js +1 -0
- package/dist/elements/message-skills.d.ts +0 -16
- package/dist/elements/message-skills.js +1 -0
- package/dist/elements/message.js +1 -0
- package/dist/elements/model-switcher.d.ts +1 -11
- package/dist/elements/model-switcher.js +1 -0
- package/dist/elements/nav.d.ts +1 -0
- package/dist/elements/nav.js +1 -0
- package/dist/elements/notice.d.ts +1 -0
- package/dist/elements/notice.js +1 -0
- package/dist/elements/pane-group.d.ts +1 -0
- package/dist/elements/pane-group.js +1 -0
- package/dist/elements/pane.d.ts +1 -0
- package/dist/elements/pane.js +1 -0
- package/dist/elements/popover.js +1 -0
- package/dist/elements/progress-bar.d.ts +1 -0
- package/dist/elements/progress-bar.js +1 -0
- package/dist/elements/prompt-dock.d.ts +1 -0
- package/dist/elements/prompt-dock.js +1 -0
- package/dist/elements/prompt-input.d.ts +1 -9
- package/dist/elements/prompt-input.js +1 -0
- package/dist/elements/prompt-suggestions.d.ts +0 -6
- package/dist/elements/prompt-suggestions.js +1 -0
- package/dist/elements/reasoning.js +1 -0
- package/dist/elements/register.d.ts +10 -0
- package/dist/elements/remote.js +1 -0
- package/dist/elements/resizable.d.ts +1 -17
- package/dist/elements/resizable.js +1 -0
- package/dist/elements/response-stream.js +14 -0
- package/dist/elements/screen.d.ts +1 -0
- package/dist/elements/screen.js +1 -0
- package/dist/elements/scroll-area.d.ts +1 -0
- package/dist/elements/scroll-area.js +1 -0
- package/dist/elements/scroll-button.js +1 -0
- package/dist/elements/search.d.ts +1 -0
- package/dist/elements/search.js +1 -0
- package/dist/elements/segmented.d.ts +1 -0
- package/dist/elements/segmented.js +1 -0
- package/dist/elements/separator.d.ts +1 -0
- package/dist/elements/separator.js +1 -0
- package/dist/elements/setting-item.d.ts +1 -0
- package/dist/elements/setting-item.js +1 -0
- package/dist/elements/settings-group.d.ts +1 -0
- package/dist/elements/settings-group.js +1 -0
- package/dist/elements/skeleton.d.ts +1 -0
- package/dist/elements/skeleton.js +1 -0
- package/dist/elements/slots.d.ts +176 -0
- package/dist/elements/source.d.ts +0 -17
- package/dist/elements/source.js +1 -0
- package/dist/elements/status.d.ts +1 -0
- package/dist/elements/status.js +1 -0
- package/dist/elements/switch.js +1 -0
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +1 -0
- package/dist/elements/tasks.js +1 -0
- package/dist/elements/text-shimmer.js +1 -0
- package/dist/elements/thinking-bar.js +1 -0
- package/dist/elements/toast.d.ts +1 -0
- package/dist/elements/toast.js +1 -0
- package/dist/elements/tool.js +1 -0
- package/dist/elements/tooltip.d.ts +1 -0
- package/dist/elements/tooltip.js +1 -0
- package/dist/elements/voice-input.js +1 -0
- package/dist/elements/voice-output.d.ts +1 -0
- package/dist/elements/voice-output.js +1 -0
- package/dist/elements.d.ts +772 -61
- package/dist/engine-javascript-C1x7zo1_.js +141 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +14405 -9810
- package/dist/kai.es.js +1 -0
- package/dist/llms/llms-full.txt +1234 -107
- package/dist/llms/llms.txt +4 -4
- package/dist/mcp.es.js +217 -6
- package/dist/primitives/card-contract.d.ts +20 -4
- package/dist/primitives/card-recovery.d.ts +55 -0
- package/dist/primitives/composer-model.d.ts +22 -0
- package/dist/primitives/composer-triggers.d.ts +19 -0
- package/dist/primitives/create-kai-chat.d.ts +38 -0
- package/dist/primitives/message-feedback.d.ts +35 -0
- package/dist/primitives/toast-store.d.ts +118 -0
- package/dist/primitives/use-speech-recognition.d.ts +26 -0
- package/dist/react/index.d.ts +1166 -98
- package/dist/react/runtime.d.ts +9 -2
- package/dist/react/use-kai-chat.d.ts +41 -0
- package/dist/react.js +575 -186
- package/dist/register-impl-CYWGkgbI.js +141 -0
- package/dist/schemas/choice.schema.json +5 -0
- package/dist/schemas/tasks.schema.json +7 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/messages.d.ts +11 -0
- package/dist/state/stream.d.ts +22 -0
- package/dist/state/suggestions.d.ts +4 -0
- package/dist/state.js +117 -0
- package/dist/theme.tokens.css +87 -0
- package/dist/types.d.ts +4 -0
- package/dist/ui/agent-card.d.ts +61 -0
- package/dist/ui/button.d.ts +5 -2
- package/dist/ui/card.d.ts +61 -0
- package/dist/ui/collapsible.d.ts +15 -1
- package/dist/ui/command.d.ts +33 -0
- package/dist/ui/dialog.d.ts +42 -0
- package/dist/ui/dropdown.d.ts +95 -1
- package/dist/ui/editable-label.d.ts +26 -0
- package/dist/ui/hover-card.d.ts +13 -1
- package/dist/ui/icon.d.ts +18 -0
- package/dist/ui/input.d.ts +33 -0
- package/dist/ui/kbd.d.ts +21 -0
- package/dist/ui/nav.d.ts +62 -0
- package/dist/ui/notice.d.ts +31 -0
- package/dist/ui/overlay.d.ts +12 -0
- package/dist/ui/pane-grid.d.ts +58 -0
- package/dist/ui/pane-group.d.ts +75 -0
- package/dist/ui/pane.d.ts +78 -0
- package/dist/ui/popover.d.ts +11 -1
- package/dist/ui/progress-bar.d.ts +23 -0
- package/dist/ui/prompt-dock.d.ts +96 -0
- package/dist/ui/resizable.d.ts +12 -0
- package/dist/ui/scroll-area.d.ts +4 -0
- package/dist/ui/segmented.d.ts +28 -0
- package/dist/ui/settings-group.d.ts +32 -0
- package/dist/ui/skeleton.d.ts +12 -0
- package/dist/ui/stat.d.ts +16 -0
- package/dist/ui/status.d.ts +13 -0
- package/dist/ui/switch.d.ts +8 -1
- package/dist/ui/tabs.d.ts +35 -0
- package/dist/ui/tooltip.d.ts +20 -1
- package/frameworks/react/index.tsx +1100 -163
- package/frameworks/react/runtime.tsx +30 -1
- package/frameworks/react/use-kai-chat.tsx +87 -0
- package/llms-full.txt +1234 -107
- package/llms.txt +4 -4
- package/package.json +36 -12
- package/src/agent-tooling/mcp/manifest.ts +14 -0
- package/src/agent-tooling/mcp/tools/debug.ts +194 -6
- package/src/agent-tooling/mcp/tools/reference.ts +32 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +162 -1
- package/src/components/artifact.tsx +98 -13
- package/src/components/attachment-types.ts +13 -0
- package/src/components/attachments.tsx +4 -14
- package/src/components/card.tsx +109 -15
- package/src/components/chain-of-thought.tsx +210 -1
- package/src/components/chat-scope-picker.tsx +13 -3
- package/src/components/chat-thread.tsx +238 -137
- package/src/components/choice-card.tsx +136 -15
- package/src/components/coachmark.tsx +245 -0
- package/src/components/component-meta.json +921 -108
- package/src/components/composer-dom.ts +159 -0
- package/src/components/composer-highlight.ts +242 -0
- package/src/components/composer-history.ts +82 -0
- package/src/components/composer.tsx +979 -0
- package/src/components/confirm-card.tsx +69 -2
- package/src/components/conversation-item.tsx +69 -7
- package/src/components/conversation-list.tsx +134 -37
- package/src/components/dismissed-stub.tsx +80 -0
- package/src/components/file-tree.tsx +197 -47
- package/src/components/form-widgets.tsx +9 -7
- package/src/components/form.tsx +161 -20
- package/src/components/image.tsx +3 -5
- package/src/components/link-preview.tsx +5 -4
- package/src/components/markdown.tsx +5 -2
- package/src/components/message.tsx +174 -14
- package/src/components/model-switcher.tsx +19 -4
- package/src/components/prompt-input.tsx +46 -75
- package/src/components/prompt-suggestion.tsx +48 -16
- package/src/components/reasoning.tsx +29 -6
- package/src/components/response-compare-types.ts +127 -0
- package/src/components/response-compare.tsx +460 -0
- package/src/components/screen.tsx +176 -0
- package/src/components/tasks-card.tsx +374 -23
- package/src/components/toast.tsx +486 -0
- package/src/components/tool-types.ts +10 -0
- package/src/components/tool.tsx +22 -16
- package/src/components/use-card-resolution.ts +24 -8
- package/src/components/voice-input.tsx +102 -19
- package/src/components/voice-output.tsx +202 -0
- package/src/elements/agent-card.tsx +67 -0
- package/src/elements/artifact.tsx +53 -4
- package/src/elements/attachments.tsx +1 -0
- package/src/elements/autoloader.ts +89 -0
- package/src/elements/avatar.tsx +33 -0
- package/src/elements/badge.tsx +32 -0
- package/src/elements/button.tsx +126 -0
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/card.tsx +124 -31
- package/src/elements/cards.tsx +81 -7
- package/src/elements/chain-of-thought.tsx +77 -32
- package/src/elements/chat-scope-picker.tsx +35 -8
- package/src/elements/chat-types.ts +9 -2
- package/src/elements/chat-workspace.tsx +157 -43
- package/src/elements/chat.tsx +49 -25
- package/src/elements/choice.tsx +71 -19
- package/src/elements/coachmark.tsx +90 -0
- package/src/elements/command.tsx +209 -0
- package/src/elements/compare.tsx +110 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +151 -0
- package/src/elements/confirm-card.tsx +36 -11
- package/src/elements/conversation-list.tsx +80 -11
- package/src/elements/default-input.tsx +75 -53
- package/src/elements/define.tsx +47 -6
- package/src/elements/dialog.tsx +98 -0
- package/src/elements/disclosure.ts +73 -0
- package/src/elements/editable-label.tsx +125 -0
- package/src/elements/element-manifest.json +310 -0
- package/src/elements/element-meta.json +5954 -1605
- package/src/elements/element-types.d.ts +771 -60
- package/src/elements/file-tree.tsx +12 -2
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +54 -11
- package/src/elements/hover-card.tsx +80 -0
- package/src/elements/icon-names.json +48 -0
- package/src/elements/icon.tsx +41 -0
- package/src/elements/input.tsx +203 -0
- package/src/elements/kbd.tsx +45 -0
- package/src/elements/menu.tsx +217 -0
- package/src/elements/message.tsx +84 -55
- package/src/elements/model-switcher.tsx +27 -1
- package/src/elements/nav.tsx +87 -0
- package/src/elements/notice.tsx +53 -0
- package/src/elements/pane-group.tsx +119 -0
- package/src/elements/pane.tsx +129 -0
- package/src/elements/popover.tsx +39 -17
- package/src/elements/progress-bar.tsx +39 -0
- package/src/elements/prompt-dock.tsx +81 -0
- package/src/elements/prompt-input.tsx +112 -63
- package/src/elements/prompt-suggestions.tsx +25 -9
- package/src/elements/reasoning.tsx +30 -9
- package/src/elements/register-impl.ts +41 -3
- package/src/elements/register.ts +20 -3
- package/src/elements/remote.tsx +4 -0
- package/src/elements/resizable.tsx +48 -11
- package/src/elements/screen.tsx +90 -0
- package/src/elements/scroll-area.tsx +33 -0
- package/src/elements/search.tsx +180 -0
- package/src/elements/segmented.tsx +135 -0
- package/src/elements/separator.tsx +36 -0
- package/src/elements/setting-item.tsx +50 -0
- package/src/elements/settings-group.tsx +37 -0
- package/src/elements/skeleton.tsx +45 -0
- package/src/elements/slots.ts +607 -0
- package/src/elements/source.tsx +13 -6
- package/src/elements/status.tsx +42 -0
- package/src/elements/styles.css +19 -0
- package/src/elements/switch.tsx +89 -14
- package/src/elements/tabs.tsx +95 -0
- package/src/elements/tasks.tsx +70 -14
- package/src/elements/toast.tsx +89 -0
- package/src/elements/tool.tsx +43 -9
- package/src/elements/tooltip.tsx +85 -0
- package/src/elements/video-poster.jpg +0 -0
- package/src/elements/voice-input.tsx +66 -16
- package/src/elements/voice-output.tsx +69 -0
- package/src/index.ts +35 -2
- package/src/primitives/card-contract.ts +14 -5
- package/src/primitives/card-recovery.ts +149 -0
- package/src/primitives/card-routing.ts +3 -0
- package/src/primitives/card-schemas/choice.schema.json +5 -0
- package/src/primitives/card-schemas/tasks.schema.json +7 -2
- package/src/primitives/composer-model.ts +49 -0
- package/src/primitives/composer-triggers.ts +35 -0
- package/src/primitives/create-kai-chat.ts +66 -0
- package/src/primitives/message-feedback.ts +123 -0
- package/src/primitives/toast-store.ts +276 -0
- package/src/primitives/use-speech-recognition.ts +135 -0
- package/src/state/index.ts +6 -0
- package/src/state/messages.ts +36 -0
- package/src/state/stream.ts +92 -0
- package/src/state/suggestions.ts +9 -0
- package/src/types.ts +4 -0
- package/src/ui/agent-card.tsx +212 -0
- package/src/ui/button.tsx +16 -5
- package/src/ui/card.tsx +245 -0
- package/src/ui/collapsible.tsx +30 -4
- package/src/ui/command.tsx +99 -0
- package/src/ui/dialog.tsx +217 -0
- package/src/ui/dropdown.tsx +375 -13
- package/src/ui/editable-label.tsx +112 -0
- package/src/ui/hover-card.tsx +28 -5
- package/src/ui/icon.tsx +110 -0
- package/src/ui/input.tsx +132 -0
- package/src/ui/kbd.tsx +118 -0
- package/src/ui/nav.tsx +282 -0
- package/src/ui/notice.tsx +84 -0
- package/src/ui/overlay.tsx +108 -2
- package/src/ui/pane-grid.tsx +116 -0
- package/src/ui/pane-group.tsx +280 -0
- package/src/ui/pane.tsx +193 -0
- package/src/ui/popover.tsx +16 -2
- package/src/ui/progress-bar.tsx +83 -0
- package/src/ui/prompt-dock.tsx +145 -0
- package/src/ui/resizable.tsx +69 -1
- package/src/ui/scroll-area.tsx +14 -3
- package/src/ui/segmented.tsx +109 -0
- package/src/ui/settings-group.tsx +67 -0
- package/src/ui/skeleton.tsx +62 -5
- package/src/ui/stat.tsx +41 -0
- package/src/ui/status.tsx +48 -0
- package/src/ui/switch.tsx +49 -23
- package/src/ui/tabs.tsx +168 -0
- package/src/ui/tooltip.tsx +57 -7
- package/theme.css +113 -0
- package/dist/components/slash-command.d.ts +0 -15
- package/dist/kitn-chat.es.js +0 -1
- package/dist/register-impl-wCsFPoh9.js +0 -77
- package/dist/register-ujhr1nBS.js +0 -1
- package/src/components/slash-command.tsx +0 -232
- package/src/elements/framework-usage.json +0 -563
- /package/dist/{kitn-chat-provider.d.ts → kai-provider.d.ts} +0 -0
- /package/dist/{kitn-chat-provider.es.js → kai-provider.es.js} +0 -0
package/src/ui/icon.tsx
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Component, JSX } from 'solid-js';
|
|
2
|
+
import {
|
|
3
|
+
Plus, Paperclip, Github, Globe, Sparkles, Settings,
|
|
4
|
+
FileText, Folder, Image, Monitor, MessageCircle, MessageSquare, Search,
|
|
5
|
+
Mic, AudioLines, X, ChevronDown, ChevronLeft,
|
|
6
|
+
Pencil, BookOpen, Code, Smile,
|
|
7
|
+
Share, ArrowLeft, MoreHorizontal,
|
|
8
|
+
House, Clock, Lock, Box, Briefcase, PanelLeft, Circle, SlidersHorizontal, Workflow, SquarePen,
|
|
9
|
+
Archive, ArrowUp, Bookmark, Copy, Ellipsis, ExternalLink, Flag,
|
|
10
|
+
GitBranch, GitPullRequest, ListFilter, RotateCw,
|
|
11
|
+
} from 'lucide-solid';
|
|
12
|
+
|
|
13
|
+
type IconComponent = Component<{ class?: string }>;
|
|
14
|
+
|
|
15
|
+
/** Curated name → lucide-solid component map for item icons in kai-menu /
|
|
16
|
+
* kai-command. Extend here when new named icons are needed. */
|
|
17
|
+
const NAMED_ICONS: Record<string, IconComponent> = {
|
|
18
|
+
plus: Plus,
|
|
19
|
+
paperclip: Paperclip,
|
|
20
|
+
github: Github,
|
|
21
|
+
globe: Globe,
|
|
22
|
+
sparkles: Sparkles,
|
|
23
|
+
settings: Settings,
|
|
24
|
+
'file-text': FileText,
|
|
25
|
+
folder: Folder,
|
|
26
|
+
image: Image,
|
|
27
|
+
monitor: Monitor,
|
|
28
|
+
'message-circle': MessageCircle,
|
|
29
|
+
'message-square': MessageSquare,
|
|
30
|
+
search: Search,
|
|
31
|
+
mic: Mic,
|
|
32
|
+
'audio-lines': AudioLines,
|
|
33
|
+
x: X,
|
|
34
|
+
'chevron-down': ChevronDown,
|
|
35
|
+
pencil: Pencil,
|
|
36
|
+
'book-open': BookOpen,
|
|
37
|
+
code: Code,
|
|
38
|
+
smile: Smile,
|
|
39
|
+
// Header / chrome glyphs.
|
|
40
|
+
share: Share,
|
|
41
|
+
'arrow-left': ArrowLeft,
|
|
42
|
+
'more-horizontal': MoreHorizontal,
|
|
43
|
+
'chevron-left': ChevronLeft,
|
|
44
|
+
// App-shell glyphs (sidebar nav, recents, filters).
|
|
45
|
+
home: House,
|
|
46
|
+
clock: Clock,
|
|
47
|
+
lock: Lock,
|
|
48
|
+
box: Box,
|
|
49
|
+
briefcase: Briefcase,
|
|
50
|
+
'panel-left': PanelLeft,
|
|
51
|
+
circle: Circle,
|
|
52
|
+
'sliders-horizontal': SlidersHorizontal,
|
|
53
|
+
workflow: Workflow,
|
|
54
|
+
'square-pen': SquarePen,
|
|
55
|
+
// Code-app / dev-tool glyphs (git, filters, list actions).
|
|
56
|
+
archive: Archive,
|
|
57
|
+
'arrow-up': ArrowUp,
|
|
58
|
+
bookmark: Bookmark,
|
|
59
|
+
copy: Copy,
|
|
60
|
+
ellipsis: Ellipsis,
|
|
61
|
+
'external-link': ExternalLink,
|
|
62
|
+
flag: Flag,
|
|
63
|
+
'git-branch': GitBranch,
|
|
64
|
+
'git-pull-request': GitPullRequest,
|
|
65
|
+
'list-filter': ListFilter,
|
|
66
|
+
'rotate-cw': RotateCw,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Render an item icon.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order:
|
|
72
|
+
* 1. Known icon name (e.g. `"paperclip"`) → lucide-solid component.
|
|
73
|
+
* 2. URL / absolute path / data-URI → `<img>`.
|
|
74
|
+
* 3. Anything else → `<span>` text fallback.
|
|
75
|
+
* Returns `null` when `icon` is undefined/empty.
|
|
76
|
+
*
|
|
77
|
+
* The img and span branches render different markup, so they accept different
|
|
78
|
+
* class options: `imgClass` for the `<img>`, `spanClass` for the `<span>`.
|
|
79
|
+
* `class` is a shared fallback. Pass `ariaHidden` to mark the span decorative. */
|
|
80
|
+
export function renderIcon(
|
|
81
|
+
icon: string | undefined,
|
|
82
|
+
opts?: { class?: string; imgClass?: string; spanClass?: string; ariaHidden?: boolean },
|
|
83
|
+
): JSX.Element {
|
|
84
|
+
if (!icon) return null;
|
|
85
|
+
const Named = NAMED_ICONS[icon];
|
|
86
|
+
if (Named) {
|
|
87
|
+
return <Named class={opts?.imgClass ?? opts?.class ?? 'mr-2 size-4 shrink-0'} />;
|
|
88
|
+
}
|
|
89
|
+
const isUrl = /^(https?:|\/|data:)/.test(icon);
|
|
90
|
+
// DEV footgun guard: a kebab/identifier-shaped string that isn't a URL and
|
|
91
|
+
// isn't a known name is almost certainly a typo'd/unregistered icon (e.g.
|
|
92
|
+
// `icon="share"` before it was added) — it would silently paint as literal
|
|
93
|
+
// text. Warn in dev only; emoji/arbitrary text passes through untouched.
|
|
94
|
+
if (import.meta.env.DEV && !isUrl && /^[a-z][a-z0-9-]*$/.test(icon)) {
|
|
95
|
+
console.warn(
|
|
96
|
+
`[kai-icon] unknown icon name "${icon}" — rendering as text. ` +
|
|
97
|
+
'Add it to NAMED_ICONS in src/ui/icon.tsx, or pass a URL / an inline SVG via slot="icon".',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return isUrl
|
|
101
|
+
? <img src={icon} alt="" class={opts?.imgClass ?? opts?.class ?? 'size-4 shrink-0'} />
|
|
102
|
+
: (
|
|
103
|
+
<span
|
|
104
|
+
class={opts?.spanClass ?? opts?.class ?? 'size-4 shrink-0'}
|
|
105
|
+
aria-hidden={opts?.ariaHidden ? 'true' : undefined}
|
|
106
|
+
>
|
|
107
|
+
{icon}
|
|
108
|
+
</span>
|
|
109
|
+
);
|
|
110
|
+
}
|
package/src/ui/input.tsx
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type JSX, Show, splitProps, createUniqueId } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface InputProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'onInput' | 'onChange' | 'size'> {
|
|
5
|
+
/** Field label rendered above the control and linked via `for`/`id`. */
|
|
6
|
+
label?: string;
|
|
7
|
+
/** Helper text rendered below the control. */
|
|
8
|
+
hint?: string;
|
|
9
|
+
/** Error text; rendered below the control and flips the field invalid. */
|
|
10
|
+
error?: string;
|
|
11
|
+
/** Control density. Defaults to `md`. */
|
|
12
|
+
size?: 'sm' | 'md';
|
|
13
|
+
/** Force the invalid (destructive-border) state without an `error` string. */
|
|
14
|
+
invalid?: boolean;
|
|
15
|
+
/** Leading affix (icon, unit). Rendered inside the field row, before the input. */
|
|
16
|
+
leading?: JSX.Element;
|
|
17
|
+
/** Trailing affix (icon, inline button). Rendered inside the field row, after the input. */
|
|
18
|
+
trailing?: JSX.Element;
|
|
19
|
+
/** Fires per keystroke with the current value. */
|
|
20
|
+
onValueInput?: (value: string) => void;
|
|
21
|
+
/** Fires on commit (blur) with the current value. */
|
|
22
|
+
onValueChange?: (value: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The single source of the field shell styling — lifted verbatim from the
|
|
26
|
+
// `inputBase` constant that used to live in `src/components/form-widgets.tsx`.
|
|
27
|
+
// `Input` now owns it; the form widgets render `Input` rather than re-pasting it.
|
|
28
|
+
export const FIELD_BASE =
|
|
29
|
+
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none';
|
|
30
|
+
|
|
31
|
+
// When a leading/trailing affix is present the border + padding wrap the whole
|
|
32
|
+
// row and the focus ring is driven off the row (`focus-within`), so the input
|
|
33
|
+
// itself sits borderless and transparent inside it.
|
|
34
|
+
const FIELD_ROW =
|
|
35
|
+
'flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-ring';
|
|
36
|
+
const ROW_INPUT =
|
|
37
|
+
'min-w-0 flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed';
|
|
38
|
+
|
|
39
|
+
const SIZE_SM = 'px-2.5 py-1';
|
|
40
|
+
const INVALID = 'border-destructive dark:border-red-400/70';
|
|
41
|
+
|
|
42
|
+
// Suppress the native search affordances Chrome/WebKit render for `type="search"`.
|
|
43
|
+
// Without this the browser's `::-webkit-search-cancel-button` (×) stacks on top of
|
|
44
|
+
// a custom clear control (e.g. kai-search's `part="clear"`) — a double ×. Applied
|
|
45
|
+
// to the inner `<input>` in BOTH layouts (the field can be `type="search"` either
|
|
46
|
+
// way; kai-search uses the affix layout for its leading icon).
|
|
47
|
+
const SEARCH_RESET =
|
|
48
|
+
'[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-cancel-button]:hidden';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `Input`: the token-themed single-line text field shell. A label/hint/error
|
|
52
|
+
* stack around a field row that holds an optional `leading` affix, the
|
|
53
|
+
* `<input>`, and an optional `trailing` affix. The shared border/background/ring
|
|
54
|
+
* styling lives here (the single field source the form widgets build on).
|
|
55
|
+
*
|
|
56
|
+
* Parts: `field` (the bordered control), `input`, `label`, `hint`.
|
|
57
|
+
* a11y: a generated id links `<label for>`; `invalid`/`error` set `aria-invalid`;
|
|
58
|
+
* the hint/error text is linked via `aria-describedby`.
|
|
59
|
+
*/
|
|
60
|
+
export function Input(props: InputProps): JSX.Element {
|
|
61
|
+
const [local, rest] = splitProps(props, [
|
|
62
|
+
'class', 'label', 'hint', 'error', 'size', 'invalid',
|
|
63
|
+
'leading', 'trailing', 'onValueInput', 'onValueChange', 'onBlur', 'id', 'disabled',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const id = local.id ?? createUniqueId();
|
|
67
|
+
const hintId = `${id}-hint`;
|
|
68
|
+
const isInvalid = () => !!local.invalid || !!local.error;
|
|
69
|
+
const hasHint = () => !!local.error || !!local.hint;
|
|
70
|
+
const hasAffix = () => local.leading != null || local.trailing != null;
|
|
71
|
+
|
|
72
|
+
const inputEl = (cls: string, part: string): JSX.Element => (
|
|
73
|
+
<input
|
|
74
|
+
{...rest}
|
|
75
|
+
id={id}
|
|
76
|
+
part={part}
|
|
77
|
+
disabled={local.disabled}
|
|
78
|
+
aria-invalid={isInvalid() ? 'true' : undefined}
|
|
79
|
+
aria-describedby={hasHint() ? hintId : undefined}
|
|
80
|
+
class={cn(SEARCH_RESET, cls)}
|
|
81
|
+
onInput={(e) => local.onValueInput?.(e.currentTarget.value)}
|
|
82
|
+
onBlur={(e) => {
|
|
83
|
+
const handler = local.onBlur;
|
|
84
|
+
if (typeof handler === 'function') handler(e);
|
|
85
|
+
local.onValueChange?.(e.currentTarget.value);
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div class="flex w-full flex-col gap-1.5">
|
|
92
|
+
<Show when={local.label}>
|
|
93
|
+
<label part="label" for={id} class="text-sm font-medium text-foreground">
|
|
94
|
+
{local.label}
|
|
95
|
+
</label>
|
|
96
|
+
</Show>
|
|
97
|
+
|
|
98
|
+
<Show
|
|
99
|
+
when={hasAffix()}
|
|
100
|
+
fallback={inputEl(
|
|
101
|
+
cn(FIELD_BASE, local.size === 'sm' && SIZE_SM, isInvalid() && INVALID, local.class),
|
|
102
|
+
'field input',
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
<div
|
|
106
|
+
part="field"
|
|
107
|
+
class={cn(
|
|
108
|
+
FIELD_ROW,
|
|
109
|
+
local.size === 'sm' && SIZE_SM,
|
|
110
|
+
isInvalid() && INVALID,
|
|
111
|
+
local.disabled && 'opacity-50 pointer-events-none',
|
|
112
|
+
local.class,
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<Show when={local.leading}>
|
|
116
|
+
<span class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
|
|
117
|
+
</Show>
|
|
118
|
+
{inputEl(ROW_INPUT, 'input')}
|
|
119
|
+
<Show when={local.trailing}>
|
|
120
|
+
<span class="flex shrink-0 items-center text-muted-foreground">{local.trailing}</span>
|
|
121
|
+
</Show>
|
|
122
|
+
</div>
|
|
123
|
+
</Show>
|
|
124
|
+
|
|
125
|
+
<Show when={hasHint()}>
|
|
126
|
+
<p part="hint" id={hintId} class={cn('text-xs', local.error ? 'text-destructive' : 'text-muted-foreground')}>
|
|
127
|
+
{local.error ?? local.hint}
|
|
128
|
+
</p>
|
|
129
|
+
</Show>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
package/src/ui/kbd.tsx
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type JSX, For, Show, splitProps } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export type KbdPlatform = 'auto' | 'mac' | 'other';
|
|
5
|
+
|
|
6
|
+
export interface KbdProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
7
|
+
/** Shortcut spec — tokens joined by `+` (e.g. `Mod+Shift+K`). When omitted,
|
|
8
|
+
* children render verbatim. Display only; `kai-kbd` does not bind keys. */
|
|
9
|
+
keys?: string;
|
|
10
|
+
/** Glyph platform. `mac` uses ⌘/⌥; `other` uses Ctrl. `auto` (default for the
|
|
11
|
+
* facade) sniffs the OS, SSR-safe (falls back to `other` on the server). */
|
|
12
|
+
platform?: KbdPlatform;
|
|
13
|
+
/** Cap size. Defaults to `md`. */
|
|
14
|
+
size?: 'sm' | 'md';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** SSR-safe mac check: prefers UA-CH `platform`, falls back to `navigator.platform`. */
|
|
18
|
+
function isMacOS(): boolean {
|
|
19
|
+
if (typeof navigator === 'undefined') return false;
|
|
20
|
+
const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
|
|
21
|
+
const platform = nav.userAgentData?.platform ?? nav.platform ?? '';
|
|
22
|
+
return /mac/i.test(platform);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve `auto` to a concrete platform; `mac`/`other` pass through. */
|
|
26
|
+
function resolvePlatform(platform: KbdPlatform): 'mac' | 'other' {
|
|
27
|
+
if (platform === 'mac') return 'mac';
|
|
28
|
+
if (platform === 'other') return 'other';
|
|
29
|
+
return isMacOS() ? 'mac' : 'other';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Map a single token to its display glyph. Unknown tokens uppercase (so `k` → `K`). */
|
|
33
|
+
function glyph(token: string, mac: boolean): string {
|
|
34
|
+
const t = token.trim();
|
|
35
|
+
switch (t.toLowerCase()) {
|
|
36
|
+
case 'mod':
|
|
37
|
+
return mac ? '⌘' : 'Ctrl';
|
|
38
|
+
case 'cmd':
|
|
39
|
+
case 'command':
|
|
40
|
+
case 'meta':
|
|
41
|
+
return '⌘';
|
|
42
|
+
case 'ctrl':
|
|
43
|
+
case 'control':
|
|
44
|
+
return 'Ctrl';
|
|
45
|
+
case 'shift':
|
|
46
|
+
return '⇧';
|
|
47
|
+
case 'alt':
|
|
48
|
+
case 'opt':
|
|
49
|
+
case 'option':
|
|
50
|
+
return '⌥';
|
|
51
|
+
case 'arrowup':
|
|
52
|
+
return '↑';
|
|
53
|
+
case 'arrowdown':
|
|
54
|
+
return '↓';
|
|
55
|
+
case 'arrowleft':
|
|
56
|
+
return '←';
|
|
57
|
+
case 'arrowright':
|
|
58
|
+
return '→';
|
|
59
|
+
case 'enter':
|
|
60
|
+
case 'return':
|
|
61
|
+
return '⏎';
|
|
62
|
+
case 'esc':
|
|
63
|
+
case 'escape':
|
|
64
|
+
return 'Esc';
|
|
65
|
+
case 'space':
|
|
66
|
+
return 'Space';
|
|
67
|
+
default:
|
|
68
|
+
return t.toUpperCase();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const SIZE: Record<'sm' | 'md', string> = {
|
|
73
|
+
sm: 'h-5 min-w-5 px-1 text-[11px]',
|
|
74
|
+
md: 'h-6 min-w-6 px-1.5 text-xs',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* `Kbd`: a keyboard-shortcut display. Splits `keys` on `+`, maps each token to a
|
|
79
|
+
* platform glyph (`Mod` → ⌘ on mac else Ctrl, `Shift` → ⇧, `ArrowUp` → ↑, …) and
|
|
80
|
+
* renders each as an inset `part="key"` cap, with an empty `part="separator"` hook
|
|
81
|
+
* between caps. Token-driven, light + dark. Display only — it does not bind keys.
|
|
82
|
+
*
|
|
83
|
+
* When `keys` is omitted, the children render verbatim (use any custom content).
|
|
84
|
+
*/
|
|
85
|
+
export function Kbd(props: KbdProps): JSX.Element {
|
|
86
|
+
const [local, rest] = splitProps(props, ['keys', 'platform', 'size', 'class', 'children']);
|
|
87
|
+
const mac = () => resolvePlatform(local.platform ?? 'other') === 'mac';
|
|
88
|
+
const tokens = () =>
|
|
89
|
+
(local.keys ?? '')
|
|
90
|
+
.split('+')
|
|
91
|
+
.map((t) => t.trim())
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<kbd {...rest} class={cn('inline-flex items-center gap-0.5 align-middle font-sans', local.class)}>
|
|
96
|
+
<Show when={local.keys != null} fallback={local.children}>
|
|
97
|
+
<For each={tokens()}>
|
|
98
|
+
{(token, i) => (
|
|
99
|
+
<>
|
|
100
|
+
<Show when={i() > 0}>
|
|
101
|
+
<span part="separator" aria-hidden="true" class="text-muted-foreground" />
|
|
102
|
+
</Show>
|
|
103
|
+
<span
|
|
104
|
+
part="key"
|
|
105
|
+
class={cn(
|
|
106
|
+
'inline-flex items-center justify-center rounded border border-border bg-muted font-medium leading-none tabular-nums text-muted-foreground',
|
|
107
|
+
SIZE[local.size ?? 'md'],
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{glyph(token, mac())}
|
|
111
|
+
</span>
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
</For>
|
|
115
|
+
</Show>
|
|
116
|
+
</kbd>
|
|
117
|
+
);
|
|
118
|
+
}
|
package/src/ui/nav.tsx
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { For, Show, type JSX, createSignal, splitProps } from 'solid-js';
|
|
2
|
+
import { ChevronRight } from 'lucide-solid';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
import { renderIcon } from './icon';
|
|
5
|
+
import { Badge } from './badge';
|
|
6
|
+
|
|
7
|
+
/** Status tone for a nav item's dot. `primary` is the theme accent; the rest use
|
|
8
|
+
* the kit's tool hues (the same colors as kai-status / kai-progress-bar). Generic
|
|
9
|
+
* enough for thread/onboarding states (success = "Completed", info = "Working")
|
|
10
|
+
* and task-run states (neutral = queued, info = running, success = done,
|
|
11
|
+
* error = failed). */
|
|
12
|
+
export type NavStatusTone = 'primary' | 'info' | 'success' | 'warning' | 'error' | 'neutral';
|
|
13
|
+
|
|
14
|
+
/** Dot hue per `tone`, mirroring the kai-status / kai-progress-bar token map. */
|
|
15
|
+
const STATUS_DOT_BG: Record<NavStatusTone, string> = {
|
|
16
|
+
primary: 'bg-primary',
|
|
17
|
+
info: 'bg-tool-blue',
|
|
18
|
+
success: 'bg-tool-green',
|
|
19
|
+
warning: 'bg-tool-amber',
|
|
20
|
+
error: 'bg-tool-red',
|
|
21
|
+
neutral: 'bg-muted-foreground',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** A small colored status indicator on a nav row. The `label` (when set) is folded
|
|
25
|
+
* into the row's accessible name; `pulse` adds an animated ping ring. */
|
|
26
|
+
export interface NavItemStatus {
|
|
27
|
+
tone: NavStatusTone;
|
|
28
|
+
/** Short status word ("Working", "Failed"). Shown as muted text and announced. */
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Animated ping ring (disabled under prefers-reduced-motion). */
|
|
31
|
+
pulse?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface KaiNavItem {
|
|
35
|
+
id: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Leading icon: a named icon, an image URL / data-URI, or plain text. */
|
|
38
|
+
icon?: string;
|
|
39
|
+
/** Trailing text pill (e.g. "Beta"). */
|
|
40
|
+
badge?: string;
|
|
41
|
+
/** Trailing icon (a named icon), e.g. an edit/compose affordance. */
|
|
42
|
+
trailing?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
/** Nested items. When present, the row becomes a collapsible group: a parent
|
|
45
|
+
* row with a disclosure chevron over an indented child list. Recurses for
|
|
46
|
+
* arbitrary depth (primarily 2 levels, e.g. project -> threads). */
|
|
47
|
+
children?: KaiNavItem[];
|
|
48
|
+
/** A small colored status dot (+ optional label) on the row. */
|
|
49
|
+
status?: NavItemStatus;
|
|
50
|
+
/** Right-aligned muted trailing text (e.g. a relative time, "24d ago").
|
|
51
|
+
* Distinct from `trailing` (a hover icon). */
|
|
52
|
+
meta?: string;
|
|
53
|
+
/** An interactive trailing action button (`icon` is a named icon; `label` is
|
|
54
|
+
* its accessible name). Activating it fires `onItemAction` and does NOT select
|
|
55
|
+
* the row. Distinct from `trailing` (a decorative hover icon, not a button). */
|
|
56
|
+
action?: { icon: string; label: string };
|
|
57
|
+
/** Render an interactive trailing close (×) button. Activating it fires
|
|
58
|
+
* `onItemClose` and does NOT select the row. */
|
|
59
|
+
closable?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface NavProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
63
|
+
items?: KaiNavItem[];
|
|
64
|
+
/** Active item id (drives aria-current + the selected look). */
|
|
65
|
+
value?: string;
|
|
66
|
+
onItemSelect?: (id: string) => void;
|
|
67
|
+
/** A row's trailing `action` button was activated (not a select). */
|
|
68
|
+
onItemAction?: (id: string, action?: { icon: string; label: string }) => void;
|
|
69
|
+
/** A `closable` row's trailing close button was activated (not a select). */
|
|
70
|
+
onItemClose?: (id: string) => void;
|
|
71
|
+
/** Ids of group items collapsed on first render. Groups default to expanded. */
|
|
72
|
+
defaultCollapsed?: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function Nav(props: NavProps) {
|
|
76
|
+
const [local, rest] = splitProps(props, [
|
|
77
|
+
'items', 'value', 'onItemSelect', 'onItemAction', 'onItemClose', 'defaultCollapsed', 'class',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// Groups default to expanded; `defaultCollapsed` seeds the closed set. Using a
|
|
81
|
+
// Set keyed by item id keeps expand state independent of the items reference,
|
|
82
|
+
// so late-arriving items (the element sets `items` post-construction) are open.
|
|
83
|
+
const [collapsed, setCollapsed] = createSignal<Set<string>>(
|
|
84
|
+
new Set(local.defaultCollapsed ?? []),
|
|
85
|
+
{ equals: false },
|
|
86
|
+
);
|
|
87
|
+
const isExpanded = (id: string) => !collapsed().has(id);
|
|
88
|
+
const toggle = (id: string) =>
|
|
89
|
+
setCollapsed((prev) => {
|
|
90
|
+
const next = new Set(prev);
|
|
91
|
+
if (next.has(id)) next.delete(id);
|
|
92
|
+
else next.add(id);
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<nav {...rest} class={cn('flex flex-col gap-0.5', local.class)}>
|
|
98
|
+
<NavRows
|
|
99
|
+
items={local.items ?? []}
|
|
100
|
+
depth={0}
|
|
101
|
+
value={local.value}
|
|
102
|
+
onItemSelect={local.onItemSelect}
|
|
103
|
+
onItemAction={local.onItemAction}
|
|
104
|
+
onItemClose={local.onItemClose}
|
|
105
|
+
isExpanded={isExpanded}
|
|
106
|
+
toggle={toggle}
|
|
107
|
+
/>
|
|
108
|
+
</nav>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface NavRowsProps {
|
|
113
|
+
items: KaiNavItem[];
|
|
114
|
+
depth: number;
|
|
115
|
+
value?: string;
|
|
116
|
+
onItemSelect?: (id: string) => void;
|
|
117
|
+
onItemAction?: (id: string, action?: { icon: string; label: string }) => void;
|
|
118
|
+
onItemClose?: (id: string) => void;
|
|
119
|
+
isExpanded: (id: string) => boolean;
|
|
120
|
+
toggle: (id: string) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Render one nesting level. Recurses through `children`. */
|
|
124
|
+
function NavRows(props: NavRowsProps) {
|
|
125
|
+
// When a level has any groups, every row reserves a chevron-width disclosure
|
|
126
|
+
// column (chevron for groups, a spacer for leaves) so icons/labels line up.
|
|
127
|
+
const hasGroups = () => props.items.some((i) => (i.children?.length ?? 0) > 0);
|
|
128
|
+
return (
|
|
129
|
+
<For each={props.items}>
|
|
130
|
+
{(item) => {
|
|
131
|
+
const isGroup = () => (item.children?.length ?? 0) > 0;
|
|
132
|
+
const expanded = () => props.isExpanded(item.id);
|
|
133
|
+
const active = () => props.value === item.id;
|
|
134
|
+
const tone = () => item.status?.tone ?? 'neutral';
|
|
135
|
+
// Fold the status word + meta into the accessible name so AT gets them
|
|
136
|
+
// (the dot/label/meta nodes are aria-hidden to avoid double-announcing).
|
|
137
|
+
const ariaLabel = () => {
|
|
138
|
+
if (!item.status?.label && !item.meta) return undefined;
|
|
139
|
+
return [item.label, item.status?.label, item.meta].filter(Boolean).join(', ');
|
|
140
|
+
};
|
|
141
|
+
const onClick = () => {
|
|
142
|
+
if (item.disabled) return;
|
|
143
|
+
if (isGroup()) props.toggle(item.id);
|
|
144
|
+
else props.onItemSelect?.(item.id);
|
|
145
|
+
};
|
|
146
|
+
// Native buttons handle Enter/Space; add ArrowRight/Left for tree parity.
|
|
147
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
148
|
+
if (!isGroup() || item.disabled) return;
|
|
149
|
+
if (e.key === 'ArrowRight' && !expanded()) { e.preventDefault(); props.toggle(item.id); }
|
|
150
|
+
else if (e.key === 'ArrowLeft' && expanded()) { e.preventDefault(); props.toggle(item.id); }
|
|
151
|
+
};
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
{/* The row is a non-interactive wrapper: the selectable item button
|
|
155
|
+
and the trailing action/close button are SIBLINGS, never nested
|
|
156
|
+
(a button inside a button is invalid HTML — browsers hoist it out
|
|
157
|
+
— and trips axe's nested-interactive). Each control owns its own
|
|
158
|
+
click, so a trailing-button click never reaches the item button's
|
|
159
|
+
select handler; no event-delegation discrimination is needed. */}
|
|
160
|
+
<div
|
|
161
|
+
class={cn(
|
|
162
|
+
'group relative flex items-center rounded-md transition-colors',
|
|
163
|
+
'hover:bg-accent',
|
|
164
|
+
active() && 'bg-accent',
|
|
165
|
+
item.disabled && 'pointer-events-none opacity-50',
|
|
166
|
+
)}
|
|
167
|
+
>
|
|
168
|
+
<button
|
|
169
|
+
part="item"
|
|
170
|
+
type="button"
|
|
171
|
+
disabled={item.disabled}
|
|
172
|
+
aria-current={active() ? 'page' : undefined}
|
|
173
|
+
aria-expanded={isGroup() ? expanded() : undefined}
|
|
174
|
+
aria-label={ariaLabel()}
|
|
175
|
+
onClick={onClick}
|
|
176
|
+
onKeyDown={onKeyDown}
|
|
177
|
+
style={props.depth > 0 ? { 'padding-left': `${10 + props.depth * 16}px` } : undefined}
|
|
178
|
+
class={cn(
|
|
179
|
+
'flex min-w-0 flex-1 items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors',
|
|
180
|
+
'text-muted-foreground group-hover:text-foreground',
|
|
181
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
182
|
+
'disabled:pointer-events-none',
|
|
183
|
+
active() && 'font-medium text-foreground',
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
<Show
|
|
187
|
+
when={isGroup()}
|
|
188
|
+
fallback={hasGroups() ? <span class="size-4 shrink-0" aria-hidden="true" /> : null}
|
|
189
|
+
>
|
|
190
|
+
<span
|
|
191
|
+
part="chevron"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
class={cn('flex shrink-0 items-center opacity-60 transition-transform', expanded() && 'rotate-90')}
|
|
194
|
+
>
|
|
195
|
+
<ChevronRight size={16} />
|
|
196
|
+
</span>
|
|
197
|
+
</Show>
|
|
198
|
+
<Show when={item.icon}>{renderIcon(item.icon, { class: 'size-4 shrink-0' })}</Show>
|
|
199
|
+
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
200
|
+
<Show when={item.status}>
|
|
201
|
+
<span part="status" class="flex shrink-0 items-center gap-1.5" aria-hidden="true">
|
|
202
|
+
<span class="relative inline-flex">
|
|
203
|
+
<Show when={item.status!.pulse}>
|
|
204
|
+
<span
|
|
205
|
+
class={cn(
|
|
206
|
+
'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden',
|
|
207
|
+
STATUS_DOT_BG[tone()],
|
|
208
|
+
)}
|
|
209
|
+
/>
|
|
210
|
+
</Show>
|
|
211
|
+
<span class={cn('relative inline-block size-2 rounded-full', STATUS_DOT_BG[tone()])} />
|
|
212
|
+
</span>
|
|
213
|
+
<Show when={item.status!.label}>
|
|
214
|
+
<span class="text-xs text-muted-foreground">{item.status!.label}</span>
|
|
215
|
+
</Show>
|
|
216
|
+
</span>
|
|
217
|
+
</Show>
|
|
218
|
+
<Show when={item.meta}>
|
|
219
|
+
<span part="meta" class="shrink-0 text-xs tabular-nums text-muted-foreground" aria-hidden="true">
|
|
220
|
+
{item.meta}
|
|
221
|
+
</span>
|
|
222
|
+
</Show>
|
|
223
|
+
<Show when={item.badge}>
|
|
224
|
+
<Badge variant="default" class="px-1.5 py-0 text-[0.625rem] font-medium uppercase tracking-wide">{item.badge}</Badge>
|
|
225
|
+
</Show>
|
|
226
|
+
<Show when={item.trailing}>{renderIcon(item.trailing, { class: 'size-4 shrink-0 opacity-0 transition-opacity group-hover:opacity-60' })}</Show>
|
|
227
|
+
</button>
|
|
228
|
+
<Show when={item.action}>
|
|
229
|
+
<button
|
|
230
|
+
part="item-action"
|
|
231
|
+
data-nav-action="action"
|
|
232
|
+
type="button"
|
|
233
|
+
aria-label={item.action!.label}
|
|
234
|
+
onClick={() => props.onItemAction?.(item.id, item.action)}
|
|
235
|
+
class={cn(
|
|
236
|
+
'mr-1 flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground',
|
|
237
|
+
'opacity-0 transition-opacity hover:bg-background/70 hover:text-foreground',
|
|
238
|
+
'focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
239
|
+
'group-hover:opacity-100',
|
|
240
|
+
)}
|
|
241
|
+
>
|
|
242
|
+
{renderIcon(item.action!.icon, { class: 'size-4' })}
|
|
243
|
+
</button>
|
|
244
|
+
</Show>
|
|
245
|
+
<Show when={item.closable}>
|
|
246
|
+
<button
|
|
247
|
+
part="item-action"
|
|
248
|
+
data-nav-action="close"
|
|
249
|
+
type="button"
|
|
250
|
+
aria-label={item.label ? `Remove ${item.label}` : 'Remove'}
|
|
251
|
+
onClick={() => props.onItemClose?.(item.id)}
|
|
252
|
+
class={cn(
|
|
253
|
+
'mr-1 flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground',
|
|
254
|
+
'opacity-0 transition-opacity hover:bg-background/70 hover:text-foreground',
|
|
255
|
+
'focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
256
|
+
'group-hover:opacity-100',
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
{renderIcon('x', { class: 'size-4' })}
|
|
260
|
+
</button>
|
|
261
|
+
</Show>
|
|
262
|
+
</div>
|
|
263
|
+
<Show when={isGroup() && expanded()}>
|
|
264
|
+
<div part="group" role="group">
|
|
265
|
+
<NavRows
|
|
266
|
+
items={item.children!}
|
|
267
|
+
depth={props.depth + 1}
|
|
268
|
+
value={props.value}
|
|
269
|
+
onItemSelect={props.onItemSelect}
|
|
270
|
+
onItemAction={props.onItemAction}
|
|
271
|
+
onItemClose={props.onItemClose}
|
|
272
|
+
isExpanded={props.isExpanded}
|
|
273
|
+
toggle={props.toggle}
|
|
274
|
+
/>
|
|
275
|
+
</div>
|
|
276
|
+
</Show>
|
|
277
|
+
</>
|
|
278
|
+
);
|
|
279
|
+
}}
|
|
280
|
+
</For>
|
|
281
|
+
);
|
|
282
|
+
}
|