@kitn.ai/ui 0.26.0 → 0.28.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 +25 -6
- package/bin/mcp.js +16 -3
- package/bin/route.js +23 -0
- package/bin/route.test.js +58 -0
- package/dist/{elements/chunks/Icon--Y8vsFal.js → Icon-DObhsRtm.js} +1 -1
- package/dist/action-icons-CcgNN2oT.js +1 -0
- package/dist/arrow-down-CjRU3JgV.js +1 -0
- package/dist/arrow-left-Bnpb1UEW.js +1 -0
- package/dist/artifact-Bwt7OgcR.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-Qr4kg-2b.js +1 -0
- package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-DToYdfSc.js} +1 -1
- package/dist/badge-CdEu_jn2.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-DQAAOpDz.js +1 -0
- package/dist/{elements/chunks/button-flUbeufF.js → button-DkuOnGLy.js} +1 -1
- package/dist/card-renderer-Jx70m6_l.js +1 -0
- package/dist/card-routing-0YidER_3.js +1 -0
- package/dist/check-njkVdZu2.js +1 -0
- package/dist/checkbox-D7iRB3nL.js +1 -0
- package/dist/checkbox-group-B2_rpoPM.js +1 -0
- package/dist/chevron-down-B72s6PXR.js +1 -0
- package/dist/chevron-right-BatuHzUm.js +1 -0
- package/dist/choice-card-m0BYqqOK.js +1 -0
- package/dist/circle-check-B62pODB3.js +1 -0
- package/dist/circle-x-BPG0iPst.js +1 -0
- package/dist/circle-zx0ByzY9.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-ClMLKb7C.js +1 -0
- package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-B4DlWicL.js} +1 -1
- package/dist/components/attachment-types.d.ts +7 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +1 -1
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +4 -0
- package/dist/components/chat-thread.d.ts +135 -2
- package/dist/components/conversation-item.d.ts +35 -7
- package/dist/components/conversation-list.d.ts +17 -5
- package/dist/components/conversation-panel.d.ts +29 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +48 -1
- package/dist/components/home-panel.d.ts +34 -0
- package/dist/components/message.d.ts +25 -3
- package/dist/components/reasoning.d.ts +6 -0
- package/dist/components/scroll-button.d.ts +11 -0
- package/dist/components/thread.d.ts +5 -0
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/widget-tab-bar.d.ts +16 -0
- package/dist/composer-sQ0HcvyG.js +106 -0
- package/dist/confirm-card-rQWDQsnC.js +1 -0
- package/dist/construct-cli.es.js +1337 -0
- package/dist/{elements/chunks/context-VLIL3sJ-.js → context-0kg4ZX1J.js} +1 -1
- package/dist/controllable-CnGpMF6r.js +1 -0
- package/dist/conversation-item-BxwQLaFJ.js +1 -0
- package/dist/conversation-list-BCfGN1JU.js +1 -0
- package/dist/copy-n9u9rqpi.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-BFT6c-Y8.js → create-tween-C-q-JCM-.js} +1 -1
- package/dist/{create-tween-CJNlzoaO.js → create-tween-CRrJXxBd.js} +1 -1
- package/dist/{create-tween-CQED4T0g.js → create-tween-DZ3RRDHD.js} +1 -1
- package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-uPkArjIG.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +1433 -202
- package/dist/default-input-CaMrJkgH.js +1 -0
- package/dist/define-By-uL588.js +1 -0
- package/dist/define.d.ts +4 -0
- package/dist/define.js +510 -0
- package/dist/define.server.d.ts +5 -0
- package/dist/define.server.js +506 -0
- package/dist/{elements/chunks/disclosure-CcNbyLy0.js → disclosure-WUImBbDv.js} +1 -1
- package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-wZVeJOMd.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- 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-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkbox-group.d.ts +1 -0
- package/dist/elements/checkbox-group.js +1 -0
- package/dist/elements/checkbox.d.ts +1 -0
- package/dist/elements/checkbox.js +1 -0
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +102 -8
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -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.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- 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.js +1 -1
- 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.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-grid.d.ts +1 -0
- package/dist/elements/pane-grid.js +1 -0
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +1 -0
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/select.d.ts +1 -0
- package/dist/elements/select.js +1 -0
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slider.d.ts +1 -0
- package/dist/elements/slider.js +1 -0
- package/dist/elements/slots.d.ts +1 -1
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- 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/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +380 -26
- package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-CbOpJnU3.js} +1 -1
- package/dist/embed-CrfjkbUG.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-ByXAwBBQ.js} +1 -1
- package/dist/file-text-DMib2qwt.js +1 -0
- package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-CJqM2aP7.js} +1 -1
- package/dist/folder-_BAb929C.js +1 -0
- package/dist/form-DSZgZQZk.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-BQbul9eq.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-CdcfRhYI.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7474 -5857
- package/dist/index.server.js +5965 -4762
- package/dist/info-DsFjGuI_.js +1 -0
- package/dist/input-DKbmkMos.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai.es.js +1 -1
- package/dist/kbd-PvdGhX4s.js +1 -0
- package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DbM1866N.js} +1 -1
- package/dist/link-zF8PzWfF.js +1 -0
- package/dist/loader-BM4CEz4G.js +1 -0
- package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-LMvmAyBk.js} +7 -7
- package/dist/mcp.es.js +1361 -81
- package/dist/message-BSsgvHvf.js +1 -0
- package/dist/message-BYZECyek.js +1 -0
- package/dist/message-circle-DruwObfX.js +1 -0
- package/dist/message-square-BG6q6mlm.js +1 -0
- package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-BQRoBV1V.js} +1 -1
- package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-CMpCOYcB.js} +1 -1
- package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-Cb-MkyDa.js} +1 -1
- package/dist/panel-right-i8warFxT.js +1 -0
- package/dist/paperclip-ByY3UVO6.js +1 -0
- package/dist/play-BvKKq3fX.js +1 -0
- package/dist/primitives/card-data-types.d.ts +49 -0
- package/dist/primitives/card-routing.d.ts +1 -33
- package/dist/primitives/card-validate.d.ts +2 -2
- package/dist/primitives/chat-config.d.ts +29 -2
- package/dist/primitives/conversation-store.d.ts +54 -0
- package/dist/primitives/field-mask.d.ts +96 -0
- package/dist/primitives/field-semantics.d.ts +36 -0
- package/dist/primitives/input-mask.d.ts +46 -0
- package/dist/primitives/url-scheme-policy.d.ts +34 -0
- package/dist/primitives/use-resize-observer.d.ts +2 -2
- package/dist/progress-bar-jVAHvMUf.js +1 -0
- package/dist/prompt-suggestion-CD-qtNgC.js +1 -0
- package/dist/radio-BsoBMhkF.js +1 -0
- package/dist/react/index.d.ts +586 -19
- package/dist/react.js +223 -187
- package/dist/reasoning-4kBPtlTx.js +1 -0
- package/dist/register-impl-B1hLkrI8.js +424 -0
- package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-uPmY9VY-.js} +1 -1
- package/dist/rotate-ccw-B2xn93py.js +1 -0
- package/dist/rotate-cw-CMVLoCyH.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/tool-defs.d.ts +11 -1
- package/dist/schemas.js +81 -81
- package/dist/scroll-area-BqLUo8ZZ.js +1 -0
- package/dist/scroll-button-DslXiJyL.js +1 -0
- package/dist/select-2cLRtPGT.js +1 -0
- package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-BWQtNJcM.js} +1 -1
- package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-BBR63jLg.js} +1 -1
- package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-B2ciy-Iw.js} +1 -1
- package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-CkHI-HgC.js} +1 -1
- package/dist/slider-DDR9U-i0.js +1 -0
- package/dist/{solid-BtdqNZI7.js → solid-BzCFdL25.js} +9771 -7594
- package/dist/{solid-CnavwBye.js → solid-DruVOXnf.js} +7893 -6183
- package/dist/solid.d.ts +16 -2
- package/dist/solid.js +256 -243
- package/dist/solid.server.js +256 -243
- package/dist/source-GEnFF528.js +1 -0
- package/dist/star-CilHq8GP.js +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state.js +133 -108
- package/dist/store-Ciyk6eZG.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-KkeQIbuH.js +1 -0
- package/dist/tasks-card-BrN4bggJ.js +1 -0
- package/dist/text-shimmer-Dcj1MHuq.js +1 -0
- package/dist/textarea-Dpvbngbg.js +1 -0
- package/dist/theme.tokens.css +110 -13
- package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-CsT9Wzb9.js} +1 -1
- package/dist/toast-store-tgK6MuMv.js +1 -0
- package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-DzUjIBT8.js} +1 -1
- package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-Bkkg_4t0.js} +1 -1
- package/dist/trash-2-jBKxjGGQ.js +1 -0
- package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DlQ68ppf.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +48 -3
- package/dist/typescript-RycA9KXf.js +1 -6
- package/dist/ui/checkbox-group.d.ts +83 -0
- package/dist/ui/checkbox.d.ts +40 -0
- package/dist/ui/dock.d.ts +51 -0
- package/dist/ui/icon.d.ts +3 -0
- package/dist/ui/input.d.ts +48 -2
- package/dist/ui/radio.d.ts +85 -0
- package/dist/ui/select.d.ts +71 -0
- package/dist/ui/slider.d.ts +65 -0
- package/dist/ui/switch.d.ts +9 -2
- package/dist/upload-TcAHXv9W.js +1 -0
- package/dist/url-scheme-policy-DR5DgvkM.js +1 -0
- package/dist/use-card-resolution-DaZqAdWf.js +1 -0
- package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-BhLuYxJR.js} +1 -1
- package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-CNmfHhcx.js} +2 -2
- package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-D6GsdTPA.js} +2 -2
- package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-DbW_OWL2.js} +1 -1
- package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-BRvaNHSi.js} +1 -1
- package/dist/{variant-custom-L16JLLZy.js → variant-custom-BvYHR62b.js} +1 -1
- package/dist/{variant-custom-BdvYC7af.js → variant-custom-DMTrEO5w.js} +2 -2
- package/dist/{variant-custom-C714mqKl.js → variant-custom-DV2x254s.js} +2 -2
- package/dist/{variant-wave-ecwykeyL.js → variant-wave-BNMGrDfj.js} +2 -2
- package/dist/{variant-wave-2S4La2Ka.js → variant-wave-BpDd8t3Z.js} +1 -1
- package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CyBueXj9.js} +2 -2
- package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-sp-qwsdI.js} +1 -1
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/x-Z5UntkIJ.js +1 -0
- package/frameworks/react/index.tsx +210 -22
- package/llms-full.txt +1184 -22
- package/llms.txt +6 -3
- package/package.json +17 -4
- package/src/agent-tooling/README.md +192 -0
- package/src/agent-tooling/catalog/invariants.ts +3 -3
- package/src/agent-tooling/catalog/surfaces.ts +72 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +147 -0
- package/src/agent-tooling/construct/codegen.ts +1635 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +319 -0
- package/src/agent-tooling/construct/dev.ts +148 -0
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +32 -0
- package/src/agent-tooling/construct/schema.ts +452 -0
- package/src/agent-tooling/mcp/server.ts +2 -1
- package/src/agent-tooling/mcp/tools/construct.ts +130 -0
- package/src/agent-tooling/mcp/tools/debug.ts +126 -4
- package/src/agent-tooling/mcp/tools/reference.ts +104 -3
- package/src/agent-tooling/recipes/composed-thread.ts +714 -0
- package/src/agent-tooling/recipes/index.ts +22 -0
- package/src/agent-tooling/recipes/types.ts +32 -0
- package/src/agent-tooling/registry.ts +5 -0
- package/src/components/attachment-types.ts +7 -0
- package/src/components/audio-visualizer/fit-scale.ts +1 -1
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +602 -43
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +2 -2
- package/src/components/composer.tsx +89 -15
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +56 -9
- package/src/components/conversation-list.tsx +25 -10
- package/src/components/conversation-panel.tsx +118 -0
- package/src/components/empty.tsx +14 -3
- package/src/components/file-tree.tsx +5 -1
- package/src/components/form-widgets.tsx +257 -195
- package/src/components/form.tsx +297 -9
- package/src/components/home-panel.tsx +159 -0
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +71 -11
- package/src/components/prompt-input.tsx +7 -1
- package/src/components/prompt-suggestion.tsx +4 -1
- package/src/components/reasoning.tsx +30 -3
- package/src/components/response-compare.tsx +2 -2
- package/src/components/scroll-button.tsx +56 -4
- package/src/components/source.tsx +6 -1
- package/src/components/tasks-card.tsx +58 -16
- package/src/components/thread.tsx +10 -1
- package/src/components/toast.tsx +1 -1
- package/src/components/tool-types.ts +9 -0
- package/src/components/widget-tab-bar.tsx +73 -0
- package/src/elements/attachments.tsx +3 -1
- package/src/elements/chat-workspace.tsx +1 -1
- package/src/elements/chat.tsx +115 -5
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +62 -22
- package/src/elements/conversation-list.tsx +2 -2
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/dock.tsx +11 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-manifest.json +24 -0
- package/src/elements/element-meta.json +767 -34
- package/src/elements/element-nonscalar.json +17 -0
- package/src/elements/element-types.d.ts +380 -26
- package/src/elements/feedback-bar.tsx +5 -0
- package/src/elements/icon-names.json +29 -0
- package/src/elements/input.tsx +312 -23
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-input.tsx +8 -2
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +6 -0
- package/src/elements/scroll-button.tsx +54 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +1 -1
- package/src/elements/styles.css +89 -3
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +14 -1
- package/src/index.ts +5 -2
- package/src/primitives/card-data-types.ts +49 -0
- package/src/primitives/card-host.tsx +4 -2
- package/src/primitives/card-routing.ts +7 -56
- package/src/primitives/card-schemas/form.schema.json +21 -1
- package/src/primitives/card-validate.ts +10 -2
- package/src/primitives/chat-config.tsx +33 -6
- package/src/primitives/conversation-store.ts +248 -0
- package/src/primitives/field-mask.ts +256 -0
- package/src/primitives/field-semantics.ts +115 -0
- package/src/primitives/input-mask.ts +853 -0
- package/src/primitives/toast-store.ts +66 -4
- package/src/primitives/url-scheme-policy.ts +70 -0
- package/src/primitives/use-resize-observer.ts +2 -2
- package/src/schemas/tool-defs.ts +12 -2
- package/src/solid.ts +20 -2
- package/src/state/index.ts +1 -1
- package/src/state/mock.ts +92 -6
- package/src/types.ts +45 -3
- package/src/ui/agent-card.tsx +2 -2
- package/src/ui/avatar.tsx +1 -1
- package/src/ui/checkbox-group.tsx +153 -0
- package/src/ui/checkbox.tsx +63 -0
- package/src/ui/dock.tsx +192 -7
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +249 -9
- package/src/ui/kbd.tsx +1 -1
- package/src/ui/nav.tsx +1 -1
- package/src/ui/pane-group.tsx +2 -2
- package/src/ui/radio.tsx +150 -0
- package/src/ui/select.tsx +168 -0
- package/src/ui/slider.tsx +178 -0
- package/src/ui/switch.tsx +49 -18
- package/src/utils/cn.ts +11 -5
- package/theme.css +112 -13
- package/dist/elements/chunks/action-icons-D9u3OzYW.js +0 -1
- package/dist/elements/chunks/arrow-left-CVh1TeN7.js +0 -1
- package/dist/elements/chunks/artifact-BkYcfIlZ.js +0 -1
- package/dist/elements/chunks/attachments-DwfNlubq.js +0 -1
- package/dist/elements/chunks/badge-Q0-Cmwzz.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-3Dyl-FpN.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/check-B5kBPKeo.js +0 -1
- package/dist/elements/chunks/chevron-down-DZ1GS39h.js +0 -1
- package/dist/elements/chunks/chevron-right-CvP7OUd2.js +0 -1
- package/dist/elements/chunks/choice-card-D4QUIOWZ.js +0 -1
- package/dist/elements/chunks/circle-CDf9g6MQ.js +0 -1
- package/dist/elements/chunks/circle-check-CA47Nqbz.js +0 -1
- package/dist/elements/chunks/circle-x-CJ_HVbC8.js +0 -1
- package/dist/elements/chunks/code-block-BdLiTitd.js +0 -1
- package/dist/elements/chunks/composer-Byve-n8K.js +0 -69
- package/dist/elements/chunks/confirm-card-B0flE3MN.js +0 -1
- package/dist/elements/chunks/controllable-DTwTIlwp.js +0 -1
- package/dist/elements/chunks/conversation-item-BDztezOG.js +0 -1
- package/dist/elements/chunks/copy-Nf-kqxXH.js +0 -1
- package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
- package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
- package/dist/elements/chunks/default-input-C2iLFq0b.js +0 -1
- package/dist/elements/chunks/define-wjHR1MkU.js +0 -1
- package/dist/elements/chunks/download-C4V7tBdb.js +0 -1
- package/dist/elements/chunks/embed-D3KewsIJ.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D1G8khle.js +0 -1
- package/dist/elements/chunks/folder-WW2LGLid.js +0 -1
- package/dist/elements/chunks/form-CQirdC4W.js +0 -1
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
- package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
- package/dist/elements/chunks/hover-card-CJJkfHTK.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-Cfgmf4fa.js +0 -1
- package/dist/elements/chunks/info-DX3YMOt6.js +0 -1
- package/dist/elements/chunks/input-BtW9U6Sq.js +0 -1
- package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
- package/dist/elements/chunks/json-DxJze_jm.js +0 -1
- package/dist/elements/chunks/kbd-LGXwuT_V.js +0 -1
- package/dist/elements/chunks/link-2My69BqT.js +0 -1
- package/dist/elements/chunks/loader-iOxc_Tu-.js +0 -1
- package/dist/elements/chunks/message-VMZohrXx.js +0 -1
- package/dist/elements/chunks/message-circle-CpXMM-_n.js +0 -1
- package/dist/elements/chunks/message-square-DxcWtETQ.js +0 -1
- package/dist/elements/chunks/message-tZmV2A3p.js +0 -1
- package/dist/elements/chunks/paperclip-CRZVp4z_.js +0 -1
- package/dist/elements/chunks/progress-bar-DNy66e_E.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-Bn64Nl7L.js +0 -1
- package/dist/elements/chunks/reasoning-Emn9N4-T.js +0 -1
- package/dist/elements/chunks/rotate-cw-CPWe37D9.js +0 -1
- package/dist/elements/chunks/scroll-area-ClZkZxIk.js +0 -1
- package/dist/elements/chunks/scroll-button-DPE-t3x_.js +0 -1
- package/dist/elements/chunks/share-CyfnNEqp.js +0 -1
- package/dist/elements/chunks/source-UWCvmVak.js +0 -1
- package/dist/elements/chunks/star-DoHFAikQ.js +0 -1
- package/dist/elements/chunks/store-Cjq0qwYN.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-CmZ5xh6v.js +0 -1
- package/dist/elements/chunks/text-shimmer-DYn09zrz.js +0 -1
- package/dist/elements/chunks/textarea-skiGHURM.js +0 -1
- package/dist/elements/chunks/toast-store-CFnQpThX.js +0 -1
- package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
- package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
- package/dist/elements/chunks/use-card-resolution-DoA2f_EU.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-B6UnOiLw.js +0 -1
- package/dist/llms/llms-full.txt +0 -2949
- package/dist/llms/llms.txt +0 -156
- package/dist/register-impl-DpE7icIb.js +0 -293
- package/dist/ui/stat.d.ts +0 -16
- package/src/ui/stat.tsx +0 -41
- /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
- /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
- /package/dist/{elements/chunks/slots-DaNjX0rc.js → slots-DaNjX0rc.js} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { type JSX, For, Show, createEffect, splitProps } from 'solid-js';
|
|
2
|
+
import { ChevronDown } from 'lucide-solid';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
import { FIELD_BASE, FIELD_INVALID } from './input';
|
|
5
|
+
|
|
6
|
+
/** One choice in a {@link Select}. */
|
|
7
|
+
export interface SelectOption<T = string> {
|
|
8
|
+
/** The value this row selects. Compared to the select's `value` by identity. */
|
|
9
|
+
value: T;
|
|
10
|
+
/** Visible text. Defaults to `String(value)`. Native `<option>`s hold text, not markup. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Disable this option alone (the select's `disabled` disables the whole control). */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SelectProps<T = string>
|
|
17
|
+
extends Omit<JSX.SelectHTMLAttributes<HTMLSelectElement>, 'value' | 'children'> {
|
|
18
|
+
/**
|
|
19
|
+
* The choices, in display order. Rendered in full: the kit never truncates,
|
|
20
|
+
* re-orders or de-duplicates a consumer's option list.
|
|
21
|
+
*
|
|
22
|
+
* Omit it and pass `children` instead when you need `<optgroup>`s or anything else
|
|
23
|
+
* a flat list cannot express.
|
|
24
|
+
*/
|
|
25
|
+
options?: readonly SelectOption<T>[];
|
|
26
|
+
/**
|
|
27
|
+
* The selected value, matched against each option's `value` by IDENTITY (so a
|
|
28
|
+
* numeric enum selects on the number, not on its string form). Pass an array for a
|
|
29
|
+
* `multiple` select.
|
|
30
|
+
*/
|
|
31
|
+
value?: T | readonly T[];
|
|
32
|
+
/**
|
|
33
|
+
* Text for a leading, disabled, empty-valued option — the "nothing chosen yet" row.
|
|
34
|
+
* Omitted means no such row is rendered at all; there is no default placeholder,
|
|
35
|
+
* because inventing one would put words in the consumer's UI.
|
|
36
|
+
*/
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** Force the invalid (destructive-border) state. Matches `Input`'s. */
|
|
39
|
+
invalid?: boolean;
|
|
40
|
+
/** Raw `<option>` / `<optgroup>` children, for lists `options` cannot express.
|
|
41
|
+
* `value` still drives the selection when you use these: it is applied to the
|
|
42
|
+
* rendered options rather than quietly doing nothing. */
|
|
43
|
+
children?: JSX.Element;
|
|
44
|
+
/** Extra classes for the positioning wrapper that holds the chevron. */
|
|
45
|
+
containerClass?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A select. A REAL native `<select>` behind `appearance: none`, in a box consistent
|
|
50
|
+
* with {@link Input}, with the kit's own chevron drawn over it.
|
|
51
|
+
*
|
|
52
|
+
* **Native, not a listbox, and that is the design.** A hand-built popup can be made to
|
|
53
|
+
* look identical and cannot be made to behave identically: the native control brings
|
|
54
|
+
* the platform picker on mobile, type-ahead on desktop, form participation, and the
|
|
55
|
+
* OS's own accessibility tree. This kit's control audit found every one of its
|
|
56
|
+
* accessibility defects in a control that had replaced a native element.
|
|
57
|
+
*
|
|
58
|
+
* The trade is that the *dropdown list itself* stays OS chrome — it renders outside the
|
|
59
|
+
* page and no stylesheet reaches it. What the kit can do is make it follow the kit's
|
|
60
|
+
* light/dark mode rather than the OS's, which `color-scheme` on the element host
|
|
61
|
+
* already does (`src/elements/styles.css`). Styling the closed control and leaving the
|
|
62
|
+
* open list to the platform is the whole of the bargain.
|
|
63
|
+
*
|
|
64
|
+
* Everything not listed in `SelectProps` is forwarded to the `<select>`, so `id`,
|
|
65
|
+
* `name`, `required`, `disabled`, `multiple`, `size`, `aria-*`, any `data-*` hook and
|
|
66
|
+
* the DOM events behave exactly as they do on a plain select. No validation is
|
|
67
|
+
* applied: `required` reaches the native attribute and stops there.
|
|
68
|
+
*
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <Select
|
|
71
|
+
* options={[{ value: 'low' }, { value: 'high', label: 'High priority' }]}
|
|
72
|
+
* value={sev()}
|
|
73
|
+
* placeholder="Choose a severity…"
|
|
74
|
+
* onChange={(e) => setSev(e.currentTarget.value)}
|
|
75
|
+
* />
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function Select<T = string>(props: SelectProps<T>): JSX.Element {
|
|
79
|
+
const [local, rest] = splitProps(props, [
|
|
80
|
+
'class', 'containerClass', 'options', 'value', 'placeholder', 'invalid', 'children', 'ref',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
// A `multiple` select is a scrolling list box, not a closed control: there is no
|
|
84
|
+
// collapsed state for a chevron to point at, and `appearance: none` on one buys
|
|
85
|
+
// nothing. So the custom chrome applies to the single-value shape only.
|
|
86
|
+
const isMulti = () => !!rest.multiple;
|
|
87
|
+
|
|
88
|
+
const isSelected = (v: T): boolean => {
|
|
89
|
+
const sel = local.value;
|
|
90
|
+
if (Array.isArray(sel)) return (sel as readonly T[]).includes(v);
|
|
91
|
+
return sel === v;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Nothing chosen yet, for deciding whether the placeholder row is the selected one.
|
|
95
|
+
// Array-aware on purpose: an empty array means nothing is selected, and without this
|
|
96
|
+
// branch the browser would fall through to auto-selecting the first NON-disabled
|
|
97
|
+
// option — i.e. showing a real choice while the caller's state said none was made.
|
|
98
|
+
const isEmpty = (): boolean => {
|
|
99
|
+
const v = local.value;
|
|
100
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
101
|
+
return v === undefined || v === null || (v as unknown) === '';
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div class={cn('relative block w-full', local.containerClass)}>
|
|
106
|
+
<select
|
|
107
|
+
{...rest}
|
|
108
|
+
ref={(el) => {
|
|
109
|
+
// `ref` is split out and re-applied by hand because this component needs the
|
|
110
|
+
// element itself for the `children` path below. Solid always hands a component
|
|
111
|
+
// a ref FUNCTION (a variable ref compiles into one), so the typeof guard is for
|
|
112
|
+
// the hand-written object case only.
|
|
113
|
+
const forward = local.ref;
|
|
114
|
+
if (typeof forward === 'function') (forward as (el: HTMLSelectElement) => void)(el);
|
|
115
|
+
|
|
116
|
+
// `value` drives the `options` list declaratively (each `<option selected>`),
|
|
117
|
+
// which cannot reach options the CALLER rendered as `children`. Applying it
|
|
118
|
+
// imperatively for that path is the difference between "children are an
|
|
119
|
+
// escape hatch" and "`value` silently does nothing once you use one".
|
|
120
|
+
createEffect(() => {
|
|
121
|
+
if (local.children === undefined) return;
|
|
122
|
+
const v = local.value;
|
|
123
|
+
if (v === undefined || v === null) return;
|
|
124
|
+
if (Array.isArray(v)) {
|
|
125
|
+
const wanted = new Set((v as readonly unknown[]).map(String));
|
|
126
|
+
for (const o of el.options) o.selected = wanted.has(o.value);
|
|
127
|
+
} else {
|
|
128
|
+
el.value = String(v);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}}
|
|
132
|
+
class={cn(
|
|
133
|
+
FIELD_BASE,
|
|
134
|
+
// `peer` so the chevron beside it can dim with the control.
|
|
135
|
+
'peer',
|
|
136
|
+
// `pr-9` reserves the lane the chevron sits in, so a long option label never
|
|
137
|
+
// slides under it.
|
|
138
|
+
!isMulti() && 'cursor-pointer appearance-none pr-9',
|
|
139
|
+
local.invalid && FIELD_INVALID,
|
|
140
|
+
local.class,
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
<Show when={local.placeholder !== undefined}>
|
|
144
|
+
<option value="" disabled selected={isEmpty()}>
|
|
145
|
+
{local.placeholder}
|
|
146
|
+
</option>
|
|
147
|
+
</Show>
|
|
148
|
+
<For each={local.options}>
|
|
149
|
+
{(opt) => (
|
|
150
|
+
<option value={String(opt.value)} disabled={opt.disabled} selected={isSelected(opt.value)}>
|
|
151
|
+
{opt.label ?? String(opt.value)}
|
|
152
|
+
</option>
|
|
153
|
+
)}
|
|
154
|
+
</For>
|
|
155
|
+
{local.children}
|
|
156
|
+
</select>
|
|
157
|
+
<Show when={!isMulti()}>
|
|
158
|
+
{/* `pointer-events-none` so the chevron is decoration and every click, including
|
|
159
|
+
one landing on the arrow, still opens the native picker. `aria-hidden`
|
|
160
|
+
because the select already announces itself as a combo box. */}
|
|
161
|
+
<ChevronDown
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
class="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground peer-disabled:opacity-50"
|
|
164
|
+
/>
|
|
165
|
+
</Show>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { type JSX, Show, splitProps, createSignal } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface SliderProps
|
|
5
|
+
extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'type' | 'children' | 'min' | 'max' | 'step' | 'value'> {
|
|
6
|
+
/**
|
|
7
|
+
* Lowest selectable value. REQUIRED, and deliberately so.
|
|
8
|
+
*
|
|
9
|
+
* A range with no `min`/`max` is not a slider, it is a slider-shaped guess, and the
|
|
10
|
+
* guess belongs to whoever knows what the number means. `SliderWidget` reading a
|
|
11
|
+
* JSON-Schema field still defaults an absent `minimum` to 0 — that is the WIDGET's
|
|
12
|
+
* decision about a consumer-authored schema, and it stays there rather than being
|
|
13
|
+
* quietly adopted by every caller of this component.
|
|
14
|
+
*/
|
|
15
|
+
min: number;
|
|
16
|
+
/** Highest selectable value. REQUIRED for the same reason as `min`. */
|
|
17
|
+
max: number;
|
|
18
|
+
/** Granularity. Omitted means the native default (1); `'any'` means continuous. */
|
|
19
|
+
step?: number | 'any';
|
|
20
|
+
/** The current value. Controlled — drive it from `onInput`. Omit for uncontrolled. */
|
|
21
|
+
value?: number;
|
|
22
|
+
/** Initial value when uncontrolled. */
|
|
23
|
+
defaultValue?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Show the current value beside the track. Off by default.
|
|
26
|
+
*
|
|
27
|
+
* Two shapes, because one is not enough and three would be too many. `true` renders
|
|
28
|
+
* the raw number, which is what most sliders want. A FUNCTION renders whatever it
|
|
29
|
+
* returns, because plenty of sliders are not counting bare numbers: `60%`, `3 of 5`,
|
|
30
|
+
* `1m 30s`. A boolean alone cannot express those, and a formatter alone would make
|
|
31
|
+
* the common case read `valueLabel={(v) => v}`.
|
|
32
|
+
*
|
|
33
|
+
* The readout is `aria-hidden`. The input already reports the same number through
|
|
34
|
+
* `aria-valuenow` / `aria-valuetext`, so an exposed copy would be announced twice.
|
|
35
|
+
*
|
|
36
|
+
* NOTE ON STRUCTURE: with a readout the component renders a flex row around the
|
|
37
|
+
* input; without one it is still a bare `<input>` and nothing changes for existing
|
|
38
|
+
* callers. Toggling this at runtime therefore rebuilds the input element, which is
|
|
39
|
+
* fine for a control panel and not something a real app does mid-drag.
|
|
40
|
+
*/
|
|
41
|
+
valueLabel?: boolean | ((value: number) => JSX.Element);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A slider. A REAL `<input type="range">` behind `appearance: none`, styled by the
|
|
46
|
+
* kit's `.kai-range` rule, never a `<div>` with a drag handler.
|
|
47
|
+
*
|
|
48
|
+
* The native control brings the whole keyboard contract (arrows, Home, End,
|
|
49
|
+
* PageUp/PageDown), pointer and touch dragging, form participation via `name`, and
|
|
50
|
+
* the `slider` role with a live value announcement — all of it for free and all of it
|
|
51
|
+
* correct. Every accessibility defect this kit's control audit found was in a control
|
|
52
|
+
* that had replaced the native element with something hand-rolled.
|
|
53
|
+
*
|
|
54
|
+
* What the component adds is the **filled track**. `.kai-range` paints the portion
|
|
55
|
+
* left of the thumb from a `--kai-range-fill` custom property, and until now every
|
|
56
|
+
* caller computed that percentage itself. It is arithmetic over `min`, `max` and the
|
|
57
|
+
* current value — three things this component already has — so no consumer should
|
|
58
|
+
* ever write it again.
|
|
59
|
+
*
|
|
60
|
+
* Everything not listed in `SliderProps` is forwarded to the input, so `id`, `name`,
|
|
61
|
+
* `disabled`, `required`, `aria-*` and any `data-*` hook behave exactly as they do on
|
|
62
|
+
* a plain `<input>`. No clamping and no validation is applied: a value outside
|
|
63
|
+
* `min`..`max` is the browser's business, not ours.
|
|
64
|
+
*
|
|
65
|
+
* ```tsx
|
|
66
|
+
* <Slider min={0} max={100} step={5} value={v()} onInput={(e) => setV(e.currentTarget.valueAsNumber)} />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function Slider(props: SliderProps): JSX.Element {
|
|
70
|
+
const [local, rest] = splitProps(props, [
|
|
71
|
+
'class', 'style', 'min', 'max', 'step', 'value', 'defaultValue', 'ref', 'valueLabel',
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// The uncontrolled mirror. A slider's fill has to follow the thumb even when nobody
|
|
75
|
+
// is holding the value in a signal, so the component tracks the live value itself
|
|
76
|
+
// through a NATIVE `on:input` listener. That is `on:` and not `onInput` on purpose:
|
|
77
|
+
// `onInput` stays in `rest` and reaches the input untouched, so a caller's handler
|
|
78
|
+
// is neither wrapped nor swallowed, and the two listeners simply both run.
|
|
79
|
+
const [live, setLive] = createSignal<number | undefined>(props.defaultValue);
|
|
80
|
+
|
|
81
|
+
// HTML's own rule for a range with no value: the midpoint of the span. Restated here
|
|
82
|
+
// rather than left to the browser because the fill has to agree with the thumb on the
|
|
83
|
+
// very first paint, before any input event exists.
|
|
84
|
+
const current = () => local.value ?? live() ?? (local.min + local.max) / 2;
|
|
85
|
+
|
|
86
|
+
const fill = (): string => {
|
|
87
|
+
const lo = local.min;
|
|
88
|
+
const hi = local.max;
|
|
89
|
+
if (!(hi > lo)) return '0%';
|
|
90
|
+
// Clamp: a value outside the range is the caller's to fix, but a track painted
|
|
91
|
+
// 140% wide is a rendering artefact nobody asked for.
|
|
92
|
+
const pct = ((current() - lo) / (hi - lo)) * 100;
|
|
93
|
+
return `${Math.min(100, Math.max(0, pct))}%`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Merge rather than replace: `style` is a legitimate thing to pass a slider, and the
|
|
97
|
+
// fill is a custom property that has to survive alongside it. Solid accepts either a
|
|
98
|
+
// string or an object here, so both shapes are handled.
|
|
99
|
+
const style = (): string | JSX.CSSProperties => {
|
|
100
|
+
const s = local.style;
|
|
101
|
+
if (typeof s === 'string') return `${s.replace(/;\s*$/, '')};--kai-range-fill:${fill()}`;
|
|
102
|
+
return { ...(s ?? {}), '--kai-range-fill': fill() } as JSX.CSSProperties;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// CONTROLLED-MODE RECONCILIATION. A controlled `<input type="range">` in Solid can
|
|
106
|
+
// drift: the browser moves the thumb on a drag, and if the caller's `value` does not
|
|
107
|
+
// change in response, the bound expression is unchanged, so Solid never writes the
|
|
108
|
+
// DOM back. The thumb ends up somewhere the component's own state says it is not —
|
|
109
|
+
// and because the fill is derived from `value`, the two visibly DISAGREE. That is the
|
|
110
|
+
// exact symptom reported against the Storybook Playground: thumb slides, track does
|
|
111
|
+
// not. A pinned, writer-less `<Slider value={40} />` is a legitimate thing to render,
|
|
112
|
+
// and it should look pinned rather than half-moved.
|
|
113
|
+
//
|
|
114
|
+
// WHY A MICROTASK AND NOT THE HANDLER OR A BARE EFFECT. Both of those run too early
|
|
115
|
+
// and would break the ordinary controlled case. The `on:input` listener above is
|
|
116
|
+
// attached to the TARGET, so it runs before the caller's `onInput`, which Solid
|
|
117
|
+
// delegates at the document; snapping back there would hand the caller's handler a
|
|
118
|
+
// value we had already reset. A `createEffect` is flushed at the end of the update
|
|
119
|
+
// that `setLive` starts, which is still inside the target listener and therefore also
|
|
120
|
+
// before the delegated one. A microtask runs after the whole event turn: every input
|
|
121
|
+
// listener has run and any signal the caller set has propagated, so a value that
|
|
122
|
+
// still disagrees means the caller genuinely did not accept the edit.
|
|
123
|
+
let el: HTMLInputElement | undefined;
|
|
124
|
+
const reconcileControlled = () => {
|
|
125
|
+
const v = local.value;
|
|
126
|
+
// Uncontrolled: `live` is the truth and the DOM is already showing it.
|
|
127
|
+
if (v === undefined || el === undefined) return;
|
|
128
|
+
if (el.valueAsNumber !== v) el.value = String(v);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const readout = (): JSX.Element => {
|
|
132
|
+
const vl = local.valueLabel;
|
|
133
|
+
return typeof vl === 'function' ? vl(current()) : current();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const control = () => (
|
|
137
|
+
<input
|
|
138
|
+
{...rest}
|
|
139
|
+
type="range"
|
|
140
|
+
class={cn('kai-range', local.class)}
|
|
141
|
+
style={style()}
|
|
142
|
+
min={local.min}
|
|
143
|
+
max={local.max}
|
|
144
|
+
step={local.step}
|
|
145
|
+
value={local.value ?? live() ?? undefined}
|
|
146
|
+
ref={(node) => {
|
|
147
|
+
// `ref` is split out and re-applied by hand because this component needs the
|
|
148
|
+
// element for the reconciliation above. Solid always hands a component a ref
|
|
149
|
+
// FUNCTION (a variable ref compiles into one), so the typeof guard is for the
|
|
150
|
+
// hand-written object case only.
|
|
151
|
+
el = node;
|
|
152
|
+
const forward = local.ref;
|
|
153
|
+
if (typeof forward === 'function') (forward as (n: HTMLInputElement) => void)(node);
|
|
154
|
+
}}
|
|
155
|
+
on:input={(e) => {
|
|
156
|
+
setLive((e.currentTarget as HTMLInputElement).valueAsNumber);
|
|
157
|
+
queueMicrotask(reconcileControlled);
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Show when={local.valueLabel} fallback={control()}>
|
|
164
|
+
<div class="flex items-center gap-3">
|
|
165
|
+
{control()}
|
|
166
|
+
{/* aria-hidden: the input already announces this number. The readout is a
|
|
167
|
+
second copy for the eyes only, and exposing it would double the
|
|
168
|
+
announcement on every step of a drag. */}
|
|
169
|
+
<span
|
|
170
|
+
aria-hidden="true"
|
|
171
|
+
class="min-w-9 shrink-0 rounded-md bg-background px-2 py-1 text-center text-sm font-medium tabular-nums text-foreground shadow-sm"
|
|
172
|
+
>
|
|
173
|
+
{readout()}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
</Show>
|
|
177
|
+
);
|
|
178
|
+
}
|
package/src/ui/switch.tsx
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { createSignal } from 'solid-js';
|
|
1
|
+
import { type JSX, createSignal, splitProps } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export interface SwitchProps
|
|
4
|
+
export interface SwitchProps
|
|
5
|
+
extends Omit<
|
|
6
|
+
JSX.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
7
|
+
| 'type' | 'role' | 'aria-checked' | 'children' | 'ref'
|
|
8
|
+
| 'onChange' | 'onchange' | 'onClick' | 'onclick' | 'onKeyDown' | 'onkeydown'
|
|
9
|
+
> {
|
|
5
10
|
/** Controlled checked state. When set, the component defers state to the
|
|
6
11
|
* parent; drive it from `onChange`. Omit for uncontrolled (internal) state. */
|
|
7
12
|
checked?: boolean;
|
|
@@ -26,51 +31,74 @@ export interface SwitchProps {
|
|
|
26
31
|
/**
|
|
27
32
|
* A toggle switch (`role="switch"`). Controlled via `checked`, or uncontrolled
|
|
28
33
|
* from `defaultChecked`. Keyboard-operable (Space/Enter) and theme-tokened.
|
|
34
|
+
*
|
|
35
|
+
* Anything not named in `SwitchProps` is forwarded to the inner button, so `id`,
|
|
36
|
+
* `aria-required` / `aria-invalid` / `aria-describedby` and any `data-*` hook land
|
|
37
|
+
* on the focusable element without a caller stamping them through a `ref`. The
|
|
38
|
+
* switch keeps ownership of `type`, `role`, `aria-checked` and the toggle handlers:
|
|
39
|
+
* those are the control, not decoration.
|
|
29
40
|
*/
|
|
30
41
|
export function Switch(props: SwitchProps) {
|
|
31
|
-
const [
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
const [local, rest] = splitProps(props, [
|
|
43
|
+
'checked', 'defaultChecked', 'disabled', 'label', 'name', 'value', 'onChange', 'buttonRef', 'class',
|
|
44
|
+
]);
|
|
45
|
+
const [internal, setInternal] = createSignal(local.defaultChecked ?? false);
|
|
46
|
+
const isControlled = () => local.checked !== undefined;
|
|
47
|
+
const isOn = () => (isControlled() ? !!local.checked : internal());
|
|
34
48
|
|
|
35
49
|
const toggle = () => {
|
|
36
|
-
if (
|
|
50
|
+
if (local.disabled) return;
|
|
37
51
|
const next = !isOn();
|
|
38
52
|
if (!isControlled()) setInternal(next);
|
|
39
|
-
|
|
53
|
+
local.onChange?.(next);
|
|
40
54
|
};
|
|
41
55
|
|
|
42
56
|
return (
|
|
43
57
|
<>
|
|
44
|
-
{
|
|
58
|
+
{local.name != null && (
|
|
45
59
|
<input
|
|
46
60
|
type="checkbox"
|
|
47
|
-
name={
|
|
48
|
-
value={
|
|
61
|
+
name={local.name}
|
|
62
|
+
value={local.value ?? 'on'}
|
|
49
63
|
checked={isOn()}
|
|
50
|
-
disabled={
|
|
64
|
+
disabled={local.disabled}
|
|
51
65
|
aria-hidden="true"
|
|
52
66
|
tabindex={-1}
|
|
53
67
|
style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', 'white-space': 'nowrap' }}
|
|
54
68
|
onChange={() => { /* driven by the button; keep the input in sync only */ }}
|
|
55
69
|
/>
|
|
56
70
|
)}
|
|
71
|
+
{/* `{...rest}` comes FIRST so the attributes below win: a caller can add an
|
|
72
|
+
`id` or an `aria-describedby`, but cannot turn this into something that is
|
|
73
|
+
no longer a switch. */}
|
|
57
74
|
<button
|
|
75
|
+
{...rest}
|
|
58
76
|
type="button"
|
|
59
77
|
role="switch"
|
|
60
|
-
ref={
|
|
78
|
+
ref={local.buttonRef}
|
|
61
79
|
aria-checked={isOn()}
|
|
62
|
-
aria-label={
|
|
63
|
-
disabled={
|
|
80
|
+
aria-label={local.label}
|
|
81
|
+
disabled={local.disabled}
|
|
64
82
|
onClick={toggle}
|
|
65
83
|
onKeyDown={(e) => {
|
|
66
84
|
if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggle(); }
|
|
67
85
|
}}
|
|
68
86
|
class={cn(
|
|
69
|
-
'relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors',
|
|
87
|
+
'relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border transition-colors',
|
|
70
88
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
|
71
89
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
// WCAG 2.1 SC 1.4.11: the track IS this control's visual boundary, so it
|
|
91
|
+
// needs 3:1 against the surface behind it. The OFF fill cannot carry that
|
|
92
|
+
// — `--color-muted` is a shared surface token (measured 1.10:1 light on
|
|
93
|
+
// `--color-background`) and darkening it to clear 3:1 would repaint every
|
|
94
|
+
// muted surface in the kit. So the boundary moves to a border on the
|
|
95
|
+
// control-edge token `--color-input`, which exists for exactly this and is
|
|
96
|
+
// already held to the 3:1 floor for radios, checkboxes and inputs
|
|
97
|
+
// (tests/primitives/control-contrast.test.ts). The ON track keeps its
|
|
98
|
+
// ~17:1 `--color-primary` fill and takes a transparent border purely so
|
|
99
|
+
// the two states stay the same size — the fill still paints under it.
|
|
100
|
+
isOn() ? 'border-transparent bg-primary' : 'border-input bg-muted',
|
|
101
|
+
local.class,
|
|
74
102
|
)}
|
|
75
103
|
>
|
|
76
104
|
<span
|
|
@@ -79,7 +107,10 @@ export function Switch(props: SwitchProps) {
|
|
|
79
107
|
// On the `primary` track use `primary-foreground` so the thumb stays
|
|
80
108
|
// visible in both themes (in dark mode `primary` is light, where a
|
|
81
109
|
// hard-coded white thumb vanished). Off-track stays white over `muted`.
|
|
82
|
-
|
|
110
|
+
// The track is `border-box` with a 1px border, so the thumb travels
|
|
111
|
+
// inside a 34px box: 2px in from each inner edge keeps both end
|
|
112
|
+
// positions 3px from the track's outer edge.
|
|
113
|
+
isOn() ? 'translate-x-4 bg-primary-foreground' : 'translate-x-0.5 bg-white',
|
|
83
114
|
)}
|
|
84
115
|
/>
|
|
85
116
|
</button>
|
package/src/utils/cn.ts
CHANGED
|
@@ -2,19 +2,25 @@ import { clsx, type ClassValue } from 'clsx';
|
|
|
2
2
|
import { extendTailwindMerge } from 'tailwind-merge';
|
|
3
3
|
|
|
4
4
|
// The kit defines custom font-size utilities via @theme tokens in theme.css:
|
|
5
|
-
// text-caption / text-meta / text-body / text-title.
|
|
6
|
-
// to know these are font sizes, so by default it
|
|
7
|
-
// text COLORS and drops a real color
|
|
5
|
+
// text-micro / text-caption / text-meta / text-compact / text-body / text-title.
|
|
6
|
+
// tailwind-merge has no way to know these are font sizes, so by default it
|
|
7
|
+
// buckets `text-body` with text COLORS and drops a real color
|
|
8
|
+
// (`text-transparent`, `text-foreground`, …)
|
|
8
9
|
// whenever both appear in the same cn() call — which silently broke TextShimmer
|
|
9
10
|
// inside the web components (the element adds `text-body`, dropping
|
|
10
11
|
// `text-transparent`, so the gradient stayed hidden behind opaque text).
|
|
11
12
|
//
|
|
12
13
|
// Register them in the `font-size` group so they conflict only with other font
|
|
13
|
-
// sizes (text-xs/sm/base/lg/…) and never with text colors.
|
|
14
|
+
// sizes (text-xs/sm/base/lg/…) and never with text colors. Since theme.css now
|
|
15
|
+
// re-points Tailwind's own scale at the same tokens (text-xs ≡ text-meta,
|
|
16
|
+
// text-sm ≡ text-body, text-base ≡ text-title), the semantic name and its
|
|
17
|
+
// Tailwind alias are literally the same size — so they MUST land in one group
|
|
18
|
+
// or `cn('text-sm', 'text-body')` would emit two classes for one declaration.
|
|
19
|
+
// `text-lg` needs no entry: it is already a stock Tailwind font size.
|
|
14
20
|
const twMerge = extendTailwindMerge({
|
|
15
21
|
extend: {
|
|
16
22
|
classGroups: {
|
|
17
|
-
'font-size': [{ text: ['caption', 'meta', 'body', 'title'] }],
|
|
23
|
+
'font-size': [{ text: ['micro', 'caption', 'meta', 'compact', 'body', 'title'] }],
|
|
18
24
|
},
|
|
19
25
|
},
|
|
20
26
|
});
|
package/theme.css
CHANGED
|
@@ -74,8 +74,68 @@
|
|
|
74
74
|
added at the bottom. */
|
|
75
75
|
--color-hover: var(--kai-color-hover, color-mix(in oklab, var(--color-muted) 60%, var(--color-background)));
|
|
76
76
|
--color-selected: var(--kai-color-selected, color-mix(in oklab, var(--color-primary) 8%, var(--color-background)));
|
|
77
|
+
/* UNREAD indicator — a dedicated token (owner ruling, 2026-08-26), not a
|
|
78
|
+
reuse of --color-destructive: unread is notification semantics, not
|
|
79
|
+
error, so a consumer re-theming "danger" red must not also move every
|
|
80
|
+
unread dot. Used identically by all three places an unread conversation
|
|
81
|
+
surfaces: the list-row trailing dot (ConversationPanel), the header
|
|
82
|
+
toggle's badge (ChatThread), and Dock's own closed-launcher badge.
|
|
83
|
+
A vivid "messenger red" (Tailwind red-500 territory, the Intercom/iOS
|
|
84
|
+
badge convention) rather than the darker --color-destructive hue: the
|
|
85
|
+
first pass at this token picked a color that read as legible-on-paper
|
|
86
|
+
but visually flat/dark next to the launcher's near-black ring. This one
|
|
87
|
+
value is used in BOTH light and dark (no .dark override below) because
|
|
88
|
+
it independently clears 3:1 (WCAG 1.4.11, non-text) against every real
|
|
89
|
+
surface an unread dot sits on in EITHER mode: light row/card background
|
|
90
|
+
(white) 3.78:1, light Dock-launcher ring (--color-primary, near-black)
|
|
91
|
+
4.68:1, dark row/card background (near-black) 4.73–4.35:1, dark
|
|
92
|
+
Dock-launcher ring (--color-primary, near-white) 3.62:1. (Measured via
|
|
93
|
+
the WCAG relative-luminance formula against this file's own light/dark
|
|
94
|
+
--color-background/-card/-primary values — see
|
|
95
|
+
.superpowers/sdd/2026-08-26-conversations/rework-report.md Round 4 for
|
|
96
|
+
the worked numbers.) Override via --kai-color-unread; a construct author
|
|
97
|
+
can also set it per-widget through `theme.unreadColor` (construct
|
|
98
|
+
vocabulary — see agent-tooling/construct/schema.ts). */
|
|
99
|
+
--color-unread: var(--kai-color-unread, hsl(0 84% 60%));
|
|
100
|
+
/* MARKED text — the fill behind a keyword the app has flagged (the composer's
|
|
101
|
+
`highlights` rules, decorated through the CSS Custom Highlight API). This is
|
|
102
|
+
`<mark>`, not a selection and not a status: a warm yellow, the convention
|
|
103
|
+
every browser already ships for <mark> and every reader already reads as
|
|
104
|
+
"someone marked this". Deliberately NOT derived from --color-primary — that
|
|
105
|
+
token is a NEUTRAL near-black in light and near-white in dark, so any tint of
|
|
106
|
+
it is grey, which is why the highlight used to be invisible. Also not blue
|
|
107
|
+
(the browser's own ::selection is blue — a highlight must not read as a
|
|
108
|
+
selection), not red (error) and not the skill/agent pill hues (blue/violet).
|
|
109
|
+
OPAQUE, so it composites predictably over whichever field it lands on.
|
|
110
|
+
Override via --kai-color-highlight. Light: near-black text over it is 15.6:1;
|
|
111
|
+
dark: near-white text over it is 6.7:1 — both well clear of WCAG AA. */
|
|
112
|
+
--color-highlight: var(--kai-color-highlight, hsl(45 96% 78%));
|
|
113
|
+
/* SELECTED text — the kit's own ::selection fill, applied inside every kai-*
|
|
114
|
+
shadow root (see src/elements/styles.css for the scoping rationale). The kit
|
|
115
|
+
owns this because leaving it to the UA meant the browser's blue outranked the
|
|
116
|
+
kit's own deliberate emphasis: a marked keyword was LESS prominent than an
|
|
117
|
+
incidental drag-select. Stays in the cool/blue family the UA trained everyone
|
|
118
|
+
to read as "selected", so it can never be confused with the warm --color-highlight
|
|
119
|
+
mark above; OPAQUE, so a selection over a mark composites cleanly (selection
|
|
120
|
+
paints on top of a custom highlight) instead of muddying into a third colour.
|
|
121
|
+
The FOREGROUND is set too, and that is not cosmetic: without it the selected
|
|
122
|
+
glyph keeps its own colour, and the kit's coloured text — muted text (3.8:1),
|
|
123
|
+
the skill/agent pill hues (3.6:1 / 4.0:1), inline code (4.7:1) — lands at or
|
|
124
|
+
under WCAG AA on this fill. Forcing the foreground token makes the guarantee
|
|
125
|
+
hold for every glyph regardless of what colour it had. Light 13.99:1,
|
|
126
|
+
dark 8.87:1. Override via --kai-color-selection(-foreground). */
|
|
127
|
+
--color-selection: var(--kai-color-selection, hsl(213 94% 87%));
|
|
128
|
+
--color-selection-foreground: var(--kai-color-selection-foreground, hsl(240 10% 3.9%));
|
|
77
129
|
--color-border: var(--kai-color-border, hsl(240 5.9% 90%));
|
|
78
|
-
|
|
130
|
+
/* CONTROL boundary — the visible edge of an interactive form control (text
|
|
131
|
+
input, textarea, select, the radio/checkbox ring). WCAG 2.1 SC 1.4.11
|
|
132
|
+
Non-text Contrast requires >=3:1 against the adjacent surface for exactly
|
|
133
|
+
this: a control whose boundary you cannot see is not perceivable. That is
|
|
134
|
+
why this is NOT --color-border: a divider or a card outline is decorative
|
|
135
|
+
and may sit below 3:1, a control's edge may not. Do NOT use border-input
|
|
136
|
+
for list wrappers or panel outlines — use border-border there.
|
|
137
|
+
Contrast is the requirement; a thicker border does not satisfy 1.4.11. */
|
|
138
|
+
--color-input: var(--kai-color-input, hsl(240 5% 53%));
|
|
79
139
|
/* Focus ring — a deliberate blue (not a neutral) so keyboard focus is always
|
|
80
140
|
obvious and on-brand in both modes. Light uses a strong blue; dark uses a
|
|
81
141
|
brighter one for contrast on dark surfaces. Both clear WCAG 2.1 non-text
|
|
@@ -108,18 +168,42 @@
|
|
|
108
168
|
--radius-lg: var(--radius);
|
|
109
169
|
--radius-xl: calc(var(--radius) + 4px);
|
|
110
170
|
|
|
111
|
-
/* Typography scale —
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
171
|
+
/* Typography scale — ONE scale for the whole kit. Each semantic name
|
|
172
|
+
(text-micro / text-caption / text-meta / text-compact / text-body /
|
|
173
|
+
text-title / text-lg)
|
|
174
|
+
resolves through a namespaced --kai-text-* token, so overriding that token
|
|
175
|
+
on :root restyles the kit globally — it pierces the Shadow DOM via the
|
|
115
176
|
var() fallback, exactly like the --kai-color-* tokens. The bare --text-*
|
|
116
|
-
names stay internal so a host's own --text-* can't collide.
|
|
117
|
-
|
|
118
|
-
the
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
177
|
+
names stay internal so a host's own --text-* can't collide.
|
|
178
|
+
|
|
179
|
+
Tailwind's OWN scale is re-pointed at the same tokens: in Tailwind v4 the
|
|
180
|
+
`text-xs` / `text-sm` / `text-base` / `text-lg` utilities are generated FROM
|
|
181
|
+
--text-xs/sm/base/lg in this very @theme block, so aliasing them makes
|
|
182
|
+
text-xs ≡ text-meta, text-sm ≡ text-body, text-base ≡ text-title and
|
|
183
|
+
text-lg ≡ text-lg. That matters because the vast majority of the kit's call
|
|
184
|
+
sites spell the Tailwind name; without the alias the --kai-text-* knobs
|
|
185
|
+
reached ~12% of them and the "globally" promise in the theming guide was
|
|
186
|
+
false. Defaults are byte-identical to stock Tailwind, so nothing moves
|
|
187
|
+
until a consumer actually sets a --kai-text-* token — including in a
|
|
188
|
+
consumer's own Tailwind build that imports this file, which is the point:
|
|
189
|
+
one scale everywhere, kit and app moving together.
|
|
190
|
+
|
|
191
|
+
Line-heights are UNITLESS RATIOS (Tailwind's own), never fixed rem. A fixed
|
|
192
|
+
leading does not scale: --kai-text-body: 1.25rem under the old 1.45rem
|
|
193
|
+
leading gave 20px text on 23.2px leading — near solid-set — which made the
|
|
194
|
+
themable token unsafe to actually theme.
|
|
195
|
+
(Message/markdown reading size also scales with the `proseSize` prop; these
|
|
196
|
+
tokens cover the fixed chrome & controls.) */
|
|
197
|
+
--text-micro: var(--kai-text-micro, 0.625rem); --text-micro--line-height: calc(0.875 / 0.625); /* 10px — badges, pills, uppercase eyebrows */
|
|
198
|
+
--text-caption: var(--kai-text-caption, 0.6875rem); --text-caption--line-height: calc(1 / 0.6875); /* 11px — micro labels, sub-counts, xs code */
|
|
199
|
+
--text-meta: var(--kai-text-meta, 0.75rem); --text-meta--line-height: calc(1 / 0.75); /* 12px — controls, toggles, switchers, captions */
|
|
200
|
+
--text-xs: var(--kai-text-meta, 0.75rem); --text-xs--line-height: calc(1 / 0.75); /* ≡ text-meta */
|
|
201
|
+
--text-compact: var(--kai-text-compact, 0.8125rem); --text-compact--line-height: calc(1.21875 / 0.8125); /* 13px — dense chrome & code: a notch below body */
|
|
202
|
+
--text-body: var(--kai-text-body, 0.875rem); --text-body--line-height: calc(1.25 / 0.875); /* 14px — primary reading text */
|
|
203
|
+
--text-sm: var(--kai-text-body, 0.875rem); --text-sm--line-height: calc(1.25 / 0.875); /* ≡ text-body */
|
|
204
|
+
--text-title: var(--kai-text-title, 1rem); --text-title--line-height: calc(1.5 / 1); /* 16px — emphasis / headers */
|
|
205
|
+
--text-base: var(--kai-text-title, 1rem); --text-base--line-height: calc(1.5 / 1); /* ≡ text-title */
|
|
206
|
+
--text-lg: var(--kai-text-lg, 1.125rem); --text-lg--line-height: calc(1.75 / 1.125); /* 18px — section headings, lg prose */
|
|
123
207
|
|
|
124
208
|
@keyframes typing { 0%,100% { transform: translateY(0); opacity: .5 } 50% { transform: translateY(-2px); opacity: 1 } }
|
|
125
209
|
@keyframes loading-dots { 0%,100% { opacity: 0 } 50% { opacity: 1 } }
|
|
@@ -180,8 +264,23 @@
|
|
|
180
264
|
--color-destructive-soft: var(--kai-color-destructive-soft, color-mix(in oklab, var(--color-destructive) 14%, var(--color-background)));
|
|
181
265
|
--color-hover: var(--kai-color-hover, color-mix(in oklab, var(--color-muted) 60%, var(--color-background)));
|
|
182
266
|
--color-selected: var(--kai-color-selected, color-mix(in oklab, var(--color-primary) 8%, var(--color-background)));
|
|
267
|
+
/* Unread indicator — dark. Same value as light (see the light block for the
|
|
268
|
+
full contrast note): one vivid red independently clears 3:1 against both
|
|
269
|
+
modes' real surfaces, so there is nothing to flip here. Still redeclared
|
|
270
|
+
for override symmetry with every other semantic color in this file. */
|
|
271
|
+
--color-unread: var(--kai-color-unread, hsl(0 84% 60%));
|
|
272
|
+
/* Marked text — dark. A deep warm amber rather than the light yellow: a pale
|
|
273
|
+
highlighter on a near-black field would blow out and force the text to flip
|
|
274
|
+
colour. See the light block for the full note. */
|
|
275
|
+
--color-highlight: var(--kai-color-highlight, hsl(40 75% 26%));
|
|
276
|
+
/* Selected text — dark. A deep blue rather than the light one, for the same
|
|
277
|
+
reason the mark darkens. See the light block for the full note. */
|
|
278
|
+
--color-selection: var(--kai-color-selection, hsl(217 60% 32%));
|
|
279
|
+
--color-selection-foreground: var(--kai-color-selection-foreground, hsl(0 0% 98%));
|
|
183
280
|
--color-border: var(--kai-color-border, hsl(45 4% 17%));
|
|
184
|
-
|
|
281
|
+
/* Control boundary — see the light block. Lifted to clear 3:1 (SC 1.4.11)
|
|
282
|
+
against the dark background and card surfaces. */
|
|
283
|
+
--color-input: var(--kai-color-input, hsl(45 4% 48%));
|
|
185
284
|
--color-ring: var(--kai-color-ring, hsl(217 91% 68%));
|
|
186
285
|
--color-scrollbar-thumb: var(--kai-color-scrollbar-thumb, hsl(45 3% 30%));
|
|
187
286
|
--color-scrollbar-thumb-hover: var(--kai-color-scrollbar-thumb-hover, hsl(45 3% 42%));
|