@adia-ai/web-components 0.6.33 → 0.6.35
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/CHANGELOG.md +64 -0
- package/color/index.js +1 -1
- package/components/accordion/accordion-item.yaml +2 -2
- package/components/accordion/accordion.css +2 -2
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-item.yaml +2 -2
- package/components/action-list/action-list.css +2 -2
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/{class.js → agent-artifact.class.js} +1 -1
- package/components/agent-artifact/agent-artifact.css +31 -31
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.css +57 -57
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.css +62 -62
- package/components/agent-reasoning/agent-reasoning.js +1 -1
- package/components/agent-suggestions/agent-suggestions.css +4 -4
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/agent-trace/agent-trace.css +53 -53
- package/components/alert/alert.a2ui.json +64 -1
- package/components/alert/{class.js → alert.class.js} +189 -2
- package/components/alert/alert.css +119 -41
- package/components/alert/alert.d.ts +14 -0
- package/components/alert/alert.js +1 -1
- package/components/alert/alert.test.js +184 -0
- package/components/alert/alert.yaml +114 -1
- package/components/avatar/avatar-group.yaml +2 -2
- package/components/avatar/avatar.css +27 -27
- package/components/avatar/avatar.js +1 -1
- package/components/badge/badge.css +27 -27
- package/components/badge/badge.js +1 -1
- package/components/block/block.css +16 -16
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.css +23 -23
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.css +101 -91
- package/components/button/button.js +1 -1
- package/components/calendar-grid/calendar-grid.a2ui.json +146 -0
- package/components/calendar-grid/calendar-grid.class.js +326 -0
- package/components/calendar-grid/calendar-grid.css +246 -0
- package/components/calendar-grid/calendar-grid.d.ts +41 -0
- package/components/calendar-grid/calendar-grid.js +17 -0
- package/components/calendar-grid/calendar-grid.yaml +136 -0
- package/components/calendar-picker/calendar-picker.css +139 -139
- package/components/calendar-picker/calendar-picker.js +1 -1
- package/components/canvas/canvas.css +12 -12
- package/components/card/card.css +83 -83
- package/components/card/card.js +1 -1
- package/components/chart/chart.css +224 -224
- package/components/chart/chart.js +1 -1
- package/components/chart-legend/chart-legend.css +26 -26
- package/components/chart-legend/chart-legend.js +1 -1
- package/components/chat-thread/chat-input.a2ui.json +1 -1
- package/components/chat-thread/chat-input.js +6 -1
- package/components/chat-thread/chat-input.yaml +4 -1
- package/components/chat-thread/chat-thread.js +1 -1
- package/components/check/check.css +40 -40
- package/components/check/check.js +1 -1
- package/components/code/code.css +125 -125
- package/components/code/code.js +1 -1
- package/components/col/col.css +15 -15
- package/components/col/col.js +1 -1
- package/components/color-input/color-input.js +1 -1
- package/components/color-picker/color-picker.css +55 -55
- package/components/color-picker/color-picker.js +1 -1
- package/components/combobox/combobox.a2ui.json +363 -0
- package/components/combobox/combobox.class.js +861 -0
- package/components/combobox/combobox.css +244 -0
- package/components/combobox/combobox.d.ts +113 -0
- package/components/combobox/combobox.examples.md +59 -0
- package/components/combobox/combobox.js +17 -0
- package/components/combobox/combobox.test.js +181 -0
- package/components/combobox/combobox.yaml +369 -0
- package/components/command/command.css +90 -90
- package/components/command/command.js +1 -1
- package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
- package/components/date-range-picker/date-range-picker.class.js +791 -0
- package/components/date-range-picker/date-range-picker.css +224 -0
- package/components/date-range-picker/date-range-picker.d.ts +82 -0
- package/components/date-range-picker/date-range-picker.examples.md +37 -0
- package/components/date-range-picker/date-range-picker.js +17 -0
- package/components/date-range-picker/date-range-picker.test.js +387 -0
- package/components/date-range-picker/date-range-picker.yaml +285 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
- package/components/datetime-picker/datetime-picker.class.js +706 -0
- package/components/datetime-picker/datetime-picker.css +150 -0
- package/components/datetime-picker/datetime-picker.d.ts +86 -0
- package/components/datetime-picker/datetime-picker.examples.md +46 -0
- package/components/datetime-picker/datetime-picker.js +17 -0
- package/components/datetime-picker/datetime-picker.test.js +454 -0
- package/components/datetime-picker/datetime-picker.yaml +332 -0
- package/components/demo-toggle/demo-toggle.css +27 -27
- package/components/demo-toggle/demo-toggle.js +1 -1
- package/components/description-list/description-list.css +18 -18
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.css +24 -24
- package/components/divider/divider.js +1 -1
- package/components/drawer/drawer.js +1 -1
- package/components/embed/embed.css +6 -6
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.css +27 -27
- package/components/empty-state/empty-state.js +1 -1
- package/components/feed/feed.css +12 -12
- package/components/feed/feed.js +1 -1
- package/components/field/field.css +28 -28
- package/components/field/field.js +1 -1
- package/components/field/field.test.js +1 -1
- package/components/fields/fields.css +5 -5
- package/components/fields/fields.js +1 -1
- package/components/grid/grid.css +5 -5
- package/components/grid/grid.js +1 -1
- package/components/heatmap/heatmap.css +63 -63
- package/components/heatmap/heatmap.js +1 -1
- package/components/icon/icon.css +12 -12
- package/components/icon/icon.js +1 -1
- package/components/image/image.css +14 -14
- package/components/image/image.js +1 -1
- package/components/index.js +11 -0
- package/components/inline-message/inline-message.a2ui.json +143 -0
- package/components/inline-message/inline-message.class.js +169 -0
- package/components/inline-message/inline-message.css +75 -0
- package/components/inline-message/inline-message.d.ts +31 -0
- package/components/inline-message/inline-message.examples.md +19 -0
- package/components/inline-message/inline-message.js +17 -0
- package/components/inline-message/inline-message.test.js +203 -0
- package/components/inline-message/inline-message.yaml +205 -0
- package/components/input/input.css +67 -67
- package/components/input/input.js +1 -1
- package/components/input/input.yaml +5 -4
- package/components/inspector/inspector.css +6 -6
- package/components/inspector/inspector.js +1 -1
- package/components/integration-card/integration-card.a2ui.json +268 -0
- package/components/integration-card/integration-card.class.js +410 -0
- package/components/integration-card/integration-card.css +169 -0
- package/components/integration-card/integration-card.d.ts +63 -0
- package/components/integration-card/integration-card.examples.md +41 -0
- package/components/integration-card/integration-card.js +17 -0
- package/components/integration-card/integration-card.test.js +306 -0
- package/components/integration-card/integration-card.yaml +280 -0
- package/components/kbd/kbd.css +32 -32
- package/components/kbd/kbd.js +1 -1
- package/components/link/link.css +12 -12
- package/components/link/link.js +1 -1
- package/components/list/list-item.yaml +2 -2
- package/components/list/list.css +8 -8
- package/components/list/list.js +1 -1
- package/components/list-window/list-window.a2ui.json +277 -0
- package/components/list-window/list-window.class.js +688 -0
- package/components/list-window/list-window.css +124 -0
- package/components/list-window/list-window.d.ts +84 -0
- package/components/list-window/list-window.examples.md +73 -0
- package/components/list-window/list-window.js +17 -0
- package/components/list-window/list-window.test.js +303 -0
- package/components/list-window/list-window.yaml +270 -0
- package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
- package/components/loading-overlay/loading-overlay.class.js +203 -0
- package/components/loading-overlay/loading-overlay.css +81 -0
- package/components/loading-overlay/loading-overlay.d.ts +24 -0
- package/components/loading-overlay/loading-overlay.examples.md +50 -0
- package/components/loading-overlay/loading-overlay.js +17 -0
- package/components/loading-overlay/loading-overlay.test.js +257 -0
- package/components/loading-overlay/loading-overlay.yaml +260 -0
- package/components/menu/menu-divider.yaml +1 -1
- package/components/menu/menu-item.yaml +1 -1
- package/components/menu/menu.a2ui.json +3 -0
- package/components/menu/menu.css +8 -8
- package/components/menu/menu.js +1 -1
- package/components/menu/menu.yaml +7 -0
- package/components/modal/{class.js → modal.class.js} +12 -1
- package/components/modal/modal.css +54 -44
- package/components/modal/modal.js +1 -1
- package/components/nav/nav.css +40 -40
- package/components/nav/nav.js +1 -1
- package/components/nav-group/nav-group.css +52 -52
- package/components/nav-group/nav-group.js +1 -1
- package/components/nav-item/nav-item.css +44 -44
- package/components/nav-item/nav-item.js +1 -1
- package/components/noodles/noodles.css +31 -31
- package/components/noodles/noodles.js +1 -1
- package/components/option-card/option-card.css +69 -69
- package/components/option-card/option-card.js +1 -1
- package/components/otp-input/otp-input.css +30 -30
- package/components/otp-input/otp-input.js +1 -1
- package/components/page/page.css +18 -18
- package/components/page/page.js +1 -1
- package/components/pagination/pagination.css +61 -61
- package/components/pagination/pagination.js +1 -1
- package/components/pane/pane.css +57 -57
- package/components/pane/pane.js +1 -1
- package/components/pipeline-status/pipeline-status.css +65 -65
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.a2ui.json +8 -1
- package/components/popover/popover.css +17 -17
- package/components/popover/popover.js +1 -1
- package/components/popover/popover.yaml +14 -1
- package/components/progress/progress.css +23 -23
- package/components/progress/progress.js +1 -1
- package/components/progress-row/progress-row.css +17 -17
- package/components/progress-row/progress-row.js +1 -1
- package/components/radio/radio.css +39 -39
- package/components/radio/radio.js +1 -1
- package/components/range/range.css +55 -55
- package/components/range/range.js +1 -1
- package/components/rating/rating.css +28 -28
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.css +133 -133
- package/components/richtext/richtext.js +1 -1
- package/components/row/row.css +19 -19
- package/components/row/row.js +1 -1
- package/components/search/search.css +5 -5
- package/components/search/search.js +1 -1
- package/components/segment/segment.css +24 -24
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.css +25 -25
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.a2ui.json +58 -4
- package/components/select/{class.js → select.class.js} +415 -6
- package/components/select/select.css +242 -84
- package/components/select/select.d.ts +31 -1
- package/components/select/select.js +1 -1
- package/components/select/select.test.js +202 -0
- package/components/select/select.yaml +126 -5
- package/components/skeleton/skeleton.css +14 -14
- package/components/skeleton/skeleton.js +1 -1
- package/components/slider/slider.css +46 -46
- package/components/slider/slider.js +1 -1
- package/components/spinner/spinner.a2ui.json +198 -0
- package/components/spinner/spinner.class.js +99 -0
- package/components/spinner/spinner.css +221 -0
- package/components/spinner/spinner.d.ts +26 -0
- package/components/spinner/spinner.examples.md +26 -0
- package/components/spinner/spinner.js +17 -0
- package/components/spinner/spinner.test.js +272 -0
- package/components/spinner/spinner.yaml +238 -0
- package/components/stack/stack.css +11 -11
- package/components/stack/stack.js +1 -1
- package/components/stat/stat.css +25 -25
- package/components/step-progress/step-progress.css +20 -20
- package/components/step-progress/step-progress.js +1 -1
- package/components/stepper/stepper-item.yaml +1 -1
- package/components/stepper/stepper.css +29 -29
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.css +12 -12
- package/components/stream/stream.js +1 -1
- package/components/swatch/swatch.css +68 -68
- package/components/swatch/swatch.js +1 -1
- package/components/swiper/swiper.css +57 -57
- package/components/swiper/swiper.js +1 -1
- package/components/switch/switch.css +52 -52
- package/components/switch/switch.js +1 -1
- package/components/table/table.css +163 -163
- package/components/table/table.js +1 -1
- package/components/table-toolbar/{class.js → table-toolbar.class.js} +1 -1
- package/components/table-toolbar/table-toolbar.css +32 -32
- package/components/table-toolbar/table-toolbar.js +1 -1
- package/components/tabs/tab.yaml +2 -2
- package/components/tabs/tabs.css +51 -51
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.css +48 -48
- package/components/tag/tag.js +1 -1
- package/components/tags-input/tags-input.a2ui.json +337 -0
- package/components/tags-input/tags-input.class.js +776 -0
- package/components/tags-input/tags-input.css +201 -0
- package/components/tags-input/tags-input.d.ts +120 -0
- package/components/tags-input/tags-input.examples.md +92 -0
- package/components/tags-input/tags-input.js +17 -0
- package/components/tags-input/tags-input.test.js +368 -0
- package/components/tags-input/tags-input.yaml +367 -0
- package/components/text/text.css +44 -44
- package/components/text/text.js +1 -1
- package/components/textarea/textarea.a2ui.json +1 -1
- package/components/textarea/textarea.css +46 -46
- package/components/textarea/textarea.js +1 -1
- package/components/textarea/textarea.yaml +11 -8
- package/components/time-picker/time-picker.a2ui.json +267 -0
- package/components/time-picker/time-picker.class.js +693 -0
- package/components/time-picker/time-picker.css +122 -0
- package/components/time-picker/time-picker.d.ts +75 -0
- package/components/time-picker/time-picker.examples.md +35 -0
- package/components/time-picker/time-picker.js +17 -0
- package/components/time-picker/time-picker.test.js +287 -0
- package/components/time-picker/time-picker.yaml +256 -0
- package/components/timeline/timeline-item.yaml +2 -2
- package/components/timeline/{class.js → timeline.class.js} +1 -1
- package/components/timeline/timeline.css +50 -50
- package/components/timeline/timeline.js +1 -1
- package/components/toast/toast.css +58 -58
- package/components/toast/toast.js +1 -1
- package/components/toggle-group/toggle-group.css +6 -6
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toggle-group/toggle-option.yaml +1 -1
- package/components/toggle-scheme/toggle-scheme.css +2 -2
- package/components/toggle-scheme/toggle-scheme.js +1 -1
- package/components/toolbar/toolbar-group.yaml +1 -1
- package/components/toolbar/toolbar.css +17 -17
- package/components/toolbar/toolbar.js +1 -1
- package/components/tooltip/tooltip.css +2 -2
- package/components/tooltip/tooltip.js +1 -1
- package/components/tree/tree-item.yaml +1 -1
- package/components/tree/tree.css +37 -37
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.css +49 -49
- package/components/upload/upload.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +146 -87
- package/package.json +3 -3
- package/styles/components.css +11 -0
- /package/components/accordion/{class.js → accordion.class.js} +0 -0
- /package/components/action-list/{class.js → action-list.class.js} +0 -0
- /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
- /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
- /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
- /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
- /package/components/avatar/{class.js → avatar.class.js} +0 -0
- /package/components/badge/{class.js → badge.class.js} +0 -0
- /package/components/block/{class.js → block.class.js} +0 -0
- /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
- /package/components/button/{class.js → button.class.js} +0 -0
- /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
- /package/components/card/{class.js → card.class.js} +0 -0
- /package/components/chart/{class.js → chart.class.js} +0 -0
- /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
- /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
- /package/components/check/{class.js → check.class.js} +0 -0
- /package/components/code/{class.js → code.class.js} +0 -0
- /package/components/col/{class.js → col.class.js} +0 -0
- /package/components/color-input/{class.js → color-input.class.js} +0 -0
- /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
- /package/components/command/{class.js → command.class.js} +0 -0
- /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
- /package/components/description-list/{class.js → description-list.class.js} +0 -0
- /package/components/divider/{class.js → divider.class.js} +0 -0
- /package/components/drawer/{class.js → drawer.class.js} +0 -0
- /package/components/embed/{class.js → embed.class.js} +0 -0
- /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
- /package/components/feed/{class.js → feed.class.js} +0 -0
- /package/components/field/{class.js → field.class.js} +0 -0
- /package/components/fields/{class.js → fields.class.js} +0 -0
- /package/components/grid/{class.js → grid.class.js} +0 -0
- /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
- /package/components/icon/{class.js → icon.class.js} +0 -0
- /package/components/image/{class.js → image.class.js} +0 -0
- /package/components/input/{class.js → input.class.js} +0 -0
- /package/components/inspector/{class.js → inspector.class.js} +0 -0
- /package/components/kbd/{class.js → kbd.class.js} +0 -0
- /package/components/link/{class.js → link.class.js} +0 -0
- /package/components/list/{class.js → list.class.js} +0 -0
- /package/components/menu/{class.js → menu.class.js} +0 -0
- /package/components/nav/{class.js → nav.class.js} +0 -0
- /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
- /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
- /package/components/noodles/{class.js → noodles.class.js} +0 -0
- /package/components/option-card/{class.js → option-card.class.js} +0 -0
- /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
- /package/components/page/{class.js → page.class.js} +0 -0
- /package/components/pagination/{class.js → pagination.class.js} +0 -0
- /package/components/pane/{class.js → pane.class.js} +0 -0
- /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
- /package/components/popover/{class.js → popover.class.js} +0 -0
- /package/components/progress/{class.js → progress.class.js} +0 -0
- /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
- /package/components/radio/{class.js → radio.class.js} +0 -0
- /package/components/range/{class.js → range.class.js} +0 -0
- /package/components/rating/{class.js → rating.class.js} +0 -0
- /package/components/richtext/{class.js → richtext.class.js} +0 -0
- /package/components/row/{class.js → row.class.js} +0 -0
- /package/components/search/{class.js → search.class.js} +0 -0
- /package/components/segment/{class.js → segment.class.js} +0 -0
- /package/components/segmented/{class.js → segmented.class.js} +0 -0
- /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
- /package/components/slider/{class.js → slider.class.js} +0 -0
- /package/components/stack/{class.js → stack.class.js} +0 -0
- /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
- /package/components/stepper/{class.js → stepper.class.js} +0 -0
- /package/components/stream/{class.js → stream.class.js} +0 -0
- /package/components/swatch/{class.js → swatch.class.js} +0 -0
- /package/components/swiper/{class.js → swiper.class.js} +0 -0
- /package/components/switch/{class.js → switch.class.js} +0 -0
- /package/components/table/{class.js → table.class.js} +0 -0
- /package/components/tabs/{class.js → tabs.class.js} +0 -0
- /package/components/tag/{class.js → tag.class.js} +0 -0
- /package/components/text/{class.js → text.class.js} +0 -0
- /package/components/textarea/{class.js → textarea.class.js} +0 -0
- /package/components/toast/{class.js → toast.class.js} +0 -0
- /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
- /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
- /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
- /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
- /package/components/tree/{class.js → tree.class.js} +0 -0
- /package/components/upload/{class.js → upload.class.js} +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spinner-ui tests — covers SPEC-001 § Verification.
|
|
3
|
+
*
|
|
4
|
+
* The component is a CSS-only animation (no stamped DOM, no JS timers).
|
|
5
|
+
* Tests fall in two categories:
|
|
6
|
+
*
|
|
7
|
+
* 1. Behavioral — props reflect to host attributes, defaults apply,
|
|
8
|
+
* ARIA wiring is in place, element is not a focus target.
|
|
9
|
+
* 2. CSS source — happy-dom doesn't resolve @scope rules through
|
|
10
|
+
* getComputedStyle(), so the size-ladder, tone-mapping, and
|
|
11
|
+
* reduced-motion fallback are validated against the CSS source
|
|
12
|
+
* directly (same recipe as tag-ui / description-list / text).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { dirname, resolve } from 'node:path';
|
|
19
|
+
|
|
20
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const SPINNER_CSS = readFileSync(resolve(HERE, 'spinner.css'), 'utf8');
|
|
22
|
+
|
|
23
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
await import('./spinner.js');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function mount(html) {
|
|
30
|
+
const wrap = document.createElement('div');
|
|
31
|
+
wrap.innerHTML = html;
|
|
32
|
+
document.body.appendChild(wrap);
|
|
33
|
+
return wrap.firstElementChild;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── 1. Defaults + property reflection ──────────────────────────────────
|
|
37
|
+
|
|
38
|
+
describe('spinner-ui — defaults', () => {
|
|
39
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
40
|
+
|
|
41
|
+
it('renders without props using documented defaults', async () => {
|
|
42
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
43
|
+
await tick();
|
|
44
|
+
expect(s.size).toBe('md');
|
|
45
|
+
expect(s.variant).toBe('arc');
|
|
46
|
+
expect(s.tone).toBe('current');
|
|
47
|
+
expect(s.paused).toBe(false);
|
|
48
|
+
expect(s.label).toBe('Loading');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('sets role="progressbar" on the host', async () => {
|
|
52
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
53
|
+
await tick();
|
|
54
|
+
expect(s.getAttribute('role')).toBe('progressbar');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('sets aria-busy="true" on the host', async () => {
|
|
58
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
59
|
+
await tick();
|
|
60
|
+
expect(s.getAttribute('aria-busy')).toBe('true');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('sets aria-valuetext to the default label', async () => {
|
|
64
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
65
|
+
await tick();
|
|
66
|
+
expect(s.getAttribute('aria-valuetext')).toBe('Loading');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ── 2. Reflected props ─────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
describe('spinner-ui — reflected attributes', () => {
|
|
73
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
74
|
+
|
|
75
|
+
it('reflects size="sm" / "lg" to the host attribute', async () => {
|
|
76
|
+
const sm = mount('<spinner-ui size="sm"></spinner-ui>');
|
|
77
|
+
await tick();
|
|
78
|
+
expect(sm.getAttribute('size')).toBe('sm');
|
|
79
|
+
|
|
80
|
+
const lg = mount('<spinner-ui size="lg"></spinner-ui>');
|
|
81
|
+
await tick();
|
|
82
|
+
expect(lg.getAttribute('size')).toBe('lg');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('reflects variant="ring" / "dots" to the host attribute', async () => {
|
|
86
|
+
const ring = mount('<spinner-ui variant="ring"></spinner-ui>');
|
|
87
|
+
await tick();
|
|
88
|
+
expect(ring.getAttribute('variant')).toBe('ring');
|
|
89
|
+
|
|
90
|
+
const dots = mount('<spinner-ui variant="dots"></spinner-ui>');
|
|
91
|
+
await tick();
|
|
92
|
+
expect(dots.getAttribute('variant')).toBe('dots');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('reflects tone="subtle" / "accent" / "inverse" to the host attribute', async () => {
|
|
96
|
+
const subtle = mount('<spinner-ui tone="subtle"></spinner-ui>');
|
|
97
|
+
const accent = mount('<spinner-ui tone="accent"></spinner-ui>');
|
|
98
|
+
const inverse = mount('<spinner-ui tone="inverse"></spinner-ui>');
|
|
99
|
+
await tick();
|
|
100
|
+
expect(subtle.getAttribute('tone')).toBe('subtle');
|
|
101
|
+
expect(accent.getAttribute('tone')).toBe('accent');
|
|
102
|
+
expect(inverse.getAttribute('tone')).toBe('inverse');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('reflects [paused] as a boolean attribute', async () => {
|
|
106
|
+
const s = mount('<spinner-ui paused></spinner-ui>');
|
|
107
|
+
await tick();
|
|
108
|
+
expect(s.hasAttribute('paused')).toBe(true);
|
|
109
|
+
expect(s.paused).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── 3. Accessibility wiring ────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
describe('spinner-ui — accessibility', () => {
|
|
116
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
117
|
+
|
|
118
|
+
it('mirrors a custom [label] to aria-valuetext', async () => {
|
|
119
|
+
const s = mount('<spinner-ui label="Saving"></spinner-ui>');
|
|
120
|
+
await tick();
|
|
121
|
+
expect(s.getAttribute('aria-valuetext')).toBe('Saving');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('updates aria-valuetext when label is set programmatically', async () => {
|
|
125
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
126
|
+
await tick();
|
|
127
|
+
s.label = 'Uploading';
|
|
128
|
+
await tick();
|
|
129
|
+
expect(s.getAttribute('aria-valuetext')).toBe('Uploading');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('is NOT a focus target (no tabindex set, body tabIndex chain skips it)', async () => {
|
|
133
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
134
|
+
await tick();
|
|
135
|
+
expect(s.hasAttribute('tabindex')).toBe(false);
|
|
136
|
+
// tabIndex defaults to -1 for non-focusable elements in happy-dom
|
|
137
|
+
expect(s.tabIndex).toBeLessThan(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('does not stamp any internal DOM (static template = null)', async () => {
|
|
141
|
+
const s = mount('<spinner-ui></spinner-ui>');
|
|
142
|
+
await tick();
|
|
143
|
+
// The visual is entirely pseudo-element-driven; the host has no
|
|
144
|
+
// element children of its own.
|
|
145
|
+
expect(s.children.length).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('preserves user-supplied role / aria-busy when set in markup', async () => {
|
|
149
|
+
const s = mount('<spinner-ui role="status" aria-busy="false"></spinner-ui>');
|
|
150
|
+
await tick();
|
|
151
|
+
// connected() only sets the default ARIA when absent
|
|
152
|
+
expect(s.getAttribute('role')).toBe('status');
|
|
153
|
+
expect(s.getAttribute('aria-busy')).toBe('false');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ── 4. CSS source contract ─────────────────────────────────────────────
|
|
158
|
+
// happy-dom doesn't resolve @scope rules through getComputedStyle, so we
|
|
159
|
+
// validate the CSS source shape directly. Same recipe as tag-ui.
|
|
160
|
+
|
|
161
|
+
describe('spinner-ui — CSS source contract', () => {
|
|
162
|
+
it('opens with the canonical @scope (spinner-ui) block', () => {
|
|
163
|
+
expect(SPINNER_CSS).toMatch(/^@scope \(spinner-ui\)/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('declares both the :where(:scope) token block and a :scope base block', () => {
|
|
167
|
+
expect(SPINNER_CSS).toMatch(/:where\(:scope\)\s*\{/);
|
|
168
|
+
expect(SPINNER_CSS).toMatch(/^\s*:scope\s*\{/m);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('declares all 7 component tokens', () => {
|
|
172
|
+
for (const tok of [
|
|
173
|
+
'--spinner-size',
|
|
174
|
+
'--spinner-color',
|
|
175
|
+
'--spinner-stroke',
|
|
176
|
+
'--spinner-duration',
|
|
177
|
+
'--spinner-track-opacity',
|
|
178
|
+
'--spinner-dot-size',
|
|
179
|
+
'--spinner-dot-gap',
|
|
180
|
+
]) {
|
|
181
|
+
expect(SPINNER_CSS).toContain(tok);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('size="sm" / "md" / "lg" all override --spinner-size-default', () => {
|
|
186
|
+
// The two-token convention (--{prop}-default for defaults +
|
|
187
|
+
// var(--{prop}, var(--{prop}-default)) at use sites) means size
|
|
188
|
+
// variants flip the -default token; consumer overrides write to
|
|
189
|
+
// the bare --{prop} which wins via the var() fallback chain.
|
|
190
|
+
expect(SPINNER_CSS).toMatch(/:scope\[size="sm"\][^}]*--spinner-size-default:\s*0\.875rem/);
|
|
191
|
+
expect(SPINNER_CSS).toMatch(/:scope\[size="md"\][^}]*--spinner-size-default:\s*1rem/);
|
|
192
|
+
expect(SPINNER_CSS).toMatch(/:scope\[size="lg"\][^}]*--spinner-size-default:\s*1\.25rem/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('tone="subtle" / "accent" / "inverse" override --spinner-color-default via semantic tokens', () => {
|
|
196
|
+
expect(SPINNER_CSS).toMatch(/:scope\[tone="subtle"\][^}]*--spinner-color-default:\s*var\(--a-fg-subtle\)/);
|
|
197
|
+
expect(SPINNER_CSS).toMatch(/:scope\[tone="accent"\][^}]*--spinner-color-default:\s*var\(--a-accent-strong\)/);
|
|
198
|
+
expect(SPINNER_CSS).toMatch(/:scope\[tone="inverse"\][^}]*--spinner-color-default:\s*var\(--a-chrome-light\)/);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('arc variant uses a rotating quarter-circle border on ::before', () => {
|
|
202
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="arc"\]::before/);
|
|
203
|
+
// The arc colors three borders transparent + the top currentColor
|
|
204
|
+
expect(SPINNER_CSS).toMatch(/border-color:\s*currentColor\s+transparent\s+transparent\s+transparent/);
|
|
205
|
+
expect(SPINNER_CSS).toMatch(/animation:\s*spinner-ui-rotate/);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('ring variant uses a full ring with one rotating colored segment', () => {
|
|
209
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="ring"\]::before/);
|
|
210
|
+
expect(SPINNER_CSS).toMatch(/border-top-color:\s*currentColor/);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('dots variant uses 3 stamped [data-spinner-dot] children with bounce keyframes', () => {
|
|
214
|
+
// Pre-fix: ::before painted dot1 + a box-shadow middle dot, ::after
|
|
215
|
+
// painted dot3. Spacing was uneven (box-shadow doesn't participate
|
|
216
|
+
// in flex-gap math) and the middle dot couldn't animate. Post-fix
|
|
217
|
+
// (spinner.class.js #syncDots): three real <span data-spinner-dot=N>
|
|
218
|
+
// children stamped on connect; flex-gap distributes the row evenly
|
|
219
|
+
// and each child carries an independent animation-delay so the
|
|
220
|
+
// bounce reads as a left-to-right wave.
|
|
221
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot\]/);
|
|
222
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="1"\]/);
|
|
223
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="2"\]/);
|
|
224
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="3"\]/);
|
|
225
|
+
expect(SPINNER_CSS).toMatch(/animation:\s*spinner-ui-bounce/);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('knight variant uses a sliding ::before thumb with knight keyframes', () => {
|
|
229
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="knight"\]\s*\{/);
|
|
230
|
+
expect(SPINNER_CSS).toMatch(/:scope\[variant="knight"\]::before/);
|
|
231
|
+
expect(SPINNER_CSS).toMatch(/animation:\s*spinner-ui-knight/);
|
|
232
|
+
// Keyframe math: translateX((1/ratio - 1) * 100%) sweeps the thumb
|
|
233
|
+
// from x=0 to x=(track - thumb) px, expressed in thumb-relative %.
|
|
234
|
+
expect(SPINNER_CSS).toMatch(/@keyframes\s+spinner-ui-knight/);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('knight stamps NO data-spinner-dot children (pseudo-element paint only)', async () => {
|
|
238
|
+
const k = mount('<spinner-ui variant="knight"></spinner-ui>');
|
|
239
|
+
await tick();
|
|
240
|
+
expect(k.querySelectorAll(':scope > [data-spinner-dot]').length).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('dots stamps exactly 3 data-spinner-dot children on connect', async () => {
|
|
244
|
+
const d = mount('<spinner-ui variant="dots"></spinner-ui>');
|
|
245
|
+
await tick();
|
|
246
|
+
const dots = d.querySelectorAll(':scope > [data-spinner-dot]');
|
|
247
|
+
expect(dots.length).toBe(3);
|
|
248
|
+
expect(dots[0].getAttribute('data-spinner-dot')).toBe('1');
|
|
249
|
+
expect(dots[1].getAttribute('data-spinner-dot')).toBe('2');
|
|
250
|
+
expect(dots[2].getAttribute('data-spinner-dot')).toBe('3');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('paused state freezes the animation via animation-play-state', () => {
|
|
254
|
+
expect(SPINNER_CSS).toMatch(/:scope\[paused\][^}]*animation-play-state:\s*paused/);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('reduced-motion media query replaces the animation with a static ellipsis', () => {
|
|
258
|
+
expect(SPINNER_CSS).toMatch(/@media\s*\(prefers-reduced-motion:\s*reduce\)/);
|
|
259
|
+
expect(SPINNER_CSS).toMatch(/content:\s*"…"/);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('uses semantic --a-* tokens for color tones; no raw hex / rgb / oklch in the file', () => {
|
|
263
|
+
// Lints adjacent to the token contract: no raw colors in component CSS.
|
|
264
|
+
expect(SPINNER_CSS).not.toMatch(/#[0-9a-fA-F]{3,8}\b/);
|
|
265
|
+
expect(SPINNER_CSS).not.toMatch(/\brgb\s*\(/);
|
|
266
|
+
expect(SPINNER_CSS).not.toMatch(/\boklch\s*\(/);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('declares a linear rotation timing function (load-bearing for smooth spin)', () => {
|
|
270
|
+
expect(SPINNER_CSS).toMatch(/spinner-ui-rotate[^;]*linear/);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Hand-authored per SPEC-001 (docs/specs/implementation-ready/SPEC-001-spinner-loader.md).
|
|
2
|
+
# Edit this file; run `npm run build:components` to regenerate spinner.a2ui.json.
|
|
3
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
4
|
+
name: UISpinner
|
|
5
|
+
tag: spinner-ui
|
|
6
|
+
status: stable
|
|
7
|
+
component: Spinner
|
|
8
|
+
category: feedback
|
|
9
|
+
version: 1
|
|
10
|
+
description: >-
|
|
11
|
+
Circular animated indicator for indeterminate loading. Renders a rotating
|
|
12
|
+
arc, full ring, or three bouncing dots inside a sized box; the animation
|
|
13
|
+
runs while the element is in the DOM and `[paused]` is unset. Fills the
|
|
14
|
+
circular-spinner gap left by <skeleton-ui> (rectangular placeholder) and
|
|
15
|
+
<progress-ui> (linear determinate bar) — use <spinner-ui> when the wait
|
|
16
|
+
duration is unknown and the shape of the eventual content is irregular
|
|
17
|
+
or the region is too small for a placeholder block.
|
|
18
|
+
props:
|
|
19
|
+
size:
|
|
20
|
+
description: Diameter — matches icon-ui's ladder (sm 14px, md 16px, lg 20px).
|
|
21
|
+
type: string
|
|
22
|
+
default: md
|
|
23
|
+
enum:
|
|
24
|
+
- sm
|
|
25
|
+
- md
|
|
26
|
+
- lg
|
|
27
|
+
reflect: true
|
|
28
|
+
variant:
|
|
29
|
+
description: >-
|
|
30
|
+
Visual flavor —
|
|
31
|
+
`arc` (rotating quarter-circle, the default glyph spinner),
|
|
32
|
+
`ring` (full ring with one colored segment rotating around it),
|
|
33
|
+
`dots` (three bouncing dots — animated in a left-to-right wave),
|
|
34
|
+
`knight` (horizontal "knight-rider" bar — a sliding thumb that
|
|
35
|
+
bounces back-and-forth across a track; widest variant, reads as
|
|
36
|
+
a determinate-looking bar but is indeterminate by intent).
|
|
37
|
+
type: string
|
|
38
|
+
default: arc
|
|
39
|
+
enum:
|
|
40
|
+
- arc
|
|
41
|
+
- ring
|
|
42
|
+
- dots
|
|
43
|
+
- knight
|
|
44
|
+
reflect: true
|
|
45
|
+
tone:
|
|
46
|
+
description: Color tone — `current` inherits parent text color (matches button label), `accent` uses brand accent, `subtle` is muted, `inverse` flips for on-accent surfaces.
|
|
47
|
+
type: string
|
|
48
|
+
default: current
|
|
49
|
+
enum:
|
|
50
|
+
- current
|
|
51
|
+
- accent
|
|
52
|
+
- subtle
|
|
53
|
+
- inverse
|
|
54
|
+
reflect: true
|
|
55
|
+
paused:
|
|
56
|
+
description: Pause the animation in-place. Useful for screenshot tests and explicit-control flows.
|
|
57
|
+
type: boolean
|
|
58
|
+
default: false
|
|
59
|
+
reflect: true
|
|
60
|
+
label:
|
|
61
|
+
description: Accessible operation name surfaced via `aria-valuetext`. Override for context-specific labels ("Saving", "Uploading").
|
|
62
|
+
type: string
|
|
63
|
+
default: Loading
|
|
64
|
+
events: {}
|
|
65
|
+
slots: {}
|
|
66
|
+
states:
|
|
67
|
+
- name: running
|
|
68
|
+
description: Default; animation active.
|
|
69
|
+
- name: paused
|
|
70
|
+
description: Animation frozen at the current frame.
|
|
71
|
+
attribute: paused
|
|
72
|
+
- name: reduced
|
|
73
|
+
description: Triggered by prefers-reduced-motion. Animation replaced with a static ellipsis. Detected via CSS, not JS.
|
|
74
|
+
traits: []
|
|
75
|
+
tokens:
|
|
76
|
+
--spinner-size:
|
|
77
|
+
description: Diameter of the spinner box.
|
|
78
|
+
default: 1rem
|
|
79
|
+
--spinner-color:
|
|
80
|
+
description: Color of the active arc / ring / dots. Defaults to currentColor so a tone-driven cascade resolves naturally.
|
|
81
|
+
default: currentColor
|
|
82
|
+
--spinner-stroke:
|
|
83
|
+
description: Border thickness for the arc / ring variants.
|
|
84
|
+
default: 2px
|
|
85
|
+
--spinner-duration:
|
|
86
|
+
description: One full rotation duration.
|
|
87
|
+
default: var(--a-duration-slow)
|
|
88
|
+
--spinner-track-opacity:
|
|
89
|
+
description: Opacity for the non-rotating ring track (variant=ring only).
|
|
90
|
+
default: '0.25'
|
|
91
|
+
a2ui:
|
|
92
|
+
rules:
|
|
93
|
+
- >-
|
|
94
|
+
Use <Spinner> for INDETERMINATE loading where the duration is
|
|
95
|
+
unknown. For determinate progress (a known fraction complete), use
|
|
96
|
+
<Progress> (linear) instead. For known-shape placeholder loading,
|
|
97
|
+
use <Skeleton>.
|
|
98
|
+
- >-
|
|
99
|
+
When a Spinner is inside a Button, set tone="current" so it
|
|
100
|
+
matches the button label color, and disable the button while the
|
|
101
|
+
operation is in progress.
|
|
102
|
+
- >-
|
|
103
|
+
When overriding [label], use a present-progressive verb form
|
|
104
|
+
("Loading", "Saving", "Uploading"). Never use "Spin" or "Wait" —
|
|
105
|
+
they describe the visual, not the operation.
|
|
106
|
+
- >-
|
|
107
|
+
Do not nest <Spinner> inside <Skeleton>; they are siblings (two
|
|
108
|
+
different loading idioms), not parent/child.
|
|
109
|
+
- >-
|
|
110
|
+
Do not stack multiple sibling <Spinner>s in the same viewport
|
|
111
|
+
region. Use one parent-level Spinner instead — multiple spinners
|
|
112
|
+
add visual noise without extra information.
|
|
113
|
+
anti_patterns:
|
|
114
|
+
- wrong: |
|
|
115
|
+
{"component": "Spinner", "label": "Spin", "value": 0.42}
|
|
116
|
+
why: |
|
|
117
|
+
Spinner is INDETERMINATE only. `value` and any quantitative
|
|
118
|
+
progress field belongs on Progress, not Spinner. Also "Spin" is
|
|
119
|
+
not a valid operation label.
|
|
120
|
+
fix: |
|
|
121
|
+
{"component": "Progress", "value": 42, "max": 100}
|
|
122
|
+
- wrong: |
|
|
123
|
+
{"component": "Skeleton", "variant": "circle", "animation": "rotate"}
|
|
124
|
+
why: |
|
|
125
|
+
Skeleton is a placeholder; rotation is not part of its contract.
|
|
126
|
+
A rotating circle is a Spinner.
|
|
127
|
+
fix: |
|
|
128
|
+
{"component": "Spinner", "size": "md"}
|
|
129
|
+
- wrong: |
|
|
130
|
+
{"component": "Card", "children": [
|
|
131
|
+
{"component": "Spinner"},
|
|
132
|
+
{"component": "Spinner"},
|
|
133
|
+
{"component": "Spinner"}
|
|
134
|
+
]}
|
|
135
|
+
why: |
|
|
136
|
+
Multiple sibling spinners in one region produce visual noise
|
|
137
|
+
without extra information. Use one parent-level Spinner.
|
|
138
|
+
fix: |
|
|
139
|
+
{"component": "Card", "children": [
|
|
140
|
+
{"component": "Spinner", "size": "lg"}
|
|
141
|
+
]}
|
|
142
|
+
examples:
|
|
143
|
+
- name: button-saving
|
|
144
|
+
description: Loading button — primary action with a saving spinner. The button is disabled while the operation is in progress; the spinner matches the button label color via tone="current".
|
|
145
|
+
a2ui: >-
|
|
146
|
+
[
|
|
147
|
+
{
|
|
148
|
+
"id": "btn-save",
|
|
149
|
+
"component": "Button",
|
|
150
|
+
"text": "Saving",
|
|
151
|
+
"variant": "primary",
|
|
152
|
+
"disabled": true,
|
|
153
|
+
"children": ["sp-1"]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "sp-1",
|
|
157
|
+
"component": "Spinner",
|
|
158
|
+
"size": "sm",
|
|
159
|
+
"tone": "current",
|
|
160
|
+
"label": "Saving"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
- name: centered-card-loading
|
|
164
|
+
description: Standalone centered spinner inside a card while body content fetches.
|
|
165
|
+
a2ui: >-
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
"id": "card",
|
|
169
|
+
"component": "Card",
|
|
170
|
+
"children": ["row"]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"id": "row",
|
|
174
|
+
"component": "Row",
|
|
175
|
+
"justify": "center",
|
|
176
|
+
"align": "center",
|
|
177
|
+
"children": ["sp"]
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "sp",
|
|
181
|
+
"component": "Spinner",
|
|
182
|
+
"size": "lg",
|
|
183
|
+
"tone": "subtle",
|
|
184
|
+
"label": "Loading dashboard"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
- name: typing-indicator
|
|
188
|
+
description: Three bouncing dots — good for chat / typing indicators.
|
|
189
|
+
a2ui: >-
|
|
190
|
+
[
|
|
191
|
+
{
|
|
192
|
+
"id": "sp",
|
|
193
|
+
"component": "Spinner",
|
|
194
|
+
"variant": "dots",
|
|
195
|
+
"tone": "subtle",
|
|
196
|
+
"label": "Assistant is typing"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
keywords:
|
|
200
|
+
- spinner
|
|
201
|
+
- loader
|
|
202
|
+
- loading
|
|
203
|
+
- indeterminate
|
|
204
|
+
- progress
|
|
205
|
+
- busy
|
|
206
|
+
- feedback
|
|
207
|
+
- circular
|
|
208
|
+
synonyms:
|
|
209
|
+
spinner:
|
|
210
|
+
- spinner
|
|
211
|
+
- loader
|
|
212
|
+
- progress
|
|
213
|
+
loader:
|
|
214
|
+
- spinner
|
|
215
|
+
- loading
|
|
216
|
+
- progress
|
|
217
|
+
loading:
|
|
218
|
+
- spinner
|
|
219
|
+
- loading
|
|
220
|
+
- progress
|
|
221
|
+
- skeleton
|
|
222
|
+
busy:
|
|
223
|
+
- spinner
|
|
224
|
+
- loading
|
|
225
|
+
indeterminate:
|
|
226
|
+
- spinner
|
|
227
|
+
- progress
|
|
228
|
+
saving:
|
|
229
|
+
- spinner
|
|
230
|
+
- loading
|
|
231
|
+
uploading:
|
|
232
|
+
- spinner
|
|
233
|
+
- loading
|
|
234
|
+
related:
|
|
235
|
+
- Progress
|
|
236
|
+
- Skeleton
|
|
237
|
+
- Button
|
|
238
|
+
- EmptyState
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
@scope (stack-ui) {
|
|
2
2
|
:where(:scope) {
|
|
3
|
-
--stack-align: center;
|
|
3
|
+
--stack-align-default: center;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
:scope {
|
|
7
7
|
box-sizing: border-box;
|
|
8
8
|
display: grid;
|
|
9
|
-
place-items: var(--stack-align);
|
|
9
|
+
place-items: var(--stack-align, var(--stack-align-default));
|
|
10
10
|
/* Universal [padding] / [margin] opt-in — see tokens.css for scale. */
|
|
11
11
|
padding: var(--a-padding, 0);
|
|
12
12
|
margin: var(--a-margin, 0);
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/* Alignment */
|
|
22
|
-
:scope[align="center"] { --stack-align: center; }
|
|
23
|
-
:scope[align="top-left"] { --stack-align: start start; }
|
|
24
|
-
:scope[align="top-right"] { --stack-align: start end; }
|
|
25
|
-
:scope[align="bottom-left"] { --stack-align: end start; }
|
|
26
|
-
:scope[align="bottom-right"] { --stack-align: end end; }
|
|
27
|
-
:scope[align="top"] { --stack-align: start center; }
|
|
28
|
-
:scope[align="bottom"] { --stack-align: end center; }
|
|
29
|
-
:scope[align="left"] { --stack-align: center start; }
|
|
30
|
-
:scope[align="right"] { --stack-align: center end; }
|
|
22
|
+
:scope[align="center"] { --stack-align-default: center; }
|
|
23
|
+
:scope[align="top-left"] { --stack-align-default: start start; }
|
|
24
|
+
:scope[align="top-right"] { --stack-align-default: start end; }
|
|
25
|
+
:scope[align="bottom-left"] { --stack-align-default: end start; }
|
|
26
|
+
:scope[align="bottom-right"] { --stack-align-default: end end; }
|
|
27
|
+
:scope[align="top"] { --stack-align-default: start center; }
|
|
28
|
+
:scope[align="bottom"] { --stack-align-default: end center; }
|
|
29
|
+
:scope[align="left"] { --stack-align-default: center start; }
|
|
30
|
+
:scope[align="right"] { --stack-align-default: center end; }
|
|
31
31
|
}
|
package/components/stat/stat.css
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
:where(:scope) {
|
|
3
3
|
/* ── Tokens ──
|
|
4
4
|
Use size-responsive tokens so stat shrinks when parent card has size="sm". */
|
|
5
|
-
--stat-value-size: var(--a-title-size);
|
|
6
|
-
--stat-value-weight: var(--a-weight-bold);
|
|
7
|
-
--stat-value-fg: var(--a-fg-strong);
|
|
8
|
-
--stat-label-size: var(--a-ui-size);
|
|
9
|
-
--stat-label-fg: var(--a-fg);
|
|
10
|
-
--stat-change-size: var(--a-ui-size);
|
|
11
|
-
--stat-up-fg: var(--a-success-bg);
|
|
12
|
-
--stat-down-fg: var(--a-danger-bg);
|
|
13
|
-
--stat-icon-fg: var(--a-fg-muted);
|
|
5
|
+
--stat-value-size-default: var(--a-title-size);
|
|
6
|
+
--stat-value-weight-default: var(--a-weight-bold);
|
|
7
|
+
--stat-value-fg-default: var(--a-fg-strong);
|
|
8
|
+
--stat-label-size-default: var(--a-ui-size);
|
|
9
|
+
--stat-label-fg-default: var(--a-fg);
|
|
10
|
+
--stat-change-size-default: var(--a-ui-size);
|
|
11
|
+
--stat-up-fg-default: var(--a-success-bg);
|
|
12
|
+
--stat-down-fg-default: var(--a-danger-bg);
|
|
13
|
+
--stat-icon-fg-default: var(--a-fg-muted);
|
|
14
14
|
|
|
15
15
|
/* ── Spacing ── */
|
|
16
|
-
--stat-column-gap: var(--a-gap);
|
|
17
|
-
--stat-row-gap: var(--a-gap-sm);
|
|
16
|
+
--stat-column-gap-default: var(--a-gap);
|
|
17
|
+
--stat-row-gap-default: var(--a-gap-sm);
|
|
18
18
|
text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"label icon"
|
|
32
32
|
"value value"
|
|
33
33
|
"change change";
|
|
34
|
-
column-gap: var(--stat-column-gap);
|
|
35
|
-
row-gap: var(--stat-row-gap);
|
|
34
|
+
column-gap: var(--stat-column-gap, var(--stat-column-gap-default));
|
|
35
|
+
row-gap: var(--stat-row-gap, var(--stat-row-gap-default));
|
|
36
36
|
align-items: baseline;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
/* ── Label (eyebrow) ── */
|
|
72
72
|
[slot="label"] {
|
|
73
73
|
grid-area: label;
|
|
74
|
-
font-size: var(--stat-label-size);
|
|
75
|
-
color: var(--stat-label-fg);
|
|
74
|
+
font-size: var(--stat-label-size, var(--stat-label-size-default));
|
|
75
|
+
color: var(--stat-label-fg, var(--stat-label-fg-default));
|
|
76
76
|
line-height: 1.4;
|
|
77
77
|
min-width: 0;
|
|
78
78
|
overflow: hidden;
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
display: inline-flex;
|
|
87
87
|
align-items: center;
|
|
88
88
|
gap: 0.25em;
|
|
89
|
-
font-size: var(--stat-change-size);
|
|
89
|
+
font-size: var(--stat-change-size, var(--stat-change-size-default));
|
|
90
90
|
line-height: 1;
|
|
91
91
|
justify-self: start;
|
|
92
92
|
}
|
|
@@ -94,9 +94,9 @@
|
|
|
94
94
|
/* ── Value ── */
|
|
95
95
|
[slot="value"] {
|
|
96
96
|
grid-column: 1 / -1;
|
|
97
|
-
font-size: var(--stat-value-size);
|
|
98
|
-
font-weight: var(--stat-value-weight);
|
|
99
|
-
color: var(--stat-value-fg);
|
|
97
|
+
font-size: var(--stat-value-size, var(--stat-value-size-default));
|
|
98
|
+
font-weight: var(--stat-value-weight, var(--stat-value-weight-default));
|
|
99
|
+
color: var(--stat-value-fg, var(--stat-value-fg-default));
|
|
100
100
|
line-height: 1.2;
|
|
101
101
|
min-width: 0;
|
|
102
102
|
overflow: hidden;
|
|
@@ -107,29 +107,29 @@
|
|
|
107
107
|
/* Trend arrows via ::before */
|
|
108
108
|
:scope[trend="up"] [slot="change"]::before {
|
|
109
109
|
content: "\25B2";
|
|
110
|
-
color: var(--stat-up-fg);
|
|
110
|
+
color: var(--stat-up-fg, var(--stat-up-fg-default));
|
|
111
111
|
}
|
|
112
112
|
:scope[trend="up"] [slot="change"] {
|
|
113
|
-
color: var(--stat-up-fg);
|
|
113
|
+
color: var(--stat-up-fg, var(--stat-up-fg-default));
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
:scope[trend="down"] [slot="change"]::before {
|
|
117
117
|
content: "\25BC";
|
|
118
|
-
color: var(--stat-down-fg);
|
|
118
|
+
color: var(--stat-down-fg, var(--stat-down-fg-default));
|
|
119
119
|
}
|
|
120
120
|
:scope[trend="down"] [slot="change"] {
|
|
121
|
-
color: var(--stat-down-fg);
|
|
121
|
+
color: var(--stat-down-fg, var(--stat-down-fg-default));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
:scope[trend="neutral"] [slot="change"] {
|
|
125
|
-
color: var(--stat-label-fg);
|
|
125
|
+
color: var(--stat-label-fg, var(--stat-label-fg-default));
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/* ── Icon ── */
|
|
129
129
|
[slot="icon"] {
|
|
130
130
|
grid-area: icon;
|
|
131
131
|
align-self: start;
|
|
132
|
-
color: var(--stat-icon-fg);
|
|
132
|
+
color: var(--stat-icon-fg, var(--stat-icon-fg-default));
|
|
133
133
|
--a-icon-size: 1.25rem;
|
|
134
134
|
}
|
|
135
135
|
|