@dowel-ui/react 0.1.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/LICENSE +21 -0
- package/dist/blocks/admin-users/meta.js +27 -0
- package/dist/blocks/admin-users/meta.js.map +1 -0
- package/dist/blocks/ai-chat/meta.js +30 -0
- package/dist/blocks/ai-chat/meta.js.map +1 -0
- package/dist/blocks/dashboard/meta.js +23 -0
- package/dist/blocks/dashboard/meta.js.map +1 -0
- package/dist/blocks/forgot-password/meta.js +24 -0
- package/dist/blocks/forgot-password/meta.js.map +1 -0
- package/dist/blocks/login/meta.js +26 -0
- package/dist/blocks/login/meta.js.map +1 -0
- package/dist/blocks/pricing/meta.js +24 -0
- package/dist/blocks/pricing/meta.js.map +1 -0
- package/dist/blocks/settings/meta.js +29 -0
- package/dist/blocks/settings/meta.js.map +1 -0
- package/dist/blocks/signup/meta.js +25 -0
- package/dist/blocks/signup/meta.js.map +1 -0
- package/dist/components/accordion/accordion.d.ts +19 -0
- package/dist/components/accordion/accordion.d.ts.map +1 -0
- package/dist/components/accordion/accordion.js +58 -0
- package/dist/components/accordion/accordion.js.map +1 -0
- package/dist/components/accordion/index.d.ts +2 -0
- package/dist/components/accordion/index.js +2 -0
- package/dist/components/accordion/meta.js +17 -0
- package/dist/components/accordion/meta.js.map +1 -0
- package/dist/components/activity-feed/activity-feed.d.ts +39 -0
- package/dist/components/activity-feed/activity-feed.d.ts.map +1 -0
- package/dist/components/activity-feed/activity-feed.js +77 -0
- package/dist/components/activity-feed/activity-feed.js.map +1 -0
- package/dist/components/activity-feed/index.d.ts +2 -0
- package/dist/components/activity-feed/index.js +2 -0
- package/dist/components/activity-feed/meta.js +17 -0
- package/dist/components/activity-feed/meta.js.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +30 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.js +43 -0
- package/dist/components/ai-agent-status/ai-agent-status.js.map +1 -0
- package/dist/components/ai-agent-status/index.d.ts +2 -0
- package/dist/components/ai-agent-status/index.js +2 -0
- package/dist/components/ai-agent-status/meta.js +17 -0
- package/dist/components/ai-agent-status/meta.js.map +1 -0
- package/dist/components/ai-conversation/ai-conversation.d.ts +45 -0
- package/dist/components/ai-conversation/ai-conversation.d.ts.map +1 -0
- package/dist/components/ai-conversation/ai-conversation.js +138 -0
- package/dist/components/ai-conversation/ai-conversation.js.map +1 -0
- package/dist/components/ai-conversation/index.d.ts +2 -0
- package/dist/components/ai-conversation/index.js +2 -0
- package/dist/components/ai-conversation/meta.js +17 -0
- package/dist/components/ai-conversation/meta.js.map +1 -0
- package/dist/components/ai-message/ai-message.d.ts +56 -0
- package/dist/components/ai-message/ai-message.d.ts.map +1 -0
- package/dist/components/ai-message/ai-message.js +90 -0
- package/dist/components/ai-message/ai-message.js.map +1 -0
- package/dist/components/ai-message/index.d.ts +2 -0
- package/dist/components/ai-message/index.js +2 -0
- package/dist/components/ai-message/meta.js +17 -0
- package/dist/components/ai-message/meta.js.map +1 -0
- package/dist/components/ai-model-selector/ai-model-selector.d.ts +42 -0
- package/dist/components/ai-model-selector/ai-model-selector.d.ts.map +1 -0
- package/dist/components/ai-model-selector/ai-model-selector.js +48 -0
- package/dist/components/ai-model-selector/ai-model-selector.js.map +1 -0
- package/dist/components/ai-model-selector/index.d.ts +2 -0
- package/dist/components/ai-model-selector/index.js +2 -0
- package/dist/components/ai-model-selector/meta.js +17 -0
- package/dist/components/ai-model-selector/meta.js.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.d.ts +61 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.d.ts.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js +170 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -0
- package/dist/components/ai-prompt-input/index.d.ts +2 -0
- package/dist/components/ai-prompt-input/index.js +2 -0
- package/dist/components/ai-prompt-input/meta.js +17 -0
- package/dist/components/ai-prompt-input/meta.js.map +1 -0
- package/dist/components/ai-reasoning/ai-reasoning.d.ts +24 -0
- package/dist/components/ai-reasoning/ai-reasoning.d.ts.map +1 -0
- package/dist/components/ai-reasoning/ai-reasoning.js +64 -0
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -0
- package/dist/components/ai-reasoning/index.d.ts +2 -0
- package/dist/components/ai-reasoning/index.js +2 -0
- package/dist/components/ai-reasoning/meta.js +17 -0
- package/dist/components/ai-reasoning/meta.js.map +1 -0
- package/dist/components/ai-response/ai-response.d.ts +36 -0
- package/dist/components/ai-response/ai-response.d.ts.map +1 -0
- package/dist/components/ai-response/ai-response.js +46 -0
- package/dist/components/ai-response/ai-response.js.map +1 -0
- package/dist/components/ai-response/index.d.ts +2 -0
- package/dist/components/ai-response/index.js +2 -0
- package/dist/components/ai-response/meta.js +17 -0
- package/dist/components/ai-response/meta.js.map +1 -0
- package/dist/components/ai-sources/ai-sources.d.ts +44 -0
- package/dist/components/ai-sources/ai-sources.d.ts.map +1 -0
- package/dist/components/ai-sources/ai-sources.js +112 -0
- package/dist/components/ai-sources/ai-sources.js.map +1 -0
- package/dist/components/ai-sources/index.d.ts +2 -0
- package/dist/components/ai-sources/index.js +2 -0
- package/dist/components/ai-sources/meta.js +17 -0
- package/dist/components/ai-sources/meta.js.map +1 -0
- package/dist/components/ai-token-usage/ai-token-usage.d.ts +34 -0
- package/dist/components/ai-token-usage/ai-token-usage.d.ts.map +1 -0
- package/dist/components/ai-token-usage/ai-token-usage.js +58 -0
- package/dist/components/ai-token-usage/ai-token-usage.js.map +1 -0
- package/dist/components/ai-token-usage/index.d.ts +2 -0
- package/dist/components/ai-token-usage/index.js +2 -0
- package/dist/components/ai-token-usage/meta.js +17 -0
- package/dist/components/ai-token-usage/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.d.ts +50 -0
- package/dist/components/ai-tool/ai-tool.d.ts.map +1 -0
- package/dist/components/ai-tool/ai-tool.js +129 -0
- package/dist/components/ai-tool/ai-tool.js.map +1 -0
- package/dist/components/ai-tool/index.d.ts +2 -0
- package/dist/components/ai-tool/index.js +2 -0
- package/dist/components/ai-tool/meta.js +17 -0
- package/dist/components/ai-tool/meta.js.map +1 -0
- package/dist/components/alert/alert.d.ts +32 -0
- package/dist/components/alert/alert.d.ts.map +1 -0
- package/dist/components/alert/alert.js +49 -0
- package/dist/components/alert/alert.js.map +1 -0
- package/dist/components/alert/index.d.ts +2 -0
- package/dist/components/alert/index.js +2 -0
- package/dist/components/alert/meta.js +17 -0
- package/dist/components/alert/meta.js.map +1 -0
- package/dist/components/avatar/avatar.d.ts +23 -0
- package/dist/components/avatar/avatar.d.ts.map +1 -0
- package/dist/components/avatar/avatar.js +44 -0
- package/dist/components/avatar/avatar.js.map +1 -0
- package/dist/components/avatar/index.d.ts +2 -0
- package/dist/components/avatar/index.js +2 -0
- package/dist/components/avatar/meta.js +17 -0
- package/dist/components/avatar/meta.js.map +1 -0
- package/dist/components/badge/badge.d.ts +20 -0
- package/dist/components/badge/badge.d.ts.map +1 -0
- package/dist/components/badge/badge.js +47 -0
- package/dist/components/badge/badge.js.map +1 -0
- package/dist/components/badge/index.d.ts +2 -0
- package/dist/components/badge/index.js +2 -0
- package/dist/components/badge/meta.js +17 -0
- package/dist/components/badge/meta.js.map +1 -0
- package/dist/components/button/button.d.ts +28 -0
- package/dist/components/button/button.d.ts.map +1 -0
- package/dist/components/button/button.js +77 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.js +2 -0
- package/dist/components/button/meta.js +17 -0
- package/dist/components/button/meta.js.map +1 -0
- package/dist/components/calendar/calendar.d.ts +16 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +70 -0
- package/dist/components/calendar/calendar.js.map +1 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.js +2 -0
- package/dist/components/calendar/meta.js +17 -0
- package/dist/components/calendar/meta.js.map +1 -0
- package/dist/components/card/card.d.ts +23 -0
- package/dist/components/card/card.d.ts.map +1 -0
- package/dist/components/card/card.js +53 -0
- package/dist/components/card/card.js.map +1 -0
- package/dist/components/card/index.d.ts +2 -0
- package/dist/components/card/index.js +2 -0
- package/dist/components/card/meta.js +17 -0
- package/dist/components/card/meta.js.map +1 -0
- package/dist/components/checkbox/checkbox.d.ts +15 -0
- package/dist/components/checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox/checkbox.js +45 -0
- package/dist/components/checkbox/checkbox.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +2 -0
- package/dist/components/checkbox/index.js +2 -0
- package/dist/components/checkbox/meta.js +17 -0
- package/dist/components/checkbox/meta.js.map +1 -0
- package/dist/components/code-block/code-block.d.ts +46 -0
- package/dist/components/code-block/code-block.d.ts.map +1 -0
- package/dist/components/code-block/code-block.js +110 -0
- package/dist/components/code-block/code-block.js.map +1 -0
- package/dist/components/code-block/index.d.ts +2 -0
- package/dist/components/code-block/index.js +2 -0
- package/dist/components/code-block/meta.js +17 -0
- package/dist/components/code-block/meta.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +59 -0
- package/dist/components/combobox/combobox.d.ts.map +1 -0
- package/dist/components/combobox/combobox.js +346 -0
- package/dist/components/combobox/combobox.js.map +1 -0
- package/dist/components/combobox/index.d.ts +2 -0
- package/dist/components/combobox/index.js +2 -0
- package/dist/components/combobox/meta.js +17 -0
- package/dist/components/combobox/meta.js.map +1 -0
- package/dist/components/command/command.d.ts +65 -0
- package/dist/components/command/command.d.ts.map +1 -0
- package/dist/components/command/command.js +330 -0
- package/dist/components/command/command.js.map +1 -0
- package/dist/components/command/index.d.ts +2 -0
- package/dist/components/command/index.js +2 -0
- package/dist/components/command/meta.js +17 -0
- package/dist/components/command/meta.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +91 -0
- package/dist/components/data-table/data-table.d.ts.map +1 -0
- package/dist/components/data-table/data-table.js +263 -0
- package/dist/components/data-table/data-table.js.map +1 -0
- package/dist/components/data-table/index.d.ts +2 -0
- package/dist/components/data-table/index.js +2 -0
- package/dist/components/data-table/meta.js +22 -0
- package/dist/components/data-table/meta.js.map +1 -0
- package/dist/components/date-picker/date-picker.d.ts +31 -0
- package/dist/components/date-picker/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker/date-picker.js +123 -0
- package/dist/components/date-picker/date-picker.js.map +1 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.js +2 -0
- package/dist/components/date-picker/meta.js +21 -0
- package/dist/components/date-picker/meta.js.map +1 -0
- package/dist/components/dialog/dialog.d.ts +30 -0
- package/dist/components/dialog/dialog.d.ts.map +1 -0
- package/dist/components/dialog/dialog.js +79 -0
- package/dist/components/dialog/dialog.js.map +1 -0
- package/dist/components/dialog/index.d.ts +2 -0
- package/dist/components/dialog/index.js +2 -0
- package/dist/components/dialog/meta.js +17 -0
- package/dist/components/dialog/meta.js.map +1 -0
- package/dist/components/drawer/drawer.d.ts +32 -0
- package/dist/components/drawer/drawer.d.ts.map +1 -0
- package/dist/components/drawer/drawer.js +153 -0
- package/dist/components/drawer/drawer.js.map +1 -0
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +2 -0
- package/dist/components/drawer/meta.js +17 -0
- package/dist/components/drawer/meta.js.map +1 -0
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +43 -0
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu/dropdown-menu.js +130 -0
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -0
- package/dist/components/dropdown-menu/index.d.ts +2 -0
- package/dist/components/dropdown-menu/index.js +2 -0
- package/dist/components/dropdown-menu/meta.js +17 -0
- package/dist/components/dropdown-menu/meta.js.map +1 -0
- package/dist/components/empty-state/empty-state.d.ts +24 -0
- package/dist/components/empty-state/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state/empty-state.js +73 -0
- package/dist/components/empty-state/empty-state.js.map +1 -0
- package/dist/components/empty-state/index.d.ts +2 -0
- package/dist/components/empty-state/index.js +2 -0
- package/dist/components/empty-state/meta.js +17 -0
- package/dist/components/empty-state/meta.js.map +1 -0
- package/dist/components/form/form.d.ts +43 -0
- package/dist/components/form/form.d.ts.map +1 -0
- package/dist/components/form/form.js +127 -0
- package/dist/components/form/form.js.map +1 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +2 -0
- package/dist/components/form/meta.js +17 -0
- package/dist/components/form/meta.js.map +1 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/index.js +2 -0
- package/dist/components/input/input.d.ts +21 -0
- package/dist/components/input/input.d.ts.map +1 -0
- package/dist/components/input/input.js +34 -0
- package/dist/components/input/input.js.map +1 -0
- package/dist/components/input/meta.js +17 -0
- package/dist/components/input/meta.js.map +1 -0
- package/dist/components/label/index.d.ts +2 -0
- package/dist/components/label/index.js +2 -0
- package/dist/components/label/label.d.ts +14 -0
- package/dist/components/label/label.d.ts.map +1 -0
- package/dist/components/label/label.js +20 -0
- package/dist/components/label/label.js.map +1 -0
- package/dist/components/label/meta.js +17 -0
- package/dist/components/label/meta.js.map +1 -0
- package/dist/components/pagination/index.d.ts +2 -0
- package/dist/components/pagination/index.js +2 -0
- package/dist/components/pagination/meta.js +17 -0
- package/dist/components/pagination/meta.js.map +1 -0
- package/dist/components/pagination/pagination.d.ts +33 -0
- package/dist/components/pagination/pagination.d.ts.map +1 -0
- package/dist/components/pagination/pagination.js +143 -0
- package/dist/components/pagination/pagination.js.map +1 -0
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.js +2 -0
- package/dist/components/popover/meta.js +17 -0
- package/dist/components/popover/meta.js.map +1 -0
- package/dist/components/popover/popover.d.ts +24 -0
- package/dist/components/popover/popover.d.ts.map +1 -0
- package/dist/components/popover/popover.js +60 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/progress/index.d.ts +2 -0
- package/dist/components/progress/index.js +2 -0
- package/dist/components/progress/meta.js +17 -0
- package/dist/components/progress/meta.js.map +1 -0
- package/dist/components/progress/progress.d.ts +21 -0
- package/dist/components/progress/progress.d.ts.map +1 -0
- package/dist/components/progress/progress.js +55 -0
- package/dist/components/progress/progress.js.map +1 -0
- package/dist/components/radio-group/index.d.ts +2 -0
- package/dist/components/radio-group/index.js +2 -0
- package/dist/components/radio-group/meta.js +17 -0
- package/dist/components/radio-group/meta.js.map +1 -0
- package/dist/components/radio-group/radio-group.d.ts +17 -0
- package/dist/components/radio-group/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group/radio-group.js +29 -0
- package/dist/components/radio-group/radio-group.js.map +1 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +2 -0
- package/dist/components/select/meta.js +17 -0
- package/dist/components/select/meta.js.map +1 -0
- package/dist/components/select/select.d.ts +37 -0
- package/dist/components/select/select.d.ts.map +1 -0
- package/dist/components/select/select.js +128 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/separator/index.d.ts +2 -0
- package/dist/components/separator/index.js +2 -0
- package/dist/components/separator/meta.js +17 -0
- package/dist/components/separator/meta.js.map +1 -0
- package/dist/components/separator/separator.d.ts +15 -0
- package/dist/components/separator/separator.d.ts.map +1 -0
- package/dist/components/separator/separator.js +23 -0
- package/dist/components/separator/separator.js.map +1 -0
- package/dist/components/sheet/index.d.ts +2 -0
- package/dist/components/sheet/index.js +2 -0
- package/dist/components/sheet/meta.js +17 -0
- package/dist/components/sheet/meta.js.map +1 -0
- package/dist/components/sheet/sheet.d.ts +31 -0
- package/dist/components/sheet/sheet.d.ts.map +1 -0
- package/dist/components/sheet/sheet.js +90 -0
- package/dist/components/sheet/sheet.js.map +1 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.js +2 -0
- package/dist/components/skeleton/meta.js +17 -0
- package/dist/components/skeleton/meta.js.map +1 -0
- package/dist/components/skeleton/skeleton.d.ts +20 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +29 -0
- package/dist/components/skeleton/skeleton.js.map +1 -0
- package/dist/components/slider/index.d.ts +2 -0
- package/dist/components/slider/index.js +2 -0
- package/dist/components/slider/meta.js +17 -0
- package/dist/components/slider/meta.js.map +1 -0
- package/dist/components/slider/slider.d.ts +29 -0
- package/dist/components/slider/slider.d.ts.map +1 -0
- package/dist/components/slider/slider.js +61 -0
- package/dist/components/slider/slider.js.map +1 -0
- package/dist/components/spinner/index.d.ts +2 -0
- package/dist/components/spinner/index.js +2 -0
- package/dist/components/spinner/meta.js +17 -0
- package/dist/components/spinner/meta.js.map +1 -0
- package/dist/components/spinner/spinner.d.ts +21 -0
- package/dist/components/spinner/spinner.d.ts.map +1 -0
- package/dist/components/spinner/spinner.js +45 -0
- package/dist/components/spinner/spinner.js.map +1 -0
- package/dist/components/switch/index.d.ts +2 -0
- package/dist/components/switch/index.js +2 -0
- package/dist/components/switch/meta.js +17 -0
- package/dist/components/switch/meta.js.map +1 -0
- package/dist/components/switch/switch.d.ts +15 -0
- package/dist/components/switch/switch.d.ts.map +1 -0
- package/dist/components/switch/switch.js +21 -0
- package/dist/components/switch/switch.js.map +1 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/index.js +2 -0
- package/dist/components/table/meta.js +17 -0
- package/dist/components/table/meta.js.map +1 -0
- package/dist/components/table/table.d.ts +41 -0
- package/dist/components/table/table.d.ts.map +1 -0
- package/dist/components/table/table.js +78 -0
- package/dist/components/table/table.js.map +1 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.js +2 -0
- package/dist/components/tabs/meta.js +17 -0
- package/dist/components/tabs/meta.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +28 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -0
- package/dist/components/tabs/tabs.js +58 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/components/toast/index.d.ts +3 -0
- package/dist/components/toast/index.js +3 -0
- package/dist/components/toast/meta.js +17 -0
- package/dist/components/toast/meta.js.map +1 -0
- package/dist/components/toast/toast-store.d.ts +84 -0
- package/dist/components/toast/toast-store.d.ts.map +1 -0
- package/dist/components/toast/toast-store.js +115 -0
- package/dist/components/toast/toast-store.js.map +1 -0
- package/dist/components/toast/toast.d.ts +39 -0
- package/dist/components/toast/toast.d.ts.map +1 -0
- package/dist/components/toast/toast.js +140 -0
- package/dist/components/toast/toast.js.map +1 -0
- package/dist/components/tooltip/index.d.ts +2 -0
- package/dist/components/tooltip/index.js +2 -0
- package/dist/components/tooltip/meta.js +17 -0
- package/dist/components/tooltip/meta.js.map +1 -0
- package/dist/components/tooltip/tooltip.d.ts +24 -0
- package/dist/components/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip/tooltip.js +38 -0
- package/dist/components/tooltip/tooltip.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +51 -0
- package/dist/lib/styles.d.ts +21 -0
- package/dist/lib/styles.d.ts.map +1 -0
- package/dist/lib/styles.js +22 -0
- package/dist/lib/styles.js.map +1 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +21 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/registry/blocks.d.ts +15 -0
- package/dist/registry/blocks.d.ts.map +1 -0
- package/dist/registry/blocks.js +32 -0
- package/dist/registry/blocks.js.map +1 -0
- package/dist/registry/components.d.ts +18 -0
- package/dist/registry/components.d.ts.map +1 -0
- package/dist/registry/components.js +112 -0
- package/dist/registry/components.js.map +1 -0
- package/dist/registry/schema.d.ts +44 -0
- package/dist/registry/schema.d.ts.map +1 -0
- package/dist/registry/schema.js +33 -0
- package/dist/registry/schema.js.map +1 -0
- package/package.json +74 -0
- package/src/blocks/admin-users/admin-users.stories.tsx +83 -0
- package/src/blocks/admin-users/admin-users.test.tsx +159 -0
- package/src/blocks/admin-users/admin-users.tsx +251 -0
- package/src/blocks/admin-users/index.ts +1 -0
- package/src/blocks/admin-users/meta.ts +27 -0
- package/src/blocks/ai-chat/ai-chat.stories.tsx +149 -0
- package/src/blocks/ai-chat/ai-chat.test.tsx +166 -0
- package/src/blocks/ai-chat/ai-chat.tsx +280 -0
- package/src/blocks/ai-chat/index.ts +7 -0
- package/src/blocks/ai-chat/meta.ts +32 -0
- package/src/blocks/dashboard/dashboard.stories.tsx +106 -0
- package/src/blocks/dashboard/dashboard.test.tsx +94 -0
- package/src/blocks/dashboard/dashboard.tsx +192 -0
- package/src/blocks/dashboard/index.ts +6 -0
- package/src/blocks/dashboard/meta.ts +18 -0
- package/src/blocks/forgot-password/forgot-password.stories.tsx +41 -0
- package/src/blocks/forgot-password/forgot-password.test.tsx +62 -0
- package/src/blocks/forgot-password/forgot-password.tsx +158 -0
- package/src/blocks/forgot-password/index.ts +1 -0
- package/src/blocks/forgot-password/meta.ts +18 -0
- package/src/blocks/login/index.ts +1 -0
- package/src/blocks/login/login.stories.tsx +46 -0
- package/src/blocks/login/login.test.tsx +100 -0
- package/src/blocks/login/login.tsx +178 -0
- package/src/blocks/login/meta.ts +18 -0
- package/src/blocks/pricing/index.ts +1 -0
- package/src/blocks/pricing/meta.ts +18 -0
- package/src/blocks/pricing/pricing.stories.tsx +62 -0
- package/src/blocks/pricing/pricing.test.tsx +101 -0
- package/src/blocks/pricing/pricing.tsx +176 -0
- package/src/blocks/settings/index.ts +6 -0
- package/src/blocks/settings/meta.ts +29 -0
- package/src/blocks/settings/settings.stories.tsx +94 -0
- package/src/blocks/settings/settings.test.tsx +167 -0
- package/src/blocks/settings/settings.tsx +317 -0
- package/src/blocks/signup/index.ts +1 -0
- package/src/blocks/signup/meta.ts +18 -0
- package/src/blocks/signup/signup.stories.tsx +27 -0
- package/src/blocks/signup/signup.test.tsx +97 -0
- package/src/blocks/signup/signup.tsx +240 -0
- package/src/components/accordion/accordion.stories.tsx +90 -0
- package/src/components/accordion/accordion.test.tsx +156 -0
- package/src/components/accordion/accordion.tsx +88 -0
- package/src/components/accordion/index.ts +9 -0
- package/src/components/accordion/meta.ts +16 -0
- package/src/components/activity-feed/activity-feed.stories.tsx +163 -0
- package/src/components/activity-feed/activity-feed.test.tsx +125 -0
- package/src/components/activity-feed/activity-feed.tsx +115 -0
- package/src/components/activity-feed/index.ts +12 -0
- package/src/components/activity-feed/meta.ts +17 -0
- package/src/components/ai-agent-status/ai-agent-status.stories.tsx +86 -0
- package/src/components/ai-agent-status/ai-agent-status.test.tsx +66 -0
- package/src/components/ai-agent-status/ai-agent-status.tsx +88 -0
- package/src/components/ai-agent-status/index.ts +6 -0
- package/src/components/ai-agent-status/meta.ts +17 -0
- package/src/components/ai-conversation/ai-conversation.stories.tsx +126 -0
- package/src/components/ai-conversation/ai-conversation.test.tsx +144 -0
- package/src/components/ai-conversation/ai-conversation.tsx +229 -0
- package/src/components/ai-conversation/index.ts +9 -0
- package/src/components/ai-conversation/meta.ts +20 -0
- package/src/components/ai-message/ai-message.stories.tsx +106 -0
- package/src/components/ai-message/ai-message.test.tsx +160 -0
- package/src/components/ai-message/ai-message.tsx +162 -0
- package/src/components/ai-message/index.ts +13 -0
- package/src/components/ai-message/meta.ts +17 -0
- package/src/components/ai-model-selector/ai-model-selector.stories.tsx +66 -0
- package/src/components/ai-model-selector/ai-model-selector.test.tsx +120 -0
- package/src/components/ai-model-selector/ai-model-selector.tsx +112 -0
- package/src/components/ai-model-selector/index.ts +1 -0
- package/src/components/ai-model-selector/meta.ts +17 -0
- package/src/components/ai-prompt-input/ai-prompt-input.stories.tsx +145 -0
- package/src/components/ai-prompt-input/ai-prompt-input.test.tsx +273 -0
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +306 -0
- package/src/components/ai-prompt-input/index.ts +11 -0
- package/src/components/ai-prompt-input/meta.ts +18 -0
- package/src/components/ai-reasoning/ai-reasoning.stories.tsx +60 -0
- package/src/components/ai-reasoning/ai-reasoning.test.tsx +95 -0
- package/src/components/ai-reasoning/ai-reasoning.tsx +106 -0
- package/src/components/ai-reasoning/index.ts +7 -0
- package/src/components/ai-reasoning/meta.ts +17 -0
- package/src/components/ai-response/ai-response.stories.tsx +75 -0
- package/src/components/ai-response/ai-response.test.tsx +96 -0
- package/src/components/ai-response/ai-response.tsx +95 -0
- package/src/components/ai-response/index.ts +7 -0
- package/src/components/ai-response/meta.ts +17 -0
- package/src/components/ai-sources/ai-sources.stories.tsx +93 -0
- package/src/components/ai-sources/ai-sources.test.tsx +102 -0
- package/src/components/ai-sources/ai-sources.tsx +186 -0
- package/src/components/ai-sources/index.ts +11 -0
- package/src/components/ai-sources/meta.ts +17 -0
- package/src/components/ai-token-usage/ai-token-usage.stories.tsx +59 -0
- package/src/components/ai-token-usage/ai-token-usage.test.tsx +83 -0
- package/src/components/ai-token-usage/ai-token-usage.tsx +105 -0
- package/src/components/ai-token-usage/index.ts +6 -0
- package/src/components/ai-token-usage/meta.ts +16 -0
- package/src/components/ai-tool/ai-tool.stories.tsx +96 -0
- package/src/components/ai-tool/ai-tool.test.tsx +137 -0
- package/src/components/ai-tool/ai-tool.tsx +235 -0
- package/src/components/ai-tool/index.ts +13 -0
- package/src/components/ai-tool/meta.ts +17 -0
- package/src/components/alert/alert.stories.tsx +101 -0
- package/src/components/alert/alert.test.tsx +94 -0
- package/src/components/alert/alert.tsx +89 -0
- package/src/components/alert/index.ts +8 -0
- package/src/components/alert/meta.ts +16 -0
- package/src/components/avatar/avatar.stories.tsx +58 -0
- package/src/components/avatar/avatar.test.tsx +64 -0
- package/src/components/avatar/avatar.tsx +68 -0
- package/src/components/avatar/index.ts +9 -0
- package/src/components/avatar/meta.ts +15 -0
- package/src/components/badge/badge.stories.tsx +68 -0
- package/src/components/badge/badge.test.tsx +61 -0
- package/src/components/badge/badge.tsx +54 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/badge/meta.ts +15 -0
- package/src/components/button/button.stories.tsx +128 -0
- package/src/components/button/button.test.tsx +224 -0
- package/src/components/button/button.tsx +114 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/button/meta.ts +16 -0
- package/src/components/calendar/calendar.stories.tsx +105 -0
- package/src/components/calendar/calendar.test.tsx +255 -0
- package/src/components/calendar/calendar.tsx +115 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/meta.ts +17 -0
- package/src/components/card/card.stories.tsx +61 -0
- package/src/components/card/card.test.tsx +66 -0
- package/src/components/card/card.tsx +81 -0
- package/src/components/card/index.ts +9 -0
- package/src/components/card/meta.ts +16 -0
- package/src/components/checkbox/checkbox.stories.tsx +124 -0
- package/src/components/checkbox/checkbox.test.tsx +121 -0
- package/src/components/checkbox/checkbox.tsx +66 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/checkbox/meta.ts +16 -0
- package/src/components/code-block/code-block.stories.tsx +62 -0
- package/src/components/code-block/code-block.test.tsx +139 -0
- package/src/components/code-block/code-block.tsx +188 -0
- package/src/components/code-block/index.ts +1 -0
- package/src/components/code-block/meta.ts +17 -0
- package/src/components/combobox/combobox.stories.tsx +171 -0
- package/src/components/combobox/combobox.test.tsx +429 -0
- package/src/components/combobox/combobox.tsx +544 -0
- package/src/components/combobox/index.ts +15 -0
- package/src/components/combobox/meta.ts +17 -0
- package/src/components/command/command.stories.tsx +152 -0
- package/src/components/command/command.test.tsx +380 -0
- package/src/components/command/command.tsx +482 -0
- package/src/components/command/index.ts +17 -0
- package/src/components/command/meta.ts +18 -0
- package/src/components/data-table/data-table.stories.tsx +151 -0
- package/src/components/data-table/data-table.test.tsx +288 -0
- package/src/components/data-table/data-table.tsx +485 -0
- package/src/components/data-table/index.ts +13 -0
- package/src/components/data-table/meta.ts +19 -0
- package/src/components/date-picker/date-picker.stories.tsx +103 -0
- package/src/components/date-picker/date-picker.test.tsx +205 -0
- package/src/components/date-picker/date-picker.tsx +198 -0
- package/src/components/date-picker/index.ts +6 -0
- package/src/components/date-picker/meta.ts +18 -0
- package/src/components/dialog/dialog.stories.tsx +140 -0
- package/src/components/dialog/dialog.test.tsx +186 -0
- package/src/components/dialog/dialog.tsx +132 -0
- package/src/components/dialog/index.ts +16 -0
- package/src/components/dialog/meta.ts +16 -0
- package/src/components/drawer/drawer.stories.tsx +111 -0
- package/src/components/drawer/drawer.test.tsx +187 -0
- package/src/components/drawer/drawer.tsx +293 -0
- package/src/components/drawer/index.ts +16 -0
- package/src/components/drawer/meta.ts +16 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +128 -0
- package/src/components/dropdown-menu/dropdown-menu.test.tsx +321 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +242 -0
- package/src/components/dropdown-menu/index.ts +21 -0
- package/src/components/dropdown-menu/meta.ts +17 -0
- package/src/components/empty-state/empty-state.stories.tsx +70 -0
- package/src/components/empty-state/empty-state.test.tsx +97 -0
- package/src/components/empty-state/empty-state.tsx +92 -0
- package/src/components/empty-state/index.ts +9 -0
- package/src/components/empty-state/meta.ts +16 -0
- package/src/components/form/form.stories.tsx +144 -0
- package/src/components/form/form.test.tsx +203 -0
- package/src/components/form/form.tsx +221 -0
- package/src/components/form/index.ts +13 -0
- package/src/components/form/meta.ts +17 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.stories.tsx +84 -0
- package/src/components/input/input.test.tsx +108 -0
- package/src/components/input/input.tsx +51 -0
- package/src/components/input/meta.ts +15 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.stories.tsx +51 -0
- package/src/components/label/label.test.tsx +55 -0
- package/src/components/label/label.tsx +26 -0
- package/src/components/label/meta.ts +15 -0
- package/src/components/pagination/index.ts +10 -0
- package/src/components/pagination/meta.ts +17 -0
- package/src/components/pagination/pagination.stories.tsx +129 -0
- package/src/components/pagination/pagination.test.tsx +119 -0
- package/src/components/pagination/pagination.tsx +142 -0
- package/src/components/popover/index.ts +9 -0
- package/src/components/popover/meta.ts +18 -0
- package/src/components/popover/popover.stories.tsx +84 -0
- package/src/components/popover/popover.test.tsx +163 -0
- package/src/components/popover/popover.tsx +89 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/meta.ts +17 -0
- package/src/components/progress/progress.stories.tsx +90 -0
- package/src/components/progress/progress.test.tsx +78 -0
- package/src/components/progress/progress.tsx +70 -0
- package/src/components/radio-group/index.ts +6 -0
- package/src/components/radio-group/meta.ts +16 -0
- package/src/components/radio-group/radio-group.stories.tsx +102 -0
- package/src/components/radio-group/radio-group.test.tsx +152 -0
- package/src/components/radio-group/radio-group.tsx +53 -0
- package/src/components/select/index.ts +14 -0
- package/src/components/select/meta.ts +16 -0
- package/src/components/select/select.stories.tsx +137 -0
- package/src/components/select/select.test.tsx +209 -0
- package/src/components/select/select.tsx +222 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/meta.ts +15 -0
- package/src/components/separator/separator.stories.tsx +42 -0
- package/src/components/separator/separator.test.tsx +46 -0
- package/src/components/separator/separator.tsx +33 -0
- package/src/components/sheet/index.ts +14 -0
- package/src/components/sheet/meta.ts +16 -0
- package/src/components/sheet/sheet.stories.tsx +107 -0
- package/src/components/sheet/sheet.test.tsx +152 -0
- package/src/components/sheet/sheet.tsx +160 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/meta.ts +15 -0
- package/src/components/skeleton/skeleton.stories.tsx +46 -0
- package/src/components/skeleton/skeleton.test.tsx +57 -0
- package/src/components/skeleton/skeleton.tsx +28 -0
- package/src/components/slider/index.ts +1 -0
- package/src/components/slider/meta.ts +19 -0
- package/src/components/slider/slider.stories.tsx +87 -0
- package/src/components/slider/slider.test.tsx +148 -0
- package/src/components/slider/slider.tsx +135 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner/meta.ts +15 -0
- package/src/components/spinner/spinner.stories.tsx +47 -0
- package/src/components/spinner/spinner.test.tsx +41 -0
- package/src/components/spinner/spinner.tsx +68 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/meta.ts +15 -0
- package/src/components/switch/switch.stories.tsx +70 -0
- package/src/components/switch/switch.test.tsx +101 -0
- package/src/components/switch/switch.tsx +42 -0
- package/src/components/table/index.ts +11 -0
- package/src/components/table/meta.ts +17 -0
- package/src/components/table/table.stories.tsx +131 -0
- package/src/components/table/table.test.tsx +153 -0
- package/src/components/table/table.tsx +138 -0
- package/src/components/tabs/index.ts +11 -0
- package/src/components/tabs/meta.ts +17 -0
- package/src/components/tabs/tabs.stories.tsx +139 -0
- package/src/components/tabs/tabs.test.tsx +148 -0
- package/src/components/tabs/tabs.tsx +106 -0
- package/src/components/toast/index.ts +24 -0
- package/src/components/toast/meta.ts +18 -0
- package/src/components/toast/toast-store.ts +181 -0
- package/src/components/toast/toast.stories.tsx +167 -0
- package/src/components/toast/toast.test.tsx +306 -0
- package/src/components/toast/toast.tsx +241 -0
- package/src/components/tooltip/index.ts +8 -0
- package/src/components/tooltip/meta.ts +17 -0
- package/src/components/tooltip/tooltip.stories.tsx +89 -0
- package/src/components/tooltip/tooltip.test.tsx +120 -0
- package/src/components/tooltip/tooltip.tsx +58 -0
- package/src/index.ts +50 -0
- package/src/lib/styles.ts +26 -0
- package/src/lib/utils.test.ts +32 -0
- package/src/lib/utils.ts +24 -0
- package/src/registry/blocks.ts +30 -0
- package/src/registry/components.ts +114 -0
- package/src/registry/meta.test.ts +234 -0
- package/src/registry/schema.ts +58 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "ai-agent-status",
|
|
5
|
+
title: "AI Agent Status",
|
|
6
|
+
description: "A pill showing what an agent is doing, in words as well as colour.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: ["class-variance-authority"],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-agent-status.tsx"],
|
|
12
|
+
a11y:
|
|
13
|
+
"The state is always text. Agent states matter most when something has failed, which is " +
|
|
14
|
+
"exactly when a colour-only signal fails the people who most need to see it. Live " +
|
|
15
|
+
"announcements are off by default — several agents each announcing their transitions turns a " +
|
|
16
|
+
"dashboard into noise; enable it for the one agent being watched.",
|
|
17
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Message, MessageAvatar, MessageBody } from "@/components/ai-message";
|
|
5
|
+
import { Response, ThinkingIndicator } from "@/components/ai-response";
|
|
6
|
+
import { Button } from "@/components/button";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Conversation,
|
|
10
|
+
ConversationMessages,
|
|
11
|
+
ConversationScrollButton,
|
|
12
|
+
ConversationStatus,
|
|
13
|
+
} from "./ai-conversation";
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Conversation> = {
|
|
16
|
+
title: "AI/Conversation",
|
|
17
|
+
component: Conversation,
|
|
18
|
+
parameters: { controls: { disable: true } },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof Conversation>;
|
|
23
|
+
|
|
24
|
+
const TURNS = [
|
|
25
|
+
{ role: "user" as const, text: "What's the difference between a Sheet and a Drawer?" },
|
|
26
|
+
{
|
|
27
|
+
role: "assistant" as const,
|
|
28
|
+
text: "A Sheet slides in from any edge and is dismissed with Escape, the overlay or a close button. A Drawer is bottom-anchored and adds a drag gesture — but the gesture is never the only way out.",
|
|
29
|
+
},
|
|
30
|
+
{ role: "user" as const, text: "When would I use one over the other?" },
|
|
31
|
+
{
|
|
32
|
+
role: "assistant" as const,
|
|
33
|
+
text: "Use a Sheet for secondary content on any viewport: filters, details, navigation. Use a Drawer when the surface is primarily touch-driven and dragging it away is the natural gesture.",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<div className="flex h-96 w-[34rem] flex-col rounded-xl border border-border">
|
|
40
|
+
<Conversation>
|
|
41
|
+
<ConversationMessages>
|
|
42
|
+
{TURNS.map((turn, index) => (
|
|
43
|
+
<Message key={index} from={turn.role}>
|
|
44
|
+
{turn.role === "assistant" ? <MessageAvatar>AI</MessageAvatar> : null}
|
|
45
|
+
<MessageBody from={turn.role}>
|
|
46
|
+
{turn.role === "assistant" ? <Response>{turn.text}</Response> : turn.text}
|
|
47
|
+
</MessageBody>
|
|
48
|
+
</Message>
|
|
49
|
+
))}
|
|
50
|
+
</ConversationMessages>
|
|
51
|
+
<ConversationScrollButton />
|
|
52
|
+
</Conversation>
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* State goes in the status region; the response text never does. A live region
|
|
59
|
+
* that updates per token is unusable with a screen reader.
|
|
60
|
+
*/
|
|
61
|
+
export const Streaming: Story = {
|
|
62
|
+
render: function Streaming() {
|
|
63
|
+
const [streaming, setStreaming] = useState(false);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex h-96 w-[34rem] flex-col rounded-xl border border-border">
|
|
67
|
+
<Conversation>
|
|
68
|
+
<ConversationMessages>
|
|
69
|
+
<Message from="user">
|
|
70
|
+
<MessageBody from="user">Explain streaming responses.</MessageBody>
|
|
71
|
+
</Message>
|
|
72
|
+
<Message from="assistant">
|
|
73
|
+
<MessageAvatar>AI</MessageAvatar>
|
|
74
|
+
<MessageBody from="assistant">
|
|
75
|
+
{streaming ? (
|
|
76
|
+
<Response streaming>Tokens arrive one at a time, and the caret</Response>
|
|
77
|
+
) : (
|
|
78
|
+
<ThinkingIndicator />
|
|
79
|
+
)}
|
|
80
|
+
</MessageBody>
|
|
81
|
+
</Message>
|
|
82
|
+
</ConversationMessages>
|
|
83
|
+
<ConversationStatus>
|
|
84
|
+
{streaming ? "Generating response" : "Waiting for the first token"}
|
|
85
|
+
</ConversationStatus>
|
|
86
|
+
</Conversation>
|
|
87
|
+
<div className="border-t border-border p-3">
|
|
88
|
+
<Button
|
|
89
|
+
size="sm"
|
|
90
|
+
variant="outline"
|
|
91
|
+
onClick={() => {
|
|
92
|
+
setStreaming((current) => !current);
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
Toggle streaming
|
|
96
|
+
</Button>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Scroll up: following stops, and an explicit way back appears. */
|
|
104
|
+
export const LongTranscript: Story = {
|
|
105
|
+
render: () => (
|
|
106
|
+
<div className="flex h-96 w-[34rem] flex-col rounded-xl border border-border">
|
|
107
|
+
<Conversation>
|
|
108
|
+
<ConversationMessages>
|
|
109
|
+
{Array.from({ length: 14 }, (_, index) => {
|
|
110
|
+
const role = index % 2 === 0 ? ("user" as const) : ("assistant" as const);
|
|
111
|
+
return (
|
|
112
|
+
<Message key={index} from={role}>
|
|
113
|
+
{role === "assistant" ? <MessageAvatar>AI</MessageAvatar> : null}
|
|
114
|
+
<MessageBody from={role}>
|
|
115
|
+
Turn {index + 1}.{" "}
|
|
116
|
+
{role === "assistant" ? "An answer of some length." : "A question."}
|
|
117
|
+
</MessageBody>
|
|
118
|
+
</Message>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</ConversationMessages>
|
|
122
|
+
<ConversationScrollButton />
|
|
123
|
+
</Conversation>
|
|
124
|
+
</div>
|
|
125
|
+
),
|
|
126
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { expectNoA11yViolations } from "../../../test/a11y";
|
|
6
|
+
import { Message, MessageBody } from "../ai-message";
|
|
7
|
+
import {
|
|
8
|
+
Conversation,
|
|
9
|
+
ConversationMessages,
|
|
10
|
+
ConversationScrollButton,
|
|
11
|
+
ConversationStatus,
|
|
12
|
+
} from "./ai-conversation";
|
|
13
|
+
|
|
14
|
+
function Example({ status }: { status?: string } = {}) {
|
|
15
|
+
return (
|
|
16
|
+
<Conversation>
|
|
17
|
+
<ConversationMessages>
|
|
18
|
+
<Message from="user">
|
|
19
|
+
<MessageBody from="user">What is the capital of France?</MessageBody>
|
|
20
|
+
</Message>
|
|
21
|
+
<Message from="assistant">
|
|
22
|
+
<MessageBody from="assistant">Paris.</MessageBody>
|
|
23
|
+
</Message>
|
|
24
|
+
</ConversationMessages>
|
|
25
|
+
{status ? <ConversationStatus>{status}</ConversationStatus> : null}
|
|
26
|
+
<ConversationScrollButton />
|
|
27
|
+
</Conversation>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** jsdom has no layout, so scroll geometry is stubbed to drive the logic. */
|
|
32
|
+
function stubScroll(
|
|
33
|
+
viewport: HTMLElement,
|
|
34
|
+
{ scrollTop = 0, scrollHeight = 1000, clientHeight = 400 } = {},
|
|
35
|
+
) {
|
|
36
|
+
Object.defineProperty(viewport, "scrollHeight", { value: scrollHeight, configurable: true });
|
|
37
|
+
Object.defineProperty(viewport, "clientHeight", { value: clientHeight, configurable: true });
|
|
38
|
+
Object.defineProperty(viewport, "scrollTop", {
|
|
39
|
+
value: scrollTop,
|
|
40
|
+
writable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function viewportOf(container: HTMLElement): HTMLElement {
|
|
46
|
+
const viewport = container.querySelector<HTMLElement>("[data-slot='conversation-viewport']");
|
|
47
|
+
if (!viewport) throw new Error("no viewport");
|
|
48
|
+
return viewport;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("Conversation", () => {
|
|
52
|
+
it("renders the transcript as an ordered list", () => {
|
|
53
|
+
render(<Example />);
|
|
54
|
+
expect(screen.getByRole("list").tagName).toBe("OL");
|
|
55
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("does NOT make the transcript a live region", () => {
|
|
59
|
+
// The central accessibility decision of this component. A live region that
|
|
60
|
+
// updates per streamed token is unusable with a screen reader.
|
|
61
|
+
const list = render(<Example />).container.querySelector(
|
|
62
|
+
"[data-slot='conversation-messages']",
|
|
63
|
+
);
|
|
64
|
+
expect(list).not.toHaveAttribute("aria-live");
|
|
65
|
+
expect(list).not.toHaveAttribute("role", "log");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("announces state, not content, through a separate status region", () => {
|
|
69
|
+
render(<Example status="Generating response" />);
|
|
70
|
+
|
|
71
|
+
const status = screen.getByRole("status");
|
|
72
|
+
expect(status).toHaveAttribute("aria-live", "polite");
|
|
73
|
+
expect(status).toHaveTextContent("Generating response");
|
|
74
|
+
// The transcript text must not be inside the live region.
|
|
75
|
+
expect(status).not.toHaveTextContent("Paris.");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("the jump-to-latest control", () => {
|
|
79
|
+
it("is absent while the reader is at the bottom", () => {
|
|
80
|
+
render(<Example />);
|
|
81
|
+
expect(screen.queryByRole("button", { name: "Jump to latest" })).not.toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("appears once the reader scrolls away", async () => {
|
|
85
|
+
const { container } = render(<Example />);
|
|
86
|
+
const viewport = viewportOf(container);
|
|
87
|
+
|
|
88
|
+
stubScroll(viewport, { scrollTop: 0 });
|
|
89
|
+
fireEvent.scroll(viewport);
|
|
90
|
+
|
|
91
|
+
expect(await screen.findByRole("button", { name: "Jump to latest" })).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("scrolls back down and then disappears", async () => {
|
|
95
|
+
const user = userEvent.setup();
|
|
96
|
+
const { container } = render(<Example />);
|
|
97
|
+
const viewport = viewportOf(container);
|
|
98
|
+
const scrollTo = vi.fn();
|
|
99
|
+
viewport.scrollTo = scrollTo;
|
|
100
|
+
|
|
101
|
+
stubScroll(viewport, { scrollTop: 0 });
|
|
102
|
+
fireEvent.scroll(viewport);
|
|
103
|
+
await user.click(await screen.findByRole("button", { name: "Jump to latest" }));
|
|
104
|
+
|
|
105
|
+
expect(scrollTo).toHaveBeenCalledWith({ top: 1000, behavior: "smooth" });
|
|
106
|
+
|
|
107
|
+
stubScroll(viewport, { scrollTop: 600 });
|
|
108
|
+
fireEvent.scroll(viewport);
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
expect(
|
|
111
|
+
screen.queryByRole("button", { name: "Jump to latest" }),
|
|
112
|
+
).not.toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("counts near-the-bottom as the bottom, so a pixel of drift is not a prompt", async () => {
|
|
117
|
+
const { container } = render(<Example />);
|
|
118
|
+
const viewport = viewportOf(container);
|
|
119
|
+
|
|
120
|
+
stubScroll(viewport, { scrollTop: 580 });
|
|
121
|
+
fireEvent.scroll(viewport);
|
|
122
|
+
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
expect(container.querySelector("[data-slot='conversation']")).toHaveAttribute(
|
|
125
|
+
"data-at-bottom",
|
|
126
|
+
"true",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("throws a useful error if the scroll button is used outside the root", () => {
|
|
133
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
134
|
+
expect(() => render(<ConversationScrollButton />)).toThrow(
|
|
135
|
+
/must be rendered inside <Conversation>/,
|
|
136
|
+
);
|
|
137
|
+
consoleError.mockRestore();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("has no accessibility violations", async () => {
|
|
141
|
+
const { container } = render(<Example status="Response complete" />);
|
|
142
|
+
await expectNoA11yViolations(container);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useLayoutEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
type ComponentPropsWithRef,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
} from "react";
|
|
15
|
+
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The scrolling container for a conversation.
|
|
20
|
+
*
|
|
21
|
+
* Two problems here are easy to get wrong and unpleasant to live with.
|
|
22
|
+
*
|
|
23
|
+
* **Scrolling.** A chat should follow new content, but only while the reader is
|
|
24
|
+
* already at the bottom. The moment someone scrolls up to re-read something,
|
|
25
|
+
* yanking them back down is worse than not following at all — so this tracks
|
|
26
|
+
* whether they are pinned to the bottom and stops following as soon as they are
|
|
27
|
+
* not, offering an explicit way back instead.
|
|
28
|
+
*
|
|
29
|
+
* **Announcements.** The obvious thing is to make the transcript a live region.
|
|
30
|
+
* Do not: a live region that updates on every token produces a stream of
|
|
31
|
+
* interruptions that is unusable with a screen reader, and it is the single
|
|
32
|
+
* most common accessibility failure in chat interfaces. The transcript here is
|
|
33
|
+
* an ordinary list, navigable at the reader's pace, and a *separate* status
|
|
34
|
+
* region announces state — "generating", "response complete" — never content.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface ConversationContextValue {
|
|
38
|
+
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
39
|
+
atBottom: boolean;
|
|
40
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ConversationContext = createContext<ConversationContextValue | null>(null);
|
|
44
|
+
|
|
45
|
+
function useConversation(component: string): ConversationContextValue {
|
|
46
|
+
const context = useContext(ConversationContext);
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error(`${component} must be rendered inside <Conversation>.`);
|
|
49
|
+
}
|
|
50
|
+
return context;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** How close to the bottom still counts as "at the bottom", in pixels. */
|
|
54
|
+
const BOTTOM_THRESHOLD = 32;
|
|
55
|
+
|
|
56
|
+
export interface ConversationProps extends ComponentPropsWithRef<"div"> {
|
|
57
|
+
/**
|
|
58
|
+
* Follow new content while the reader is at the bottom.
|
|
59
|
+
*
|
|
60
|
+
* Turning this off never scrolls automatically; it does not pin the view.
|
|
61
|
+
*/
|
|
62
|
+
autoScroll?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function Conversation({
|
|
66
|
+
className,
|
|
67
|
+
autoScroll = true,
|
|
68
|
+
children,
|
|
69
|
+
...props
|
|
70
|
+
}: ConversationProps) {
|
|
71
|
+
const viewportRef = useRef<HTMLDivElement | null>(null);
|
|
72
|
+
const [atBottom, setAtBottom] = useState(true);
|
|
73
|
+
|
|
74
|
+
const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
|
|
75
|
+
const viewport = viewportRef.current;
|
|
76
|
+
if (!viewport) return;
|
|
77
|
+
viewport.scrollTo({ top: viewport.scrollHeight, behavior });
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const viewport = viewportRef.current;
|
|
82
|
+
if (!viewport) return;
|
|
83
|
+
|
|
84
|
+
function handleScroll() {
|
|
85
|
+
if (!viewport) return;
|
|
86
|
+
const distance = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
87
|
+
setAtBottom(distance <= BOTTOM_THRESHOLD);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
handleScroll();
|
|
91
|
+
viewport.addEventListener("scroll", handleScroll, { passive: true });
|
|
92
|
+
return () => {
|
|
93
|
+
viewport.removeEventListener("scroll", handleScroll);
|
|
94
|
+
};
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
// Follows content as it grows, but only while the reader is pinned to the
|
|
98
|
+
// bottom. Layout effect so the jump happens before paint rather than as a
|
|
99
|
+
// visible lurch.
|
|
100
|
+
useLayoutEffect(() => {
|
|
101
|
+
const viewport = viewportRef.current;
|
|
102
|
+
if (!viewport || !autoScroll) return;
|
|
103
|
+
|
|
104
|
+
const observer = new ResizeObserver(() => {
|
|
105
|
+
if (atBottom) viewport.scrollTop = viewport.scrollHeight;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
for (const child of viewport.children) observer.observe(child);
|
|
109
|
+
return () => {
|
|
110
|
+
observer.disconnect();
|
|
111
|
+
};
|
|
112
|
+
}, [autoScroll, atBottom]);
|
|
113
|
+
|
|
114
|
+
const context = useMemo<ConversationContextValue>(
|
|
115
|
+
() => ({ viewportRef, atBottom, scrollToBottom }),
|
|
116
|
+
[atBottom, scrollToBottom],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ConversationContext.Provider value={context}>
|
|
121
|
+
<div
|
|
122
|
+
data-slot="conversation"
|
|
123
|
+
data-at-bottom={atBottom || undefined}
|
|
124
|
+
className={cn("relative flex min-h-0 flex-1 flex-col", className)}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
ref={viewportRef}
|
|
129
|
+
data-slot="conversation-viewport"
|
|
130
|
+
className="flex-1 overflow-y-auto overscroll-contain"
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</ConversationContext.Provider>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The transcript.
|
|
141
|
+
*
|
|
142
|
+
* An ordered list, deliberately not a live region — see the note on
|
|
143
|
+
* `Conversation`. Order is meaning here, and list semantics give a screen
|
|
144
|
+
* reader user position and count ("3 of 12") as they move through it.
|
|
145
|
+
*/
|
|
146
|
+
export function ConversationMessages({ className, ...props }: ComponentPropsWithRef<"ol">) {
|
|
147
|
+
return (
|
|
148
|
+
<ol
|
|
149
|
+
data-slot="conversation-messages"
|
|
150
|
+
className={cn("flex flex-col gap-6 p-4", className)}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ConversationStatusProps extends ComponentPropsWithRef<"div"> {
|
|
157
|
+
/** Announced politely when it changes. Keep it short: state, never content. */
|
|
158
|
+
children?: ReactNode;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A polite live region for conversation state.
|
|
163
|
+
*
|
|
164
|
+
* This is where "generating response" and "response complete" belong. Putting
|
|
165
|
+
* the response *text* here instead is the mistake this component exists to
|
|
166
|
+
* prevent.
|
|
167
|
+
*/
|
|
168
|
+
export function ConversationStatus({ className, children, ...props }: ConversationStatusProps) {
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
data-slot="conversation-status"
|
|
172
|
+
role="status"
|
|
173
|
+
aria-live="polite"
|
|
174
|
+
className={cn("px-4 text-xs text-muted-foreground", className)}
|
|
175
|
+
{...props}
|
|
176
|
+
>
|
|
177
|
+
{children}
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface ConversationScrollButtonProps extends ComponentPropsWithRef<"button"> {
|
|
183
|
+
label?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns the reader to the newest message.
|
|
188
|
+
*
|
|
189
|
+
* Rendered only when they have scrolled away, and removed from the DOM
|
|
190
|
+
* otherwise so it is never a focus stop pointing at where the reader already
|
|
191
|
+
* is.
|
|
192
|
+
*/
|
|
193
|
+
export function ConversationScrollButton({
|
|
194
|
+
className,
|
|
195
|
+
label = "Jump to latest",
|
|
196
|
+
...props
|
|
197
|
+
}: ConversationScrollButtonProps) {
|
|
198
|
+
const { atBottom, scrollToBottom } = useConversation("ConversationScrollButton");
|
|
199
|
+
if (atBottom) return null;
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
data-slot="conversation-scroll-button"
|
|
205
|
+
onClick={() => {
|
|
206
|
+
scrollToBottom();
|
|
207
|
+
}}
|
|
208
|
+
className={cn(
|
|
209
|
+
"absolute bottom-3 left-1/2 z-[var(--z-sticky)] flex -translate-x-1/2 items-center gap-1.5",
|
|
210
|
+
"rounded-full border border-border bg-popover px-3 py-1.5 text-xs font-medium shadow-md",
|
|
211
|
+
"transition-colors duration-[var(--duration-fast)] hover:bg-accent",
|
|
212
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring/55",
|
|
213
|
+
className,
|
|
214
|
+
)}
|
|
215
|
+
{...props}
|
|
216
|
+
>
|
|
217
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="size-3.5">
|
|
218
|
+
<path
|
|
219
|
+
d="M12 5v14m0 0-6-6m6 6 6-6"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
strokeWidth="2"
|
|
222
|
+
strokeLinecap="round"
|
|
223
|
+
strokeLinejoin="round"
|
|
224
|
+
/>
|
|
225
|
+
</svg>
|
|
226
|
+
{label}
|
|
227
|
+
</button>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "ai-conversation",
|
|
5
|
+
title: "AI Conversation",
|
|
6
|
+
description:
|
|
7
|
+
"The scrolling transcript container, with follow-on-new-content and a status region.",
|
|
8
|
+
category: "ai",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
registryDependencies: [],
|
|
12
|
+
files: ["ai-conversation.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
"The transcript is an ordered list, NOT a live region. A live region that updates on every " +
|
|
15
|
+
"streamed token is unusable with a screen reader, and is the most common accessibility " +
|
|
16
|
+
'failure in chat interfaces. Announce state — "generating", "response complete" — through ' +
|
|
17
|
+
"ConversationStatus instead, and let the reader navigate the transcript at their own pace. " +
|
|
18
|
+
"Auto-scroll stops the moment the reader scrolls up, and ConversationScrollButton is the " +
|
|
19
|
+
"explicit way back.",
|
|
20
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Decorator, Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Copy, RefreshCw, ThumbsDown, ThumbsUp } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Response } from "@/components/ai-response";
|
|
5
|
+
import { Button } from "@/components/button";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Message,
|
|
9
|
+
MessageActions,
|
|
10
|
+
MessageAvatar,
|
|
11
|
+
MessageBody,
|
|
12
|
+
MessageFooter,
|
|
13
|
+
} from "./ai-message";
|
|
14
|
+
|
|
15
|
+
/** Named so its type is nameable in declaration output (TS2883). */
|
|
16
|
+
const withFixedWidth: Decorator = (Story) => (
|
|
17
|
+
<ol className="flex w-[32rem] flex-col gap-6">
|
|
18
|
+
<Story />
|
|
19
|
+
</ol>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const meta = {
|
|
23
|
+
title: "AI/Message",
|
|
24
|
+
component: Message,
|
|
25
|
+
args: { from: "assistant" },
|
|
26
|
+
argTypes: { from: { control: "inline-radio", options: ["user", "assistant", "system"] } },
|
|
27
|
+
decorators: [withFixedWidth],
|
|
28
|
+
} satisfies Meta<typeof Message>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
render: (args) => (
|
|
35
|
+
<Message {...args}>
|
|
36
|
+
{args.from === "assistant" ? <MessageAvatar>AI</MessageAvatar> : null}
|
|
37
|
+
<MessageBody from={args.from}>
|
|
38
|
+
{args.from === "user"
|
|
39
|
+
? "How do I make a table keyboard-scrollable?"
|
|
40
|
+
: 'Give the scrolling wrapper tabindex="0" and an accessible name. Without it, columns past the edge are unreachable by keyboard.'}
|
|
41
|
+
</MessageBody>
|
|
42
|
+
</Message>
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Roles: Story = {
|
|
47
|
+
parameters: { controls: { disable: true } },
|
|
48
|
+
render: () => (
|
|
49
|
+
<>
|
|
50
|
+
<Message from="system">
|
|
51
|
+
<MessageBody from="system">Conversation started with the Balanced model.</MessageBody>
|
|
52
|
+
</Message>
|
|
53
|
+
<Message from="user">
|
|
54
|
+
<MessageBody from="user">What is the capital of France?</MessageBody>
|
|
55
|
+
</Message>
|
|
56
|
+
<Message from="assistant">
|
|
57
|
+
<MessageAvatar>AI</MessageAvatar>
|
|
58
|
+
<MessageBody from="assistant">
|
|
59
|
+
<Response>Paris.</Response>
|
|
60
|
+
</MessageBody>
|
|
61
|
+
</Message>
|
|
62
|
+
</>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Actions fade in on hover but stay in the tab order the whole time. */
|
|
67
|
+
export const WithActions: Story = {
|
|
68
|
+
parameters: { controls: { disable: true } },
|
|
69
|
+
render: () => (
|
|
70
|
+
<Message from="assistant">
|
|
71
|
+
<MessageAvatar>AI</MessageAvatar>
|
|
72
|
+
<MessageBody from="assistant">
|
|
73
|
+
<Response>
|
|
74
|
+
A Sheet slides in from an edge; a Drawer is bottom-anchored and can be dragged away.
|
|
75
|
+
</Response>
|
|
76
|
+
<MessageActions>
|
|
77
|
+
{[
|
|
78
|
+
{ icon: Copy, label: "Copy" },
|
|
79
|
+
{ icon: RefreshCw, label: "Regenerate" },
|
|
80
|
+
{ icon: ThumbsUp, label: "Good response" },
|
|
81
|
+
{ icon: ThumbsDown, label: "Bad response" },
|
|
82
|
+
].map(({ icon: Icon, label }) => (
|
|
83
|
+
<Button key={label} variant="ghost" size="icon-sm" aria-label={label}>
|
|
84
|
+
<Icon />
|
|
85
|
+
</Button>
|
|
86
|
+
))}
|
|
87
|
+
</MessageActions>
|
|
88
|
+
</MessageBody>
|
|
89
|
+
</Message>
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const WithFooter: Story = {
|
|
94
|
+
parameters: { controls: { disable: true } },
|
|
95
|
+
render: () => (
|
|
96
|
+
<Message from="assistant">
|
|
97
|
+
<MessageAvatar>AI</MessageAvatar>
|
|
98
|
+
<MessageBody from="assistant">
|
|
99
|
+
<Response>Paris has been the capital since 987.</Response>
|
|
100
|
+
<MessageFooter>
|
|
101
|
+
<span className="text-2xs text-muted-foreground">2 sources · 148 tokens</span>
|
|
102
|
+
</MessageFooter>
|
|
103
|
+
</MessageBody>
|
|
104
|
+
</Message>
|
|
105
|
+
),
|
|
106
|
+
};
|