@kitn.ai/ui 0.17.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 +28 -5
- 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 +37 -12
- package/dist/components/choice-card.d.ts +26 -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/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 +12 -0
- 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.d.ts +14 -0
- package/dist/components/screen.d.ts +46 -0
- package/dist/components/tasks-card.d.ts +49 -1
- package/dist/components/toast.d.ts +12 -2
- package/dist/components/tool-types.d.ts +10 -0
- package/dist/components/tool.d.ts +8 -8
- 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 +8392 -1860
- package/dist/elements/agent-card.d.ts +1 -0
- package/dist/elements/agent-card.js +1 -0
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/autoloader.js +1 -1
- 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 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +47 -0
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- 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/button-XDUM6lXs.js +1 -0
- package/dist/elements/chunks/card-CBlFZ-7I.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/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/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/hover-card-gE7iyXdW.js +1 -0
- package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
- package/dist/elements/chunks/{index-CTBEJeo6.js → index-BCRhi84a.js} +1 -1
- package/dist/elements/chunks/info-B_toFS0l.js +1 -0
- package/dist/elements/chunks/input-gzbHYZvQ.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-CMTGx9r8.js → markdown-DBrcqq4S.js} +9 -9
- 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/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/use-card-resolution-VSVoxDeJ.js +1 -0
- package/dist/elements/chunks/video-DxTPZqT8.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 -1
- package/dist/elements/command.d.ts +1 -0
- package/dist/elements/command.js +1 -0
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.d.ts +1 -0
- package/dist/elements/composer.js +1 -0
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/css.d.ts +1 -0
- package/dist/elements/default-input.d.ts +51 -0
- package/dist/elements/define.d.ts +53 -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 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- 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 -1
- 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 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.d.ts +1 -0
- package/dist/elements/menu.js +1 -0
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- 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 -1
- 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.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register-impl.d.ts +0 -0
- package/dist/elements/register.d.ts +13 -0
- package/dist/elements/remote.d.ts +1 -0
- package/dist/elements/remote.js +1 -0
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- 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 -1
- 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.js +1 -1
- package/dist/elements/status.d.ts +1 -0
- package/dist/elements/status.js +1 -0
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +1 -0
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.d.ts +1 -0
- package/dist/elements/tooltip.js +1 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.d.ts +1 -0
- package/dist/elements/voice-output.js +1 -0
- package/dist/elements.d.ts +733 -58
- package/dist/engine-javascript-C1x7zo1_.js +141 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8256 -5703
- package/dist/kai.es.js +1 -0
- package/dist/llms/llms-full.txt +1146 -64
- package/dist/llms/llms.txt +4 -4
- package/dist/mcp.es.js +62 -7
- 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/toast-store.d.ts +32 -2
- package/dist/primitives/use-speech-recognition.d.ts +26 -0
- package/dist/react/index.d.ts +1085 -97
- package/dist/react/runtime.d.ts +9 -2
- package/dist/react/use-kai-chat.d.ts +41 -0
- package/dist/react.js +572 -193
- package/dist/register-impl-CYWGkgbI.js +141 -0
- package/dist/schemas/tasks.schema.json +2 -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 +19 -1
- package/frameworks/react/index.tsx +1042 -157
- package/frameworks/react/runtime.tsx +30 -1
- package/frameworks/react/use-kai-chat.tsx +87 -0
- package/llms-full.txt +1146 -64
- package/llms.txt +4 -4
- package/package.json +23 -8
- package/src/agent-tooling/mcp/manifest.ts +14 -0
- package/src/agent-tooling/mcp/tools/debug.ts +10 -6
- package/src/agent-tooling/mcp/tools/reference.ts +32 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +30 -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 +225 -116
- package/src/components/choice-card.tsx +103 -11
- package/src/components/coachmark.tsx +245 -0
- package/src/components/component-meta.json +691 -102
- 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 +54 -1
- package/src/components/conversation-item.tsx +69 -7
- package/src/components/conversation-list.tsx +134 -37
- package/src/components/file-tree.tsx +197 -47
- package/src/components/form-widgets.tsx +9 -7
- package/src/components/form.tsx +140 -17
- 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 +23 -4
- 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.tsx +31 -2
- package/src/components/screen.tsx +176 -0
- package/src/components/tasks-card.tsx +342 -21
- package/src/components/toast.tsx +119 -31
- package/src/components/tool-types.ts +10 -0
- package/src/components/tool.tsx +22 -16
- 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/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 +2 -2
- package/src/elements/chat-workspace.tsx +153 -41
- package/src/elements/chat.tsx +45 -23
- 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 +24 -2
- 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 +129 -1
- package/src/elements/element-meta.json +5562 -1336
- package/src/elements/element-types.d.ts +732 -57
- 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 +58 -11
- 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 +38 -3
- package/src/elements/register.ts +11 -3
- 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/switch.tsx +89 -14
- package/src/elements/tabs.tsx +95 -0
- package/src/elements/tasks.tsx +70 -14
- package/src/elements/toast.tsx +17 -2
- 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 +5 -2
- package/src/primitives/card-schemas/tasks.schema.json +2 -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/toast-store.ts +43 -2
- 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 +52 -8
- package/theme.css +113 -0
- package/dist/components/slash-command.d.ts +0 -15
- package/dist/elements/chunks/Icon-D_Zmwmwh.js +0 -1
- package/dist/elements/chunks/action-icons-PVfIvcuG.js +0 -1
- package/dist/elements/chunks/attachments-Dp6vqtrc.js +0 -1
- package/dist/elements/chunks/button-CKyYD9G4.js +0 -1
- package/dist/elements/chunks/card-Cjho-PYk.js +0 -1
- package/dist/elements/chunks/chat-thread-RT808iPf.js +0 -1
- package/dist/elements/chunks/check-CDYaMYrM.js +0 -1
- package/dist/elements/chunks/chevron-down-3SBiq8HF.js +0 -1
- package/dist/elements/chunks/chevron-right-k2UFlIVK.js +0 -1
- package/dist/elements/chunks/cn-Dogu6PbV.js +0 -1
- package/dist/elements/chunks/code-block-BYUmfFSd.js +0 -1
- package/dist/elements/chunks/collapsible-BiNwdOn6.js +0 -1
- package/dist/elements/chunks/context-18g6oi4J.js +0 -1
- package/dist/elements/chunks/conversation-list-D9Y0NVdo.js +0 -1
- package/dist/elements/chunks/default-input-CSAoYYMd.js +0 -1
- package/dist/elements/chunks/download-D8K0Je8r.js +0 -1
- package/dist/elements/chunks/dropdown-bUs2iTia.js +0 -1
- package/dist/elements/chunks/external-link-6mA0wxab.js +0 -1
- package/dist/elements/chunks/file-text-DnaYk3Dm.js +0 -1
- package/dist/elements/chunks/file-tree-CgtRLlxK.js +0 -1
- package/dist/elements/chunks/hover-card-CIGw62gP.js +0 -1
- package/dist/elements/chunks/link-BlGSeoPy.js +0 -1
- package/dist/elements/chunks/loader-DvhR4qa0.js +0 -1
- package/dist/elements/chunks/message-CDZwXjhL.js +0 -1
- package/dist/elements/chunks/message-feedback-CYx81QiA.js +0 -1
- package/dist/elements/chunks/model-switcher-O_tUhoOT.js +0 -1
- package/dist/elements/chunks/overlay-DydXYMQW.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-ldlWV5kd.js +0 -1
- package/dist/elements/chunks/reasoning-2PUnHlmr.js +0 -1
- package/dist/elements/chunks/resizable-B5WNbSeb.js +0 -1
- package/dist/elements/chunks/star-t9tI-P0q.js +0 -1
- package/dist/elements/chunks/store-C6rIooZz.js +0 -1
- package/dist/elements/chunks/text-shimmer-uh9V_KUh.js +0 -1
- package/dist/elements/chunks/textarea-3nm6Ia9U.js +0 -1
- package/dist/elements/chunks/thumbs-up-BBtguGld.js +0 -1
- package/dist/elements/chunks/toast-store-D8cL3fWl.js +0 -1
- package/dist/elements/chunks/tool-GJ3vwp1U.js +0 -1
- package/dist/elements/chunks/tooltip-DwKAz4XR.js +0 -1
- package/dist/elements/chunks/triangle-alert-Ct1zt2JL.js +0 -1
- package/dist/elements/chunks/use-card-resolution-D-_0Alzy.js +0 -1
- package/dist/elements/chunks/video-mVYiM99-.js +0 -1
- package/dist/elements/chunks/x-CzxL27Ch.js +0 -1
- package/dist/kitn-chat.es.js +0 -1
- package/dist/register-JyTglR6m.js +0 -1
- package/dist/register-impl-C6wlSsts.js +0 -77
- package/src/components/slash-command.tsx +0 -232
- package/src/elements/framework-usage.json +0 -588
- /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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type JSX, For, Show, mergeProps } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface SegmentedOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional leading icon (a JSX node, e.g. a lucide-solid icon). */
|
|
8
|
+
icon?: JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SegmentedProps {
|
|
12
|
+
/** The selectable segments, left to right. */
|
|
13
|
+
options: SegmentedOption[];
|
|
14
|
+
/** The selected segment's `value` (controlled). */
|
|
15
|
+
value: string;
|
|
16
|
+
/** Fires with the next `value` when a segment is chosen. */
|
|
17
|
+
onChange: (value: string) => void;
|
|
18
|
+
/** Control density. Defaults to `md`. */
|
|
19
|
+
size?: 'sm' | 'md';
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SIZE: Record<'sm' | 'md', string> = {
|
|
24
|
+
sm: 'h-7 px-2 text-xs',
|
|
25
|
+
md: 'h-8 px-3 text-sm',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `Segmented`: a single-select pill track (a.k.a. segmented / toggle-group). A
|
|
30
|
+
* recessed `bg-surface-sunken` rail of segment buttons where the selected segment
|
|
31
|
+
* lifts onto a raised `bg-background` chip. Token-driven, light + dark.
|
|
32
|
+
*
|
|
33
|
+
* a11y: `role="group"` of real `<button>`s, each carrying `aria-pressed`; a roving
|
|
34
|
+
* tabindex (only the selected segment is in the tab order) with Arrow / Home / End
|
|
35
|
+
* key navigation that moves selection between segments.
|
|
36
|
+
*/
|
|
37
|
+
export function Segmented(props: SegmentedProps): JSX.Element {
|
|
38
|
+
const merged = mergeProps({ size: 'md' as const }, props);
|
|
39
|
+
const buttons: HTMLButtonElement[] = [];
|
|
40
|
+
|
|
41
|
+
const currentIndex = () => merged.options.findIndex((o) => o.value === merged.value);
|
|
42
|
+
|
|
43
|
+
const select = (i: number) => {
|
|
44
|
+
const opt = merged.options[i];
|
|
45
|
+
if (!opt) return;
|
|
46
|
+
merged.onChange(opt.value);
|
|
47
|
+
buttons[i]?.focus();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
51
|
+
const count = merged.options.length;
|
|
52
|
+
if (count === 0) return;
|
|
53
|
+
const cur = currentIndex();
|
|
54
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
select((Math.max(cur, 0) + 1) % count);
|
|
57
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
select((Math.max(cur, 0) - 1 + count) % count);
|
|
60
|
+
} else if (e.key === 'Home') {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
select(0);
|
|
63
|
+
} else if (e.key === 'End') {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
select(count - 1);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
role="group"
|
|
72
|
+
part="track"
|
|
73
|
+
onKeyDown={onKeyDown}
|
|
74
|
+
class={cn('inline-flex items-center gap-0.5 rounded-lg bg-surface-sunken p-0.5', merged.class)}
|
|
75
|
+
>
|
|
76
|
+
<For each={merged.options}>
|
|
77
|
+
{(opt, i) => {
|
|
78
|
+
const selected = () => opt.value === merged.value;
|
|
79
|
+
// Roving tabindex: the selected segment is tabbable; if nothing matches,
|
|
80
|
+
// the first segment falls back to tabbable so the group is reachable.
|
|
81
|
+
const focusable = () => selected() || (currentIndex() === -1 && i() === 0);
|
|
82
|
+
return (
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
part="segment"
|
|
86
|
+
ref={(el) => { buttons[i()] = el; }}
|
|
87
|
+
aria-pressed={selected()}
|
|
88
|
+
tabindex={focusable() ? 0 : -1}
|
|
89
|
+
onClick={() => merged.onChange(opt.value)}
|
|
90
|
+
class={cn(
|
|
91
|
+
'inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition-colors',
|
|
92
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
93
|
+
SIZE[merged.size],
|
|
94
|
+
selected()
|
|
95
|
+
? 'bg-background text-foreground shadow-sm'
|
|
96
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<Show when={opt.icon}>
|
|
100
|
+
<span class="inline-flex shrink-0 items-center" aria-hidden="true">{opt.icon}</span>
|
|
101
|
+
</Show>
|
|
102
|
+
<span>{opt.label}</span>
|
|
103
|
+
</button>
|
|
104
|
+
);
|
|
105
|
+
}}
|
|
106
|
+
</For>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type JSX, Show } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface SettingsGroupProps {
|
|
5
|
+
/** Small section heading shown above the card. */
|
|
6
|
+
heading: string;
|
|
7
|
+
/** Optional muted description under the heading. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** The stacked `SettingItem` rows. */
|
|
10
|
+
children: JSX.Element;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A settings section: a small heading + optional muted description over a bordered,
|
|
16
|
+
* rounded card that stacks `SettingItem` rows with hairline dividers between them.
|
|
17
|
+
* Host-agnostic chrome: the SAME group drops into a modal or a full settings page;
|
|
18
|
+
* only the content (the rows) changes per app.
|
|
19
|
+
*/
|
|
20
|
+
export function SettingsGroup(props: SettingsGroupProps): JSX.Element {
|
|
21
|
+
return (
|
|
22
|
+
<section class={cn('flex flex-col gap-2', props.class)}>
|
|
23
|
+
<div part="header" class="flex flex-col gap-0.5 px-1">
|
|
24
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
25
|
+
{props.heading}
|
|
26
|
+
</h3>
|
|
27
|
+
<Show when={props.description}>
|
|
28
|
+
<p class="text-xs text-muted-foreground">{props.description}</p>
|
|
29
|
+
</Show>
|
|
30
|
+
</div>
|
|
31
|
+
<div part="body" class="divide-y divide-border overflow-hidden rounded-xl border border-border bg-surface">
|
|
32
|
+
{props.children}
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SettingItemProps {
|
|
39
|
+
/** Row label (primary text). */
|
|
40
|
+
label: string;
|
|
41
|
+
/** Optional secondary description under the label. */
|
|
42
|
+
description?: string;
|
|
43
|
+
/** The control on the right (a `Switch`, `Segmented`, select, ...). Omit for a
|
|
44
|
+
* plain label row. */
|
|
45
|
+
control?: JSX.Element;
|
|
46
|
+
class?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One row inside a `SettingsGroup`: a left label/description block and an optional
|
|
51
|
+
* right-aligned `control`, vertically centered with comfortable padding.
|
|
52
|
+
*/
|
|
53
|
+
export function SettingItem(props: SettingItemProps): JSX.Element {
|
|
54
|
+
return (
|
|
55
|
+
<div class={cn('flex items-center justify-between gap-4 px-4 py-3', props.class)}>
|
|
56
|
+
<div part="label" class="flex min-w-0 flex-col gap-0.5">
|
|
57
|
+
<span class="text-sm text-foreground">{props.label}</span>
|
|
58
|
+
<Show when={props.description}>
|
|
59
|
+
<span class="text-xs leading-snug text-muted-foreground">{props.description}</span>
|
|
60
|
+
</Show>
|
|
61
|
+
</div>
|
|
62
|
+
<Show when={props.control}>
|
|
63
|
+
<div part="control" class="shrink-0">{props.control}</div>
|
|
64
|
+
</Show>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
package/src/ui/skeleton.tsx
CHANGED
|
@@ -1,16 +1,73 @@
|
|
|
1
|
-
import { type JSX, splitProps } from 'solid-js';
|
|
1
|
+
import { type JSX, splitProps, For, Show } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export type SkeletonVariant = 'text' | 'rect' | 'circle';
|
|
5
|
+
|
|
6
|
+
export interface SkeletonProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Shape preset. `text` = one or more lines; `rect` = a block; `circle` =
|
|
8
|
+
* round. Omit for the bare, class-driven block (size it with utility classes
|
|
9
|
+
* yourself — the original low-level usage). */
|
|
10
|
+
variant?: SkeletonVariant;
|
|
11
|
+
/** CSS width (a number is treated as px). With a `variant` and no width it
|
|
12
|
+
* fills its container (responsive); for `circle` it is the diameter. */
|
|
13
|
+
width?: string | number;
|
|
14
|
+
/** CSS height (a number is treated as px). Defaults per variant. */
|
|
15
|
+
height?: string | number;
|
|
16
|
+
/** `text` only: how many lines to render (the last is shorter). Default 1. */
|
|
17
|
+
lines?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const len = (v?: string | number) => (typeof v === 'number' ? `${v}px` : v);
|
|
21
|
+
|
|
22
|
+
const rounding = (variant?: SkeletonVariant) =>
|
|
23
|
+
variant === 'circle' ? 'rounded-full' : variant === 'text' ? 'rounded' : 'rounded-md';
|
|
5
24
|
|
|
6
25
|
function Skeleton(props: SkeletonProps) {
|
|
7
|
-
const [local, rest] = splitProps(props, ['class']);
|
|
8
|
-
|
|
26
|
+
const [local, rest] = splitProps(props, ['class', 'variant', 'width', 'height', 'lines', 'style']);
|
|
27
|
+
|
|
28
|
+
const hasWidth = () => local.width != null;
|
|
29
|
+
const hasHeight = () => local.height != null;
|
|
30
|
+
// The responsive default applies only to the variant API; bare class-driven
|
|
31
|
+
// usage is left untouched so `<Skeleton class="h-4 w-64" />` keeps working.
|
|
32
|
+
const fills = () => !hasWidth() && local.variant != null && local.variant !== 'circle';
|
|
33
|
+
|
|
34
|
+
const blockStyle = (shortLast: boolean): JSX.CSSProperties => {
|
|
35
|
+
const style: JSX.CSSProperties = typeof local.style === 'object' && local.style ? { ...local.style } : {};
|
|
36
|
+
if (local.variant === 'circle') {
|
|
37
|
+
const d = len(local.width) ?? len(local.height) ?? '2.5rem';
|
|
38
|
+
style.width = d;
|
|
39
|
+
style.height = d;
|
|
40
|
+
return style;
|
|
41
|
+
}
|
|
42
|
+
if (shortLast) style.width = '70%';
|
|
43
|
+
else if (hasWidth()) style.width = len(local.width);
|
|
44
|
+
if (hasHeight()) style.height = len(local.height);
|
|
45
|
+
else if (local.variant === 'text') style.height = '0.75rem';
|
|
46
|
+
return style;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const block = (shortLast = false) => (
|
|
9
50
|
<div
|
|
10
|
-
|
|
51
|
+
part="skeleton"
|
|
52
|
+
// `bg-foreground/10` tints from the theme foreground, so it keeps a visible
|
|
53
|
+
// ~10% contrast against the background in BOTH light and dark (the old
|
|
54
|
+
// `bg-muted` was nearly invisible on a light surface). Recolor / restyle
|
|
55
|
+
// opacity from outside via `::part(skeleton)`.
|
|
56
|
+
class={cn('animate-pulse bg-foreground/10', rounding(local.variant), fills() && 'w-full', local.class)}
|
|
57
|
+
style={blockStyle(shortLast)}
|
|
11
58
|
{...rest}
|
|
12
59
|
/>
|
|
13
60
|
);
|
|
61
|
+
|
|
62
|
+
const lineCount = () => (local.variant === 'text' ? Math.max(1, local.lines ?? 1) : 1);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Show when={lineCount() > 1} fallback={block()}>
|
|
66
|
+
<div class="flex w-full flex-col gap-2">
|
|
67
|
+
<For each={Array.from({ length: lineCount() })}>{(_, i) => block(i() === lineCount() - 1)}</For>
|
|
68
|
+
</div>
|
|
69
|
+
</Show>
|
|
70
|
+
);
|
|
14
71
|
}
|
|
15
72
|
|
|
16
73
|
export { Skeleton };
|
package/src/ui/stat.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Show, type JSX, splitProps } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface StatProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/** The small muted caption above the value. */
|
|
6
|
+
label?: string;
|
|
7
|
+
/** The big value. A default-slot / `children` override wins over this. */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** A small caption below the value. */
|
|
10
|
+
hint?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A single metric / KPI tile: a muted label on top, a large value below, and an
|
|
15
|
+
* optional hint caption. The consumer arranges tiles in their own CSS grid — this
|
|
16
|
+
* is ONE cell. `children` (the default slot in the element) override `value` for
|
|
17
|
+
* rich content.
|
|
18
|
+
*/
|
|
19
|
+
export function Stat(props: StatProps) {
|
|
20
|
+
const [local, rest] = splitProps(props, ['label', 'value', 'hint', 'class', 'children']);
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
part="stat"
|
|
24
|
+
class={cn('flex flex-col gap-1 rounded-lg bg-surface p-3', local.class)}
|
|
25
|
+
{...rest}
|
|
26
|
+
>
|
|
27
|
+
<Show when={local.label}>
|
|
28
|
+
<span part="label" class="text-xs text-muted-foreground">{local.label}</span>
|
|
29
|
+
</Show>
|
|
30
|
+
<span part="value" class="text-2xl font-semibold leading-tight text-foreground">
|
|
31
|
+
{local.children ?? local.value}
|
|
32
|
+
</span>
|
|
33
|
+
{/* When used directly (no slot), `value` renders; the element facade
|
|
34
|
+
passes a <slot>{value}</slot> as `children`, so projected light-DOM
|
|
35
|
+
content overrides `value` while `value` remains the slot's fallback. */}
|
|
36
|
+
<Show when={local.hint}>
|
|
37
|
+
<span class="text-xs text-muted-foreground">{local.hint}</span>
|
|
38
|
+
</Show>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type JSX, Show, splitProps } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export type StatusKind = 'new' | 'online' | 'busy' | 'away' | 'offline';
|
|
5
|
+
|
|
6
|
+
/** status → background hue utility (backed by the kit's tool-* / muted tokens). */
|
|
7
|
+
export const STATUS_BG: Record<StatusKind, string> = {
|
|
8
|
+
new: 'bg-tool-blue',
|
|
9
|
+
online: 'bg-tool-green',
|
|
10
|
+
busy: 'bg-tool-red',
|
|
11
|
+
away: 'bg-tool-amber',
|
|
12
|
+
offline: 'bg-muted-foreground',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SIZE: Record<'sm' | 'md', string> = { sm: 'size-2', md: 'size-2.5' };
|
|
16
|
+
|
|
17
|
+
export interface StatusProps extends JSX.HTMLAttributes<HTMLSpanElement> {
|
|
18
|
+
status?: StatusKind;
|
|
19
|
+
size?: 'sm' | 'md';
|
|
20
|
+
/** Add an animated ping ring (disabled under prefers-reduced-motion). */
|
|
21
|
+
pulse?: boolean;
|
|
22
|
+
/** Accessible name. With it, the dot is announced; without it, it is decorative. */
|
|
23
|
+
label?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Status(props: StatusProps) {
|
|
27
|
+
const [local, rest] = splitProps(props, ['status', 'size', 'pulse', 'label', 'class']);
|
|
28
|
+
const kind = () => local.status ?? 'new';
|
|
29
|
+
return (
|
|
30
|
+
<span
|
|
31
|
+
class={cn('relative inline-flex', local.class)}
|
|
32
|
+
role={local.label ? 'status' : undefined}
|
|
33
|
+
aria-label={local.label}
|
|
34
|
+
aria-hidden={local.label ? undefined : 'true'}
|
|
35
|
+
>
|
|
36
|
+
<Show when={local.pulse}>
|
|
37
|
+
<span
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
class={cn(
|
|
40
|
+
'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden',
|
|
41
|
+
STATUS_BG[kind()],
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</Show>
|
|
45
|
+
<span {...rest} class={cn('relative inline-block rounded-full', STATUS_BG[kind()], SIZE[local.size ?? 'sm'])} />
|
|
46
|
+
</span>
|
|
47
|
+
);
|
|
48
|
+
}
|
package/src/ui/switch.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { cn } from '../utils/cn';
|
|
|
3
3
|
|
|
4
4
|
export interface SwitchProps {
|
|
5
5
|
/** Controlled checked state. When set, the component defers state to the
|
|
6
|
-
* parent
|
|
6
|
+
* parent; drive it from `onChange`. Omit for uncontrolled (internal) state. */
|
|
7
7
|
checked?: boolean;
|
|
8
8
|
/** Initial checked state when uncontrolled. */
|
|
9
9
|
defaultChecked?: boolean;
|
|
@@ -11,8 +11,15 @@ export interface SwitchProps {
|
|
|
11
11
|
disabled?: boolean;
|
|
12
12
|
/** Accessible label for the control. */
|
|
13
13
|
label?: string;
|
|
14
|
+
/** Form-control name. When set (paired with `value`), a hidden checkbox carries
|
|
15
|
+
* the on/off value for serialization. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Submitted value when checked (paired with `name`). Defaults to `'on'`. */
|
|
18
|
+
value?: string;
|
|
14
19
|
/** Fires with the next checked state on toggle. */
|
|
15
20
|
onChange?: (checked: boolean) => void;
|
|
21
|
+
/** Receives the inner `role="switch"` button so a parent can focus it. */
|
|
22
|
+
buttonRef?: (el: HTMLButtonElement) => void;
|
|
16
23
|
class?: string;
|
|
17
24
|
}
|
|
18
25
|
|
|
@@ -33,30 +40,49 @@ export function Switch(props: SwitchProps) {
|
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
return (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
50
|
-
isOn() ? 'bg-primary' : 'bg-muted',
|
|
51
|
-
props.class,
|
|
43
|
+
<>
|
|
44
|
+
{props.name != null && (
|
|
45
|
+
<input
|
|
46
|
+
type="checkbox"
|
|
47
|
+
name={props.name}
|
|
48
|
+
value={props.value ?? 'on'}
|
|
49
|
+
checked={isOn()}
|
|
50
|
+
disabled={props.disabled}
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
tabindex={-1}
|
|
53
|
+
style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', 'white-space': 'nowrap' }}
|
|
54
|
+
onChange={() => { /* driven by the button; keep the input in sync only */ }}
|
|
55
|
+
/>
|
|
52
56
|
)}
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
role="switch"
|
|
60
|
+
ref={props.buttonRef}
|
|
61
|
+
aria-checked={isOn()}
|
|
62
|
+
aria-label={props.label}
|
|
63
|
+
disabled={props.disabled}
|
|
64
|
+
onClick={toggle}
|
|
65
|
+
onKeyDown={(e) => {
|
|
66
|
+
if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggle(); }
|
|
67
|
+
}}
|
|
55
68
|
class={cn(
|
|
56
|
-
'inline-
|
|
57
|
-
|
|
69
|
+
'relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors',
|
|
70
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
|
71
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
72
|
+
isOn() ? 'bg-primary' : 'bg-muted',
|
|
73
|
+
props.class,
|
|
58
74
|
)}
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
>
|
|
76
|
+
<span
|
|
77
|
+
class={cn(
|
|
78
|
+
'inline-block h-4 w-4 rounded-full shadow transition-transform',
|
|
79
|
+
// On the `primary` track use `primary-foreground` so the thumb stays
|
|
80
|
+
// visible in both themes (in dark mode `primary` is light, where a
|
|
81
|
+
// hard-coded white thumb vanished). Off-track stays white over `muted`.
|
|
82
|
+
isOn() ? 'translate-x-[1.125rem] bg-primary-foreground' : 'translate-x-0.5 bg-white',
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
</button>
|
|
86
|
+
</>
|
|
61
87
|
);
|
|
62
88
|
}
|
package/src/ui/tabs.tsx
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { For, type JSX, splitProps } from 'solid-js';
|
|
2
|
+
import { renderIcon } from './icon';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
/** One tab in a `Tabs` strip. `id` is the selected `value` throughout. */
|
|
6
|
+
export interface KaiTabItem {
|
|
7
|
+
/** Stable id, emitted as the selected `value`. Also useful for `aria-controls`. */
|
|
8
|
+
id: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Named icon (e.g. "code"), image URL / data-URI, or plain text. */
|
|
11
|
+
icon?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TabsVariant = 'segmented' | 'underline';
|
|
16
|
+
|
|
17
|
+
/** variant → tablist container utilities. */
|
|
18
|
+
export const TABLIST_CLASS: Record<TabsVariant, string> = {
|
|
19
|
+
segmented: 'inline-flex items-center gap-1 rounded-lg bg-muted p-1',
|
|
20
|
+
underline: 'inline-flex items-center gap-1 border-b border-border',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** variant → per-tab utilities, given whether the tab is active. */
|
|
24
|
+
export function tabClass(variant: TabsVariant, active: boolean): string {
|
|
25
|
+
const base =
|
|
26
|
+
'inline-flex items-center gap-1.5 whitespace-nowrap text-sm font-medium transition-colors ' +
|
|
27
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ' +
|
|
28
|
+
'disabled:pointer-events-none disabled:opacity-50';
|
|
29
|
+
if (variant === 'underline') {
|
|
30
|
+
return cn(
|
|
31
|
+
base,
|
|
32
|
+
'border-b-2 px-3 py-2 -mb-px',
|
|
33
|
+
active
|
|
34
|
+
? 'border-primary text-foreground'
|
|
35
|
+
: 'border-transparent text-muted-foreground hover:text-foreground',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return cn(
|
|
39
|
+
base,
|
|
40
|
+
'rounded-md px-3 py-1.5',
|
|
41
|
+
active
|
|
42
|
+
? 'bg-background text-foreground shadow-sm'
|
|
43
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TabsProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
48
|
+
items?: KaiTabItem[];
|
|
49
|
+
/** Selected item id. */
|
|
50
|
+
value?: string;
|
|
51
|
+
variant?: TabsVariant;
|
|
52
|
+
/** Stretch the strip to full width, each tab sharing the space equally. */
|
|
53
|
+
block?: boolean;
|
|
54
|
+
/** Disable the whole strip. */
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
/** Fired with the newly-selected item's id. */
|
|
57
|
+
onChange?: (value: string) => void;
|
|
58
|
+
/** Capture the tablist node (so the facade's `focus()` can target the active tab). */
|
|
59
|
+
ref?: (el: HTMLDivElement) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Accessible tab strip. Selection only (emits the selected id via `onChange`);
|
|
64
|
+
* the consumer renders each tab's content. Roving tabindex, Arrow/Home/End
|
|
65
|
+
* keyboard nav, disabled items skipped.
|
|
66
|
+
*/
|
|
67
|
+
export function Tabs(props: TabsProps) {
|
|
68
|
+
const [local, rest] = splitProps(props, [
|
|
69
|
+
'items', 'value', 'variant', 'block', 'disabled', 'onChange', 'ref', 'class',
|
|
70
|
+
]);
|
|
71
|
+
const items = () => local.items ?? [];
|
|
72
|
+
const variant = (): TabsVariant => local.variant ?? 'segmented';
|
|
73
|
+
|
|
74
|
+
const tabEls: Record<string, HTMLButtonElement> = {};
|
|
75
|
+
|
|
76
|
+
const enabled = () => items().filter((it) => !it.disabled);
|
|
77
|
+
const activeId = () => local.value;
|
|
78
|
+
|
|
79
|
+
// Roving tabindex needs exactly one tab in the tab order. The active tab gets
|
|
80
|
+
// it; if nothing is selected, the first enabled tab does, so the strip is still
|
|
81
|
+
// reachable by Tab.
|
|
82
|
+
const rovingId = () => {
|
|
83
|
+
if (activeId() !== undefined && enabled().some((it) => it.id === activeId())) return activeId();
|
|
84
|
+
return enabled()[0]?.id;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const select = (item: KaiTabItem) => {
|
|
88
|
+
if (local.disabled || item.disabled) return;
|
|
89
|
+
if (item.id === local.value) return;
|
|
90
|
+
local.onChange?.(item.id);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Roving focus: move focus to the target tab and emit selection (activate on focus move).
|
|
94
|
+
const focusId = (id: string) => {
|
|
95
|
+
const el = tabEls[id];
|
|
96
|
+
if (el) el.focus();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onKeyDown = (e: KeyboardEvent, item: KaiTabItem) => {
|
|
100
|
+
const list = enabled();
|
|
101
|
+
if (list.length === 0) return;
|
|
102
|
+
const i = list.findIndex((it) => it.id === item.id);
|
|
103
|
+
let next: KaiTabItem | undefined;
|
|
104
|
+
switch (e.key) {
|
|
105
|
+
case 'ArrowRight':
|
|
106
|
+
case 'ArrowDown':
|
|
107
|
+
next = list[(i + 1) % list.length];
|
|
108
|
+
break;
|
|
109
|
+
case 'ArrowLeft':
|
|
110
|
+
case 'ArrowUp':
|
|
111
|
+
next = list[(i - 1 + list.length) % list.length];
|
|
112
|
+
break;
|
|
113
|
+
case 'Home':
|
|
114
|
+
next = list[0];
|
|
115
|
+
break;
|
|
116
|
+
case 'End':
|
|
117
|
+
next = list[list.length - 1];
|
|
118
|
+
break;
|
|
119
|
+
case 'Enter':
|
|
120
|
+
case ' ':
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
select(item);
|
|
123
|
+
return;
|
|
124
|
+
default:
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (next) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
select(next);
|
|
130
|
+
focusId(next.id);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
{...rest}
|
|
137
|
+
ref={(el) => local.ref?.(el)}
|
|
138
|
+
role="tablist"
|
|
139
|
+
part="tablist"
|
|
140
|
+
aria-disabled={local.disabled ? 'true' : undefined}
|
|
141
|
+
class={cn(TABLIST_CLASS[variant()], local.block && 'flex w-full', local.class)}
|
|
142
|
+
>
|
|
143
|
+
<For each={items()}>
|
|
144
|
+
{(item) => {
|
|
145
|
+
const active = () => item.id === activeId();
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
ref={(el) => { tabEls[item.id] = el; }}
|
|
149
|
+
type="button"
|
|
150
|
+
part="tab"
|
|
151
|
+
role="tab"
|
|
152
|
+
data-active={active() ? '' : undefined}
|
|
153
|
+
aria-selected={active() ? 'true' : 'false'}
|
|
154
|
+
tabindex={item.id === rovingId() ? 0 : -1}
|
|
155
|
+
disabled={local.disabled || item.disabled}
|
|
156
|
+
class={cn(tabClass(variant(), active()), local.block && 'flex-1 justify-center')}
|
|
157
|
+
onClick={() => select(item)}
|
|
158
|
+
onKeyDown={(e) => onKeyDown(e, item)}
|
|
159
|
+
>
|
|
160
|
+
{renderIcon(item.icon, { class: 'size-4 shrink-0' })}
|
|
161
|
+
{item.label}
|
|
162
|
+
</button>
|
|
163
|
+
);
|
|
164
|
+
}}
|
|
165
|
+
</For>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|