@elabs-ai/components-ai 4.2.0 → 5.0.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 +1 -1
- package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
- package/dist/_code-block-theme.js +12 -0
- package/dist/_code-block-theme.js.map +1 -0
- package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
- package/dist/_flow-boundary.js.map +1 -0
- package/dist/_flow-lazy.js +8 -0
- package/dist/_flow-lazy.js.map +1 -0
- package/dist/_lazy-boundary-conformance.js +2 -0
- package/dist/_lazy-boundary-conformance.js.map +1 -0
- package/dist/_lazy-cjk.js +13 -0
- package/dist/_lazy-cjk.js.map +1 -0
- package/dist/_lazy-engine-boundary.js +9 -0
- package/dist/_lazy-engine-boundary.js.map +1 -0
- package/dist/_lazy-math.js +13 -0
- package/dist/_lazy-math.js.map +1 -0
- package/dist/_lazy-mermaid.js +12 -0
- package/dist/_lazy-mermaid.js.map +1 -0
- package/dist/_mermaid-error-panel.js +9 -0
- package/dist/_mermaid-error-panel.js.map +1 -0
- package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
- package/dist/_persona-rive.js.map +1 -0
- package/dist/_streamdown-i18n.js +21 -0
- package/dist/_streamdown-i18n.js.map +1 -0
- package/dist/_streamdown-safety.js +10 -0
- package/dist/_streamdown-safety.js.map +1 -0
- package/dist/_theme-scope-store.js +13 -0
- package/dist/_theme-scope-store.js.map +1 -0
- package/dist/a2ui/a2ui-surface.js +17 -0
- package/dist/a2ui/a2ui-surface.js.map +1 -0
- package/dist/a2ui/core/catalog.generated.js +10 -0
- package/dist/a2ui/core/catalog.generated.js.map +1 -0
- package/dist/a2ui/core/complete-json.js +10 -0
- package/dist/a2ui/core/complete-json.js.map +1 -0
- package/dist/a2ui/core/index.js +37 -0
- package/dist/a2ui/core/index.js.map +1 -0
- package/dist/a2ui/core/schema.js +11 -0
- package/dist/a2ui/core/schema.js.map +1 -0
- package/dist/a2ui/core/spec.js +10 -0
- package/dist/a2ui/core/spec.js.map +1 -0
- package/dist/a2ui/core/validate.js +16 -0
- package/dist/a2ui/core/validate.js.map +1 -0
- package/dist/a2ui/index.js +54 -0
- package/dist/a2ui/index.js.map +1 -0
- package/dist/a2ui/ui-catalog.js +18 -0
- package/dist/a2ui/ui-catalog.js.map +1 -0
- package/dist/agent-event.js +10 -0
- package/dist/agent-event.js.map +1 -0
- package/dist/agent-timeline.js +11 -0
- package/dist/agent-timeline.js.map +1 -0
- package/dist/agent.js +25 -0
- package/dist/agent.js.map +1 -0
- package/dist/artifact.js +23 -0
- package/dist/artifact.js.map +1 -0
- package/dist/asset-preview.js +22 -0
- package/dist/asset-preview.js.map +1 -0
- package/dist/attachments.js +33 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio-player.js +30 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-visualizer.js +9 -0
- package/dist/audio-visualizer.js.map +1 -0
- package/dist/canvas.js +10 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chain-of-thought.js +22 -0
- package/dist/chain-of-thought.js.map +1 -0
- package/dist/chat-greeting.js +8 -0
- package/dist/chat-greeting.js.map +1 -0
- package/dist/chat-shell.js +9 -0
- package/dist/chat-shell.js.map +1 -0
- package/dist/checkpoint.js +13 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/chunk-2XOIG5TG.js +72 -0
- package/dist/chunk-2XOIG5TG.js.map +1 -0
- package/dist/chunk-34CQ2VHM.js +172 -0
- package/dist/chunk-34CQ2VHM.js.map +1 -0
- package/dist/chunk-3TDHPXZZ.js +60 -0
- package/dist/chunk-3TDHPXZZ.js.map +1 -0
- package/dist/chunk-3XNPMDZY.js +478 -0
- package/dist/chunk-3XNPMDZY.js.map +1 -0
- package/dist/chunk-3YPT3V7L.js +56 -0
- package/dist/chunk-3YPT3V7L.js.map +1 -0
- package/dist/chunk-475UEU2Q.js +87 -0
- package/dist/chunk-475UEU2Q.js.map +1 -0
- package/dist/chunk-4CBTII2I.js +95 -0
- package/dist/chunk-4CBTII2I.js.map +1 -0
- package/dist/chunk-4XRNIZMG.js +22 -0
- package/dist/chunk-4XRNIZMG.js.map +1 -0
- package/dist/chunk-5NNWUKK5.js +2 -0
- package/dist/chunk-5NNWUKK5.js.map +1 -0
- package/dist/chunk-5UOMU27Z.js +164 -0
- package/dist/chunk-5UOMU27Z.js.map +1 -0
- package/dist/chunk-5VGYZ62I.js +264 -0
- package/dist/chunk-5VGYZ62I.js.map +1 -0
- package/dist/chunk-6IQPH2OS.js +21 -0
- package/dist/chunk-6IQPH2OS.js.map +1 -0
- package/dist/chunk-6PB2MXCI.js +482 -0
- package/dist/chunk-6PB2MXCI.js.map +1 -0
- package/dist/chunk-7FD5CZEU.js +215 -0
- package/dist/chunk-7FD5CZEU.js.map +1 -0
- package/dist/chunk-7RL6KZDF.js +266 -0
- package/dist/chunk-7RL6KZDF.js.map +1 -0
- package/dist/chunk-ADOYAGGK.js +482 -0
- package/dist/chunk-ADOYAGGK.js.map +1 -0
- package/dist/chunk-AXMNJMMH.js +984 -0
- package/dist/chunk-AXMNJMMH.js.map +1 -0
- package/dist/chunk-BA3AEMOU.js +21 -0
- package/dist/chunk-BA3AEMOU.js.map +1 -0
- package/dist/chunk-BBHFOYML.js +336 -0
- package/dist/chunk-BBHFOYML.js.map +1 -0
- package/dist/chunk-BHUVISIY.js +221 -0
- package/dist/chunk-BHUVISIY.js.map +1 -0
- package/dist/chunk-BM2LWERA.js +100 -0
- package/dist/chunk-BM2LWERA.js.map +1 -0
- package/dist/chunk-CMOYXNPJ.js +211 -0
- package/dist/chunk-CMOYXNPJ.js.map +1 -0
- package/dist/chunk-CZP3RHC4.js +74 -0
- package/dist/chunk-CZP3RHC4.js.map +1 -0
- package/dist/chunk-DDG5JHQT.js +112 -0
- package/dist/chunk-DDG5JHQT.js.map +1 -0
- package/dist/chunk-DJODXO7I.js +38 -0
- package/dist/chunk-DJODXO7I.js.map +1 -0
- package/dist/chunk-DWJDJLPU.js +192 -0
- package/dist/chunk-DWJDJLPU.js.map +1 -0
- package/dist/chunk-DZW6LM22.js +45 -0
- package/dist/chunk-DZW6LM22.js.map +1 -0
- package/dist/chunk-ELGCZLZF.js +305 -0
- package/dist/chunk-ELGCZLZF.js.map +1 -0
- package/dist/chunk-EV3U6ZZB.js +209 -0
- package/dist/chunk-EV3U6ZZB.js.map +1 -0
- package/dist/chunk-EY7LVUKQ.js +87 -0
- package/dist/chunk-EY7LVUKQ.js.map +1 -0
- package/dist/chunk-F4C74F7M.js +230 -0
- package/dist/chunk-F4C74F7M.js.map +1 -0
- package/dist/chunk-FERYDFZA.js +108 -0
- package/dist/chunk-FERYDFZA.js.map +1 -0
- package/dist/chunk-FUIYG2DS.js +166 -0
- package/dist/chunk-FUIYG2DS.js.map +1 -0
- package/dist/chunk-GKYZT3LA.js +136 -0
- package/dist/chunk-GKYZT3LA.js.map +1 -0
- package/dist/chunk-H42LRUQ4.js +102 -0
- package/dist/chunk-H42LRUQ4.js.map +1 -0
- package/dist/chunk-HVXM2G5F.js +222 -0
- package/dist/chunk-HVXM2G5F.js.map +1 -0
- package/dist/chunk-I4RDAGVY.js +53 -0
- package/dist/chunk-I4RDAGVY.js.map +1 -0
- package/dist/chunk-IQJG4IDX.js +208 -0
- package/dist/chunk-IQJG4IDX.js.map +1 -0
- package/dist/chunk-IX2PXG4Y.js +144 -0
- package/dist/chunk-IX2PXG4Y.js.map +1 -0
- package/dist/chunk-IXICSOOP.js +287 -0
- package/dist/chunk-IXICSOOP.js.map +1 -0
- package/dist/chunk-JY5HSN4D.js +84 -0
- package/dist/chunk-JY5HSN4D.js.map +1 -0
- package/dist/chunk-LMNSBOBG.js +19 -0
- package/dist/chunk-LMNSBOBG.js.map +1 -0
- package/dist/chunk-MBI7D6IL.js +42 -0
- package/dist/chunk-MBI7D6IL.js.map +1 -0
- package/dist/chunk-MLFMYCPH.js +192 -0
- package/dist/chunk-MLFMYCPH.js.map +1 -0
- package/dist/chunk-MOK66734.js +33 -0
- package/dist/chunk-MOK66734.js.map +1 -0
- package/dist/chunk-MXTAWPYM.js +196 -0
- package/dist/chunk-MXTAWPYM.js.map +1 -0
- package/dist/chunk-NCL2UZKI.js +96 -0
- package/dist/chunk-NCL2UZKI.js.map +1 -0
- package/dist/chunk-NET4YLRY.js +107 -0
- package/dist/chunk-NET4YLRY.js.map +1 -0
- package/dist/chunk-NIG67CFM.js +329 -0
- package/dist/chunk-NIG67CFM.js.map +1 -0
- package/dist/chunk-NMVNNO3L.js +10 -0
- package/dist/chunk-NMVNNO3L.js.map +1 -0
- package/dist/chunk-NP7VID4K.js +2 -0
- package/dist/chunk-NP7VID4K.js.map +1 -0
- package/dist/chunk-NPUCMAG2.js +382 -0
- package/dist/chunk-NPUCMAG2.js.map +1 -0
- package/dist/chunk-NSCLRHES.js +102 -0
- package/dist/chunk-NSCLRHES.js.map +1 -0
- package/dist/chunk-OEUXHVFW.js +86 -0
- package/dist/chunk-OEUXHVFW.js.map +1 -0
- package/dist/chunk-ONBHFIMZ.js +137 -0
- package/dist/chunk-ONBHFIMZ.js.map +1 -0
- package/dist/chunk-OPH2HHL2.js +228 -0
- package/dist/chunk-OPH2HHL2.js.map +1 -0
- package/dist/chunk-OQERHIQP.js +287 -0
- package/dist/chunk-OQERHIQP.js.map +1 -0
- package/dist/chunk-P6YJAZFG.js +103 -0
- package/dist/chunk-P6YJAZFG.js.map +1 -0
- package/dist/chunk-PG7ERDOD.js +65 -0
- package/dist/chunk-PG7ERDOD.js.map +1 -0
- package/dist/chunk-PIB5JBMA.js +90 -0
- package/dist/chunk-PIB5JBMA.js.map +1 -0
- package/dist/chunk-PQ5BPIWQ.js +22 -0
- package/dist/chunk-PQ5BPIWQ.js.map +1 -0
- package/dist/chunk-PQBF6ZIQ.js +55 -0
- package/dist/chunk-PQBF6ZIQ.js.map +1 -0
- package/dist/chunk-PTWCTSZL.js +173 -0
- package/dist/chunk-PTWCTSZL.js.map +1 -0
- package/dist/chunk-QJUNPVOS.js +101 -0
- package/dist/chunk-QJUNPVOS.js.map +1 -0
- package/dist/chunk-QUDZOTRV.js +43 -0
- package/dist/chunk-QUDZOTRV.js.map +1 -0
- package/dist/chunk-RB7G65JC.js +213 -0
- package/dist/chunk-RB7G65JC.js.map +1 -0
- package/dist/chunk-RPHAOR7P.js +45 -0
- package/dist/chunk-RPHAOR7P.js.map +1 -0
- package/dist/chunk-RTCSH2AN.js +331 -0
- package/dist/chunk-RTCSH2AN.js.map +1 -0
- package/dist/chunk-S3ISPW3A.js +28 -0
- package/dist/chunk-S3ISPW3A.js.map +1 -0
- package/dist/chunk-SEVN656N.js +36 -0
- package/dist/chunk-SEVN656N.js.map +1 -0
- package/dist/chunk-SKVG672B.js +64 -0
- package/dist/chunk-SKVG672B.js.map +1 -0
- package/dist/chunk-SPQIU7GA.js +236 -0
- package/dist/chunk-SPQIU7GA.js.map +1 -0
- package/dist/chunk-SSGJ6MEM.js +1076 -0
- package/dist/chunk-SSGJ6MEM.js.map +1 -0
- package/dist/chunk-SSKZ2AED.js +118 -0
- package/dist/chunk-SSKZ2AED.js.map +1 -0
- package/dist/chunk-T56JFGXI.js +561 -0
- package/dist/chunk-T56JFGXI.js.map +1 -0
- package/dist/chunk-TCB6O74A.js +137 -0
- package/dist/chunk-TCB6O74A.js.map +1 -0
- package/dist/chunk-TTEYPO6X.js +57 -0
- package/dist/chunk-TTEYPO6X.js.map +1 -0
- package/dist/chunk-TWPHAJNM.js +462 -0
- package/dist/chunk-TWPHAJNM.js.map +1 -0
- package/dist/chunk-TXJJLP4O.js +336 -0
- package/dist/chunk-TXJJLP4O.js.map +1 -0
- package/dist/chunk-TZ6TB7VM.js +245 -0
- package/dist/chunk-TZ6TB7VM.js.map +1 -0
- package/dist/chunk-UBLKJJ7K.js +278 -0
- package/dist/chunk-UBLKJJ7K.js.map +1 -0
- package/dist/chunk-UCJ6IUFS.js +52 -0
- package/dist/chunk-UCJ6IUFS.js.map +1 -0
- package/dist/chunk-UCKFJ3QF.js +197 -0
- package/dist/chunk-UCKFJ3QF.js.map +1 -0
- package/dist/chunk-UPXSLILN.js +20 -0
- package/dist/chunk-UPXSLILN.js.map +1 -0
- package/dist/chunk-V4DORGWJ.js +92 -0
- package/dist/chunk-V4DORGWJ.js.map +1 -0
- package/dist/chunk-V6E5DYZD.js +80 -0
- package/dist/chunk-V6E5DYZD.js.map +1 -0
- package/dist/chunk-VLLYKF3P.js +96 -0
- package/dist/chunk-VLLYKF3P.js.map +1 -0
- package/dist/chunk-VLNBTOH6.js +43 -0
- package/dist/chunk-VLNBTOH6.js.map +1 -0
- package/dist/chunk-WMDSMSPL.js +303 -0
- package/dist/chunk-WMDSMSPL.js.map +1 -0
- package/dist/chunk-WU5BLGTF.js +149 -0
- package/dist/chunk-WU5BLGTF.js.map +1 -0
- package/dist/chunk-WYTZVSGK.js +245 -0
- package/dist/chunk-WYTZVSGK.js.map +1 -0
- package/dist/chunk-X47QMM6K.js +187 -0
- package/dist/chunk-X47QMM6K.js.map +1 -0
- package/dist/chunk-XNBKO32Y.js +174 -0
- package/dist/chunk-XNBKO32Y.js.map +1 -0
- package/dist/chunk-YHUSW7D2.js +40 -0
- package/dist/chunk-YHUSW7D2.js.map +1 -0
- package/dist/chunk-YSKD7UXX.js +126 -0
- package/dist/chunk-YSKD7UXX.js.map +1 -0
- package/dist/chunk-YYRDGWOU.js +72 -0
- package/dist/chunk-YYRDGWOU.js.map +1 -0
- package/dist/chunk-ZIGZMWM7.js +19 -0
- package/dist/chunk-ZIGZMWM7.js.map +1 -0
- package/dist/chunk-ZKRTUKNP.js +40 -0
- package/dist/chunk-ZKRTUKNP.js.map +1 -0
- package/dist/code-block.js +38 -0
- package/dist/code-block.js.map +1 -0
- package/dist/commit.js +51 -0
- package/dist/commit.js.map +1 -0
- package/dist/composer.js +15 -0
- package/dist/composer.js.map +1 -0
- package/dist/confirmation.js +53 -0
- package/dist/confirmation.js.map +1 -0
- package/dist/connection.js +8 -0
- package/dist/connection.js.map +1 -0
- package/dist/context-panel.js +25 -0
- package/dist/context-panel.js.map +1 -0
- package/dist/controls.js +10 -0
- package/dist/controls.js.map +1 -0
- package/dist/conversation.js +19 -0
- package/dist/conversation.js.map +1 -0
- package/dist/diff-view.js +15 -0
- package/dist/diff-view.js.map +1 -0
- package/dist/edge.js +10 -0
- package/dist/edge.js.map +1 -0
- package/dist/environment-variables.js +29 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/file-tree.js +23 -0
- package/dist/file-tree.js.map +1 -0
- package/dist/gallery.js +9 -0
- package/dist/gallery.js.map +1 -0
- package/dist/grouped-parts.js +21 -0
- package/dist/grouped-parts.js.map +1 -0
- package/dist/image.js +9 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +366 -98
- package/dist/index.js +672 -13577
- package/dist/index.js.map +1 -1
- package/dist/inline-citation.js +39 -0
- package/dist/inline-citation.js.map +1 -0
- package/dist/jsx-preview.js +17 -0
- package/dist/jsx-preview.js.map +1 -0
- package/dist/markdown-view.js +17 -0
- package/dist/markdown-view.js.map +1 -0
- package/dist/message-compare.js +12 -0
- package/dist/message-compare.js.map +1 -0
- package/dist/message-edit.js +19 -0
- package/dist/message-edit.js.map +1 -0
- package/dist/message-feedback.js +11 -0
- package/dist/message-feedback.js.map +1 -0
- package/dist/message-form-spec.js +38 -0
- package/dist/message-form-spec.js.map +1 -0
- package/dist/message-form.js +26 -0
- package/dist/message-form.js.map +1 -0
- package/dist/message-table-spec.js +26 -0
- package/dist/message-table-spec.js.map +1 -0
- package/dist/message-table.js +12 -0
- package/dist/message-table.js.map +1 -0
- package/dist/message.js +51 -0
- package/dist/message.js.map +1 -0
- package/dist/mic-selector.js +27 -0
- package/dist/mic-selector.js.map +1 -0
- package/dist/model-provider-logo.js +13 -0
- package/dist/model-provider-logo.js.map +1 -0
- package/dist/motion-config.js +9 -0
- package/dist/motion-config.js.map +1 -0
- package/dist/node.js +22 -0
- package/dist/node.js.map +1 -0
- package/dist/open-in-chat.js +31 -0
- package/dist/open-in-chat.js.map +1 -0
- package/dist/package-info.js +25 -0
- package/dist/package-info.js.map +1 -0
- package/dist/panel.js +10 -0
- package/dist/panel.js.map +1 -0
- package/dist/part-groups.js +18 -0
- package/dist/part-groups.js.map +1 -0
- package/dist/permission-mode-select.js +9 -0
- package/dist/permission-mode-select.js.map +1 -0
- package/dist/persona-sources.js +8 -0
- package/dist/persona-sources.js.map +1 -0
- package/dist/persona.js +14 -0
- package/dist/persona.js.map +1 -0
- package/dist/plan.js +32 -0
- package/dist/plan.js.map +1 -0
- package/dist/prompt-input-effort.js +9 -0
- package/dist/prompt-input-effort.js.map +1 -0
- package/dist/prompt-input-mode.js +10 -0
- package/dist/prompt-input-mode.js.map +1 -0
- package/dist/prompt-input-slash.js +12 -0
- package/dist/prompt-input-slash.js.map +1 -0
- package/dist/prompt-input.js +89 -0
- package/dist/prompt-input.js.map +1 -0
- package/dist/queue.js +37 -0
- package/dist/queue.js.map +1 -0
- package/dist/reasoning.js +23 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/sandbox.js +28 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema-display.js +33 -0
- package/dist/schema-display.js.map +1 -0
- package/dist/selection-toolbar.js +11 -0
- package/dist/selection-toolbar.js.map +1 -0
- package/dist/session-header.js +9 -0
- package/dist/session-header.js.map +1 -0
- package/dist/session-status-bar.js +9 -0
- package/dist/session-status-bar.js.map +1 -0
- package/dist/shimmer.js +9 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/snippet.js +17 -0
- package/dist/snippet.js.map +1 -0
- package/dist/sources.js +17 -0
- package/dist/sources.js.map +1 -0
- package/dist/speech-input.js +9 -0
- package/dist/speech-input.js.map +1 -0
- package/dist/stack-trace.js +27 -0
- package/dist/stack-trace.js.map +1 -0
- package/dist/suggestion.js +16 -0
- package/dist/suggestion.js.map +1 -0
- package/dist/task.js +18 -0
- package/dist/task.js.map +1 -0
- package/dist/test-results.js +43 -0
- package/dist/test-results.js.map +1 -0
- package/dist/token-usage.js +27 -0
- package/dist/token-usage.js.map +1 -0
- package/dist/tool-result-card.js +9 -0
- package/dist/tool-result-card.js.map +1 -0
- package/dist/tool.js +27 -0
- package/dist/tool.js.map +1 -0
- package/dist/toolbar.js +10 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/transcription.js +11 -0
- package/dist/transcription.js.map +1 -0
- package/dist/turn-status.js +9 -0
- package/dist/turn-status.js.map +1 -0
- package/dist/use-audio-level.js +9 -0
- package/dist/use-audio-level.js.map +1 -0
- package/dist/voice-selector.js +47 -0
- package/dist/voice-selector.js.map +1 -0
- package/dist/web-preview.js +19 -0
- package/dist/web-preview.js.map +1 -0
- package/package.json +11 -9
- package/schemas/a2ui-surface.v1.schema.json +5425 -0
- package/src/_chat-shell-rail.tsx +3 -1
- package/src/_flow-boundary.tsx +2 -2
- package/src/_persona-rive.tsx +27 -41
- package/src/a2ui/a2ui-surface.stories.tsx +662 -0
- package/src/a2ui/a2ui-surface.test.tsx +201 -0
- package/src/a2ui/a2ui-surface.tsx +354 -0
- package/src/a2ui/catalog.source.json +772 -0
- package/src/a2ui/core/a2ui-core.test.ts +178 -0
- package/src/a2ui/core/catalog.generated.ts +1006 -0
- package/src/a2ui/core/complete-json.ts +81 -0
- package/src/a2ui/core/index.ts +10 -0
- package/src/a2ui/core/schema.ts +111 -0
- package/src/a2ui/core/spec.ts +148 -0
- package/src/a2ui/core/validate.ts +266 -0
- package/src/a2ui/index.ts +7 -0
- package/src/a2ui/ui-catalog.tsx +225 -0
- package/src/a2ui/wave1-chart-spec.test.ts +87 -0
- package/src/a2ui/wave2-chart-spec.test.ts +108 -0
- package/src/agentic-workspace.stories.tsx +6 -45
- package/src/chat-shell.test.tsx +40 -10
- package/src/chat-shell.tsx +90 -44
- package/src/chat.stories.tsx +47 -42
- package/src/composer.stories.tsx +4 -5
- package/src/composer.test.tsx +45 -0
- package/src/composer.tsx +104 -21
- package/src/context-panel.stories.tsx +1 -1
- package/src/context-panel.tsx +1 -1
- package/src/conversation.stories.tsx +1 -0
- package/src/conversation.test.tsx +16 -0
- package/src/conversation.tsx +16 -2
- package/src/diff-view.stories.tsx +79 -0
- package/src/diff-view.tsx +3 -2
- package/src/file-tree.tsx +24 -17
- package/src/gallery.stories.tsx +42 -0
- package/src/gallery.tsx +2 -2
- package/src/grouped-parts.stories.tsx +23 -0
- package/src/grouped-parts.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/message-compare.stories.tsx +40 -0
- package/src/message-compare.tsx +2 -1
- package/src/message-form.tsx +1 -1
- package/src/message-table.stories.tsx +24 -0
- package/src/message-table.tsx +1 -1
- package/src/package-info.tsx +12 -9
- package/src/reasoning.stories.tsx +27 -0
- package/src/reasoning.tsx +1 -1
- package/src/session-header.stories.tsx +35 -1
- package/src/session-header.tsx +1 -1
- package/src/sources.stories.tsx +30 -0
- package/src/sources.tsx +4 -1
- package/src/templates-ai-assistant.stories.tsx +151 -87
- package/src/tool.stories.tsx +39 -0
- package/src/tool.tsx +3 -3
- package/src/toolbar.tsx +0 -10
- package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
- package/dist/_persona-rive-JEG44YHX.js.map +0 -1
- /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
|
@@ -194,3 +194,82 @@ export const ChangeReviewComposition: Story = {
|
|
|
194
194
|
</div>
|
|
195
195
|
),
|
|
196
196
|
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Focus indicator on expandable line (#313): Tab to the "show more" button
|
|
200
|
+
* and assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
|
|
201
|
+
* `--ring-contour`) that `focus-ring` adds — not just `boxShadow !== "none"`,
|
|
202
|
+
* which the legacy `ring-2` pattern also produces.
|
|
203
|
+
*
|
|
204
|
+
* The prior version passed a `hiddenLineCount` prop that `DiffViewProps`
|
|
205
|
+
* (`diff-view.tsx:77`) does not define — React spread it onto the DOM as a
|
|
206
|
+
* meaningless `hiddenlinecount` attribute and warned about it, and the "show
|
|
207
|
+
* more" row NEVER rendered (a real diff never mentions "50" anywhere).
|
|
208
|
+
* `getByRole("button", …)` then always threw before the focus check ran.
|
|
209
|
+
* `CollapsedRow` (`diff-view.tsx:289`) only appears when `collapseDiffRows`
|
|
210
|
+
* (`packages/ui/src/lib/diff-rows.ts`) actually collapses a context run
|
|
211
|
+
* longer than `contextLines` — so this story now supplies a real 10-line
|
|
212
|
+
* context run with `contextLines={2}`, which collapses to
|
|
213
|
+
* `top(1) + collapsed(8) + bottom(1)`, i.e. a real "Show 8 more lines"
|
|
214
|
+
* button. The button's accessible name is
|
|
215
|
+
* `t("ai.diffView.showMore", { count })`, not the literal "Show more" —
|
|
216
|
+
* matched by a count-tolerant regex so it doesn't drift with the wording.
|
|
217
|
+
*/
|
|
218
|
+
export const FocusIndicatorExpand: Story = {
|
|
219
|
+
name: "Keyboard focus — expand",
|
|
220
|
+
render: () => (
|
|
221
|
+
<div className="max-w-2xl">
|
|
222
|
+
<DiffView
|
|
223
|
+
lines={Array.from({ length: 10 }, (_, i) => ({
|
|
224
|
+
type: "context" as const,
|
|
225
|
+
oldNumber: i + 1,
|
|
226
|
+
newNumber: i + 1,
|
|
227
|
+
text: `line ${i + 1}`,
|
|
228
|
+
}))}
|
|
229
|
+
contextLines={2}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
),
|
|
233
|
+
play: async ({ canvas, userEvent }) => {
|
|
234
|
+
const button = canvas.getByRole("button", { name: /show \d+ more lines?/i });
|
|
235
|
+
await userEvent.tab();
|
|
236
|
+
await expect(button).toHaveFocus();
|
|
237
|
+
const focused = getComputedStyle(button);
|
|
238
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
239
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
240
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Focus indicator on scrollable pager (#313): Tab to the diff body and
|
|
246
|
+
* assert the compound indicator's OUTLINE layer on the actual scroll
|
|
247
|
+
* container. The prior version located it via `getByText(/^export/)`, which
|
|
248
|
+
* throws "multiple elements" under `variant="split"` (the same line renders
|
|
249
|
+
* once per pane) — and even resolved, `scrollDiv.parentElement` was never
|
|
250
|
+
* the scroll container itself (the text sits several nodes deep inside it).
|
|
251
|
+
* Query the tabbable scroll container directly — it carries `tabIndex={0}`
|
|
252
|
+
* and `focus-ring-inset` (`diff-view.tsx`'s `DiffViewPager`) — instead of
|
|
253
|
+
* inferring it from rendered content.
|
|
254
|
+
*/
|
|
255
|
+
export const FocusIndicatorPager: Story = {
|
|
256
|
+
name: "Keyboard focus — pager",
|
|
257
|
+
render: () => (
|
|
258
|
+
<div className="max-w-2xl">
|
|
259
|
+
<DiffView lines={SAMPLE_LINES} pager variant="split" />
|
|
260
|
+
</div>
|
|
261
|
+
),
|
|
262
|
+
play: async ({ canvas, userEvent }) => {
|
|
263
|
+
const pager = canvas.getByRole("region");
|
|
264
|
+
const scrollDiv = pager.querySelector<HTMLElement>('[tabindex="0"]');
|
|
265
|
+
if (scrollDiv == null) {
|
|
266
|
+
throw new Error("expected a tabbable scroll container inside the pager region");
|
|
267
|
+
}
|
|
268
|
+
await userEvent.tab();
|
|
269
|
+
await expect(scrollDiv).toHaveFocus();
|
|
270
|
+
const focused = getComputedStyle(scrollDiv);
|
|
271
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
272
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
273
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
274
|
+
},
|
|
275
|
+
};
|
package/src/diff-view.tsx
CHANGED
|
@@ -296,7 +296,7 @@ function CollapsedRow({ hiddenCount, onExpand }: { hiddenCount: number; onExpand
|
|
|
296
296
|
<button
|
|
297
297
|
type="button"
|
|
298
298
|
onClick={onExpand}
|
|
299
|
-
className="col-start-4 w-fit rounded-sm px-1 text-info-text text-meta hover:underline focus-
|
|
299
|
+
className="col-start-4 w-fit rounded-sm px-1 text-info-text text-meta hover:underline focus-ring"
|
|
300
300
|
>
|
|
301
301
|
{t("ai.diffView.showMore", { count: hiddenCount })}
|
|
302
302
|
</button>
|
|
@@ -486,6 +486,7 @@ function DiffViewHeader({
|
|
|
486
486
|
}) {
|
|
487
487
|
const { t } = useLocale();
|
|
488
488
|
return (
|
|
489
|
+
// header-band-exempt: a file header inside the diff card, not shell chrome
|
|
489
490
|
<div
|
|
490
491
|
data-slot="diff-view-header"
|
|
491
492
|
className="flex items-center justify-between gap-3 border-b bg-muted/80 px-3 py-2 text-meta text-muted-foreground"
|
|
@@ -579,7 +580,7 @@ function DiffViewPager({
|
|
|
579
580
|
tabIndex={0}
|
|
580
581
|
onScroll={updatePercent}
|
|
581
582
|
onKeyDown={onKeyDown}
|
|
582
|
-
className="max-h-[32rem] overflow-y-auto focus-
|
|
583
|
+
className="max-h-[32rem] overflow-y-auto focus-ring-inset"
|
|
583
584
|
>
|
|
584
585
|
{children}
|
|
585
586
|
</div>
|
package/src/file-tree.tsx
CHANGED
|
@@ -369,6 +369,19 @@ export const ProducedAssetTree = forwardRef<HTMLDivElement, ProducedAssetTreePro
|
|
|
369
369
|
},
|
|
370
370
|
[assetsById, onSelect],
|
|
371
371
|
);
|
|
372
|
+
// Empty: a plain note, not an empty `role="tree"` — a tree with no
|
|
373
|
+
// `treeitem` fails `aria-required-children` and announces nothing useful.
|
|
374
|
+
if (assets.length === 0) {
|
|
375
|
+
return (
|
|
376
|
+
<div
|
|
377
|
+
ref={ref}
|
|
378
|
+
data-slot="file-tree-empty"
|
|
379
|
+
className={cn("px-2 py-1 text-body text-muted-foreground", props.className)}
|
|
380
|
+
>
|
|
381
|
+
{t("ai.fileTree.noAssetsProduced")}
|
|
382
|
+
</div>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
372
385
|
return (
|
|
373
386
|
<FileTree
|
|
374
387
|
ref={ref}
|
|
@@ -377,23 +390,17 @@ export const ProducedAssetTree = forwardRef<HTMLDivElement, ProducedAssetTreePro
|
|
|
377
390
|
onSelect={handleSelect}
|
|
378
391
|
{...props}
|
|
379
392
|
>
|
|
380
|
-
{assets.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
</FileTreeIcon>
|
|
392
|
-
<FileTreeName>{asset.name}</FileTreeName>
|
|
393
|
-
</FileTreeFile>
|
|
394
|
-
);
|
|
395
|
-
})
|
|
396
|
-
)}
|
|
393
|
+
{assets.map((asset) => {
|
|
394
|
+
const Icon = assetIcon(asset);
|
|
395
|
+
return (
|
|
396
|
+
<FileTreeFile key={asset.id} path={asset.id} name={asset.name}>
|
|
397
|
+
<FileTreeIcon>
|
|
398
|
+
<Icon aria-hidden="true" className="size-4 text-muted-foreground" />
|
|
399
|
+
</FileTreeIcon>
|
|
400
|
+
<FileTreeName>{asset.name}</FileTreeName>
|
|
401
|
+
</FileTreeFile>
|
|
402
|
+
);
|
|
403
|
+
})}
|
|
397
404
|
</FileTree>
|
|
398
405
|
);
|
|
399
406
|
},
|
package/src/gallery.stories.tsx
CHANGED
|
@@ -180,3 +180,45 @@ export const PartialSet: Story = {
|
|
|
180
180
|
await expect(canvas.queryByText(/more/i)).not.toBeInTheDocument();
|
|
181
181
|
},
|
|
182
182
|
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Focus indicator on image thumbnails (#313): Tab to an image button and
|
|
186
|
+
* assert the compound indicator's OUTLINE layer — `outlineStyle: "solid"`
|
|
187
|
+
* sourced from `--ring-contour`. The pre-#67 `focus-visible:outline-none
|
|
188
|
+
* focus-visible:ring-2 focus-visible:ring-ring` pattern also paints a
|
|
189
|
+
* non-"none" `boxShadow` (Tailwind's `ring-2` box-shadow trick), so asserting
|
|
190
|
+
* boxShadow alone passes on the legacy class string too; the outline layer is
|
|
191
|
+
* the one thing only `focus-ring`/`focus-ring-inset` add.
|
|
192
|
+
*/
|
|
193
|
+
export const FocusIndicator: Story = {
|
|
194
|
+
name: "Keyboard focus",
|
|
195
|
+
args: { images: IMAGES.slice(0, 4) },
|
|
196
|
+
play: async ({ canvas, userEvent }) => {
|
|
197
|
+
const buttons = canvas.getAllByRole("button", { name: /fjord|desert/i });
|
|
198
|
+
await userEvent.tab();
|
|
199
|
+
await expect(buttons[0]).toHaveFocus();
|
|
200
|
+
const focused = getComputedStyle(buttons[0]);
|
|
201
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
202
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
203
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Focus indicator on the overflow ("+N more") tile (#313): a separate DOM
|
|
209
|
+
* node from the plain thumbnails, styled the same way — never covered by any
|
|
210
|
+
* prior test (`gallery.tsx:409`, `GalleryOverflowTile`).
|
|
211
|
+
*/
|
|
212
|
+
export const OverflowFocusIndicator: Story = {
|
|
213
|
+
name: "Overflow tile keyboard focus",
|
|
214
|
+
args: { images: IMAGES },
|
|
215
|
+
play: async ({ canvas, userEvent }) => {
|
|
216
|
+
const overflow = canvas.getByRole("button", { name: /show all 12 images/i });
|
|
217
|
+
overflow.focus();
|
|
218
|
+
await expect(overflow).toHaveFocus();
|
|
219
|
+
const focused = getComputedStyle(overflow);
|
|
220
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
221
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
222
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
223
|
+
},
|
|
224
|
+
};
|
package/src/gallery.tsx
CHANGED
|
@@ -372,7 +372,7 @@ function GalleryTile({ image, index }: { image: GalleryImage; index: number }) {
|
|
|
372
372
|
type="button"
|
|
373
373
|
onClick={() => actions.open(index)}
|
|
374
374
|
aria-label={label}
|
|
375
|
-
className="block w-full rounded-lg focus-
|
|
375
|
+
className="block w-full rounded-lg focus-ring"
|
|
376
376
|
>
|
|
377
377
|
<AspectRatio ratio={aspectRatio} className="overflow-hidden rounded-lg">
|
|
378
378
|
{/* Decorative: the button carries the accessible name. */}
|
|
@@ -406,7 +406,7 @@ function GalleryOverflowTile() {
|
|
|
406
406
|
type="button"
|
|
407
407
|
onClick={() => actions.open(maxVisible)}
|
|
408
408
|
aria-label={`Show all ${total} images`}
|
|
409
|
-
className="block w-full rounded-lg border border-border bg-muted focus-
|
|
409
|
+
className="block w-full rounded-lg border border-border bg-muted focus-ring"
|
|
410
410
|
>
|
|
411
411
|
<AspectRatio ratio={aspectRatio} className="overflow-hidden rounded-lg">
|
|
412
412
|
<span className="flex size-full items-center justify-center text-subtitle text-muted-foreground transition-colors duration-fast ease-standard group-hover:text-foreground motion-reduce:transition-none">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
2
3
|
import { GroupedParts } from "./grouped-parts";
|
|
3
4
|
import { groupPartByType, type GroupablePart } from "./part-groups";
|
|
4
5
|
|
|
@@ -102,3 +103,25 @@ export const DarkTheme: Story = {
|
|
|
102
103
|
),
|
|
103
104
|
],
|
|
104
105
|
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Focus indicator on group triggers (#313): Tab to a collapsible trigger and
|
|
109
|
+
* assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
|
|
110
|
+
* `--ring-contour`) that `focus-ring-inset` adds. `boxShadow !== "none"`
|
|
111
|
+
* alone also passes on the legacy `focus-visible:outline-none
|
|
112
|
+
* focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset`
|
|
113
|
+
* pattern (Tailwind's `ring-2` still paints a box-shadow), so it does not
|
|
114
|
+
* lock the fix.
|
|
115
|
+
*/
|
|
116
|
+
export const FocusIndicator: Story = {
|
|
117
|
+
args: { parts },
|
|
118
|
+
play: async ({ canvas, userEvent }) => {
|
|
119
|
+
const triggers = canvas.getAllByRole("button");
|
|
120
|
+
await userEvent.tab();
|
|
121
|
+
await expect(triggers[0]).toHaveFocus();
|
|
122
|
+
const focused = getComputedStyle(triggers[0]);
|
|
123
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
124
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
125
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
126
|
+
},
|
|
127
|
+
};
|
package/src/grouped-parts.tsx
CHANGED
|
@@ -87,7 +87,7 @@ function DefaultGroup({
|
|
|
87
87
|
className={cn(
|
|
88
88
|
"group flex w-full items-center gap-2 p-3 text-body text-muted-foreground",
|
|
89
89
|
"transition-colors duration-fast ease-standard hover:text-foreground motion-reduce:transition-none",
|
|
90
|
-
"focus-
|
|
90
|
+
"focus-ring-inset",
|
|
91
91
|
)}
|
|
92
92
|
>
|
|
93
93
|
<BrainIcon aria-hidden="true" className="size-4 shrink-0" />
|
package/src/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ export * from "./gallery";
|
|
|
47
47
|
export * from "./grouped-parts";
|
|
48
48
|
export * from "./image";
|
|
49
49
|
export * from "./inline-citation";
|
|
50
|
+
export * from "./a2ui";
|
|
50
51
|
export * from "./jsx-preview";
|
|
51
52
|
// Mermaid loads on first diagram render, not in the entry chunk. `preloadMermaid`
|
|
52
53
|
// warms it early when a surface is known to render diagrams.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
3
4
|
import { MessageCompare, MessageCompareColumn } from "./message-compare";
|
|
4
5
|
import { MessageFeedback } from "./message-feedback";
|
|
5
6
|
import { MessageResponse } from "./message";
|
|
@@ -173,3 +174,42 @@ export const Mobile: Story = {
|
|
|
173
174
|
</div>
|
|
174
175
|
),
|
|
175
176
|
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Focus indicator on scrollable compare column (#313): Tab to the column
|
|
180
|
+
* body and assert the compound indicator's OUTLINE layer. The prior version
|
|
181
|
+
* read `role="region"` — that's `MessageCompareColumn`'s OUTER wrapper
|
|
182
|
+
* (`message-compare.tsx:416`, header + body), which never receives focus or
|
|
183
|
+
* `focus-ring-inset`. The actual scrollable, tabbable, styled element is the
|
|
184
|
+
* nested `[data-slot="message-compare-column-body"]` div
|
|
185
|
+
* (`message-compare.tsx:441-446`) — querying the region always read
|
|
186
|
+
* `boxShadow: "none"` off the wrong node, regardless of the fix.
|
|
187
|
+
*/
|
|
188
|
+
export const FocusIndicator: Story = {
|
|
189
|
+
render: () => (
|
|
190
|
+
<div className="h-96">
|
|
191
|
+
<MessageCompare columns={2}>
|
|
192
|
+
<MessageCompareColumn model={{ name: "GPT-5" }} status="ready">
|
|
193
|
+
<MessageResponse>{GPT_ANSWER}</MessageResponse>
|
|
194
|
+
</MessageCompareColumn>
|
|
195
|
+
<MessageCompareColumn model={{ name: "Claude" }} status="ready">
|
|
196
|
+
<MessageResponse>{CLAUDE_ANSWER}</MessageResponse>
|
|
197
|
+
</MessageCompareColumn>
|
|
198
|
+
</MessageCompare>
|
|
199
|
+
</div>
|
|
200
|
+
),
|
|
201
|
+
play: async ({ canvasElement, userEvent }) => {
|
|
202
|
+
const body = canvasElement.querySelector<HTMLElement>(
|
|
203
|
+
'[data-slot="message-compare-column-body"]',
|
|
204
|
+
);
|
|
205
|
+
if (body == null) {
|
|
206
|
+
throw new Error("expected a message-compare-column-body scroll container");
|
|
207
|
+
}
|
|
208
|
+
await userEvent.tab();
|
|
209
|
+
await expect(body).toHaveFocus();
|
|
210
|
+
const focused = getComputedStyle(body);
|
|
211
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
212
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
213
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
214
|
+
},
|
|
215
|
+
};
|
package/src/message-compare.tsx
CHANGED
|
@@ -416,6 +416,7 @@ export const MessageCompareColumn = forwardRef<HTMLDivElement, MessageCompareCol
|
|
|
416
416
|
role="region"
|
|
417
417
|
{...props}
|
|
418
418
|
>
|
|
419
|
+
{/* header-band-exempt: a column header inside the comparison grid, not shell chrome */}
|
|
419
420
|
<div
|
|
420
421
|
className="flex shrink-0 items-center justify-between gap-2 border-b px-3 py-2"
|
|
421
422
|
data-slot="message-compare-column-header"
|
|
@@ -439,7 +440,7 @@ export const MessageCompareColumn = forwardRef<HTMLDivElement, MessageCompareCol
|
|
|
439
440
|
)}
|
|
440
441
|
</div>
|
|
441
442
|
<div
|
|
442
|
-
className="min-h-0 flex-1 overflow-y-auto p-4 focus-
|
|
443
|
+
className="min-h-0 flex-1 overflow-y-auto p-4 focus-ring-inset"
|
|
443
444
|
data-slot="message-compare-column-body"
|
|
444
445
|
onScroll={handleScroll}
|
|
445
446
|
ref={setScrollRef}
|
package/src/message-form.tsx
CHANGED
|
@@ -689,7 +689,7 @@ export const MessageFormTitle = forwardRef<HTMLParagraphElement, MessageFormTitl
|
|
|
689
689
|
<p
|
|
690
690
|
ref={ref}
|
|
691
691
|
id={headingId}
|
|
692
|
-
className={cn("text-subtitle
|
|
692
|
+
className={cn("text-subtitle text-foreground text-balance", className)}
|
|
693
693
|
{...props}
|
|
694
694
|
>
|
|
695
695
|
{content}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent } from "storybook/test";
|
|
2
3
|
import { useState } from "react";
|
|
3
4
|
import { MessageTable, type TableSort } from "./message-table";
|
|
4
5
|
import type { TableSpec } from "./message-table-spec";
|
|
@@ -174,3 +175,26 @@ export const DarkTheme: Story = {
|
|
|
174
175
|
),
|
|
175
176
|
],
|
|
176
177
|
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Focus indicator on sortable column headers (#313): Tab to a sort header and
|
|
181
|
+
* assert the compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
|
|
182
|
+
* `--ring-contour`) that `focus-ring` adds. `boxShadow !== "none"` alone also
|
|
183
|
+
* passes on the legacy `focus-visible:outline-none focus-visible:ring-2
|
|
184
|
+
* focus-visible:ring-ring focus-visible:ring-offset-1` pattern (Tailwind's
|
|
185
|
+
* `ring-2` still paints a box-shadow), so it does not lock the fix.
|
|
186
|
+
*/
|
|
187
|
+
export const FocusIndicator: Story = {
|
|
188
|
+
args: { spec: invoicesSpec, sortable: true },
|
|
189
|
+
play: async ({ canvas, userEvent }) => {
|
|
190
|
+
const buttons = canvas.getAllByRole("button");
|
|
191
|
+
if (buttons.length > 0) {
|
|
192
|
+
await userEvent.tab();
|
|
193
|
+
await expect(buttons[0]).toHaveFocus();
|
|
194
|
+
const focused = getComputedStyle(buttons[0]);
|
|
195
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
196
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
197
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
package/src/message-table.tsx
CHANGED
|
@@ -301,7 +301,7 @@ export const MessageTable = forwardRef<HTMLDivElement, MessageTableProps>(functi
|
|
|
301
301
|
onClick={() => toggleSort(column)}
|
|
302
302
|
className={cn(
|
|
303
303
|
"inline-flex items-center gap-1 rounded-sm font-medium",
|
|
304
|
-
"focus-
|
|
304
|
+
"focus-ring",
|
|
305
305
|
"hover:text-foreground transition-colors duration-fast ease-standard motion-reduce:transition-none",
|
|
306
306
|
numeric && "flex-row-reverse",
|
|
307
307
|
)}
|
package/src/package-info.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Badge } from "@elabs-ai/components-ui";
|
|
3
|
+
import { Badge, useLocale } from "@elabs-ai/components-ui";
|
|
4
4
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
5
5
|
import { ArrowRightIcon, MinusIcon, PackageIcon, PlusIcon } from "lucide-react";
|
|
6
6
|
import type { HTMLAttributes } from "react";
|
|
@@ -173,14 +173,17 @@ export const PackageInfoDependencies = ({
|
|
|
173
173
|
className,
|
|
174
174
|
children,
|
|
175
175
|
...props
|
|
176
|
-
}: PackageInfoDependenciesProps) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
}: PackageInfoDependenciesProps) => {
|
|
177
|
+
const { t } = useLocale();
|
|
178
|
+
return (
|
|
179
|
+
<div className={cn("space-y-2", className)} {...props}>
|
|
180
|
+
<span className="text-eyebrow uppercase text-muted-foreground">
|
|
181
|
+
{t("ai.packageInfo.dependencies")}
|
|
182
|
+
</span>
|
|
183
|
+
<div className="space-y-1">{children}</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
184
187
|
|
|
185
188
|
export type PackageInfoDependencyProps = HTMLAttributes<HTMLDivElement> & {
|
|
186
189
|
name: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect } from "storybook/test";
|
|
2
3
|
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
3
4
|
import { Reasoning, ReasoningContent, ReasoningTrigger } from "./reasoning";
|
|
4
5
|
const meta = {
|
|
@@ -79,3 +80,29 @@ export const StructuredLedger: Story = {
|
|
|
79
80
|
</Reasoning>
|
|
80
81
|
),
|
|
81
82
|
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Focus indicator: Tab to the trigger and assert a real box-shadow exists,
|
|
86
|
+
* mirroring the library's compound indicator (not the browser default).
|
|
87
|
+
*/
|
|
88
|
+
export const FocusIndicator: Story = {
|
|
89
|
+
name: "Keyboard focus",
|
|
90
|
+
render: () => (
|
|
91
|
+
<Reasoning className="max-w-prose">
|
|
92
|
+
<ReasoningTrigger />
|
|
93
|
+
<ReasoningContent>{"Checked the schema and ran the aggregation."}</ReasoningContent>
|
|
94
|
+
</Reasoning>
|
|
95
|
+
),
|
|
96
|
+
play: async ({ canvas, userEvent }) => {
|
|
97
|
+
// Find the trigger button inside the Collapsible component
|
|
98
|
+
const trigger = canvas.getByRole("button");
|
|
99
|
+
|
|
100
|
+
// Tab rather than .focus(): `:focus-visible` is what `focus-ring` keys
|
|
101
|
+
// on, and a programmatic focus does not reliably match it.
|
|
102
|
+
await userEvent.tab();
|
|
103
|
+
await expect(trigger).toHaveFocus();
|
|
104
|
+
|
|
105
|
+
const focused = getComputedStyle(trigger);
|
|
106
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
107
|
+
},
|
|
108
|
+
};
|
package/src/reasoning.tsx
CHANGED
|
@@ -172,7 +172,7 @@ export const ReasoningTrigger = memo(
|
|
|
172
172
|
return (
|
|
173
173
|
<CollapsibleTrigger
|
|
174
174
|
className={cn(
|
|
175
|
-
"flex w-full items-center gap-2 text-muted-foreground text-body transition-colors hover:text-foreground",
|
|
175
|
+
"flex w-full items-center gap-2 text-muted-foreground text-body transition-colors hover:text-foreground focus-ring",
|
|
176
176
|
className,
|
|
177
177
|
)}
|
|
178
178
|
{...props}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { fn } from "storybook/test";
|
|
2
|
+
import { expect, fn, userEvent } from "storybook/test";
|
|
3
3
|
|
|
4
4
|
import { ChatGreeting } from "./chat-greeting";
|
|
5
5
|
import { SessionHeader } from "./session-header";
|
|
@@ -126,3 +126,37 @@ export const WithChatGreeting: Story = {
|
|
|
126
126
|
</div>
|
|
127
127
|
),
|
|
128
128
|
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Focus indicator on whats-new link (#313): Tab to the link and assert the
|
|
132
|
+
* compound indicator's OUTLINE layer (`outlineStyle: "solid"`,
|
|
133
|
+
* `--ring-contour`) that `focus-ring` adds. `boxShadow !== "none"` alone
|
|
134
|
+
* also passes on the legacy `focus-visible:outline-none
|
|
135
|
+
* focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2`
|
|
136
|
+
* pattern (Tailwind's `ring-2` still paints a box-shadow), so it does not
|
|
137
|
+
* lock the fix.
|
|
138
|
+
*/
|
|
139
|
+
export const FocusIndicator: Story = {
|
|
140
|
+
args: {
|
|
141
|
+
whatsNew: [
|
|
142
|
+
{ label: "New feature", href: "https://example.com" },
|
|
143
|
+
{ label: "Bug fix", href: "https://example.com" },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
render: (args) => (
|
|
147
|
+
<div className="mx-auto max-w-md bg-background">
|
|
148
|
+
<SessionHeader {...args} />
|
|
149
|
+
</div>
|
|
150
|
+
),
|
|
151
|
+
play: async ({ canvas, userEvent }) => {
|
|
152
|
+
const links = canvas.getAllByRole("link");
|
|
153
|
+
if (links.length > 0) {
|
|
154
|
+
await userEvent.tab();
|
|
155
|
+
await expect(links[0]).toHaveFocus();
|
|
156
|
+
const focused = getComputedStyle(links[0]);
|
|
157
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
158
|
+
await expect(focused.outlineStyle).toBe("solid");
|
|
159
|
+
await expect(parseFloat(focused.outlineWidth)).toBeGreaterThan(0);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
package/src/session-header.tsx
CHANGED
|
@@ -194,7 +194,7 @@ export const SessionHeader = forwardRef<HTMLDivElement, SessionHeaderProps>(func
|
|
|
194
194
|
{item.href != null ? (
|
|
195
195
|
<a
|
|
196
196
|
href={item.href}
|
|
197
|
-
className="text-link underline-offset-4 hover:underline focus-
|
|
197
|
+
className="text-link underline-offset-4 hover:underline focus-ring"
|
|
198
198
|
>
|
|
199
199
|
{item.label}
|
|
200
200
|
</a>
|
package/src/sources.stories.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect } from "storybook/test";
|
|
2
3
|
import { Source, SourceList, Sources, SourcesContent, SourcesTrigger } from "./sources";
|
|
3
4
|
const meta = {
|
|
4
5
|
title: "AI/Sources",
|
|
@@ -32,3 +33,32 @@ export const GroundingFooter: Story = {
|
|
|
32
33
|
/>
|
|
33
34
|
),
|
|
34
35
|
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Focus indicator: Tab to the trigger and assert a real box-shadow exists,
|
|
39
|
+
* mirroring the library's compound indicator (not the browser default).
|
|
40
|
+
*/
|
|
41
|
+
export const FocusIndicator: Story = {
|
|
42
|
+
name: "Keyboard focus",
|
|
43
|
+
render: () => (
|
|
44
|
+
<Sources>
|
|
45
|
+
<SourcesTrigger count={2} />
|
|
46
|
+
<SourcesContent>
|
|
47
|
+
<Source href="https://docs.acme.com" title="Acme Docs" />
|
|
48
|
+
<Source href="https://ci.acme.com" title="CI run #42" />
|
|
49
|
+
</SourcesContent>
|
|
50
|
+
</Sources>
|
|
51
|
+
),
|
|
52
|
+
play: async ({ canvas, userEvent }) => {
|
|
53
|
+
// Find the trigger button inside the Collapsible component
|
|
54
|
+
const trigger = canvas.getByRole("button");
|
|
55
|
+
|
|
56
|
+
// Tab rather than .focus(): `:focus-visible` is what `focus-ring` keys
|
|
57
|
+
// on, and a programmatic focus does not reliably match it.
|
|
58
|
+
await userEvent.tab();
|
|
59
|
+
await expect(trigger).toHaveFocus();
|
|
60
|
+
|
|
61
|
+
const focused = getComputedStyle(trigger);
|
|
62
|
+
await expect(focused.boxShadow).not.toBe("none");
|
|
63
|
+
},
|
|
64
|
+
};
|
package/src/sources.tsx
CHANGED
|
@@ -16,7 +16,10 @@ export type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export const SourcesTrigger = ({ className, count, children, ...props }: SourcesTriggerProps) => (
|
|
19
|
-
<CollapsibleTrigger
|
|
19
|
+
<CollapsibleTrigger
|
|
20
|
+
className={cn("group flex items-center gap-2 focus-ring", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
20
23
|
{children ?? (
|
|
21
24
|
<>
|
|
22
25
|
<p className="font-medium">Used {count} sources</p>
|