@ds-mo/ui 12.9.0 → 13.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/README.md +1 -0
- package/dist/.package-ready.json +1 -0
- package/dist/agent-patterns.json +124 -135
- package/dist/agent.json +249 -1108
- package/dist/angular/ds-bar-nav.d.ts +1 -1
- package/dist/angular/ds-bar-nav.js +3 -3
- package/dist/angular/ds-button-filled.d.ts +1 -1
- package/dist/angular/ds-button-filled.js +3 -3
- package/dist/angular/ds-card-data-viz.d.ts +18 -0
- package/dist/angular/{ds-card-data-viz-donut.js → ds-card-data-viz.js} +12 -12
- package/dist/angular/ds-card-overview.d.ts +1 -1
- package/dist/angular/ds-card-overview.js +3 -3
- package/dist/angular/ds-chart-bar.d.ts +1 -1
- package/dist/angular/ds-chart-bar.js +3 -3
- package/dist/angular/ds-menu.d.ts +1 -7
- package/dist/angular/ds-menu.js +2 -5
- package/dist/angular/ds-mobile-header.d.ts +1 -1
- package/dist/angular/ds-mobile-header.js +3 -3
- package/dist/angular/ds-mobile-section-switcher.d.ts +1 -1
- package/dist/angular/ds-mobile-section-switcher.js +3 -3
- package/dist/angular/ds-panel-tools.d.ts +1 -1
- package/dist/angular/ds-panel-tools.js +3 -3
- package/dist/angular/ds-select.d.ts +5 -4
- package/dist/angular/ds-select.js +3 -3
- package/dist/angular/ds-shell-app.d.ts +90 -1
- package/dist/angular/ds-shell-app.js +44 -4
- package/dist/angular/ds-shell-tools.d.ts +1 -1
- package/dist/angular/ds-shell-tools.js +3 -3
- package/dist/angular/index.d.ts +1 -1
- package/dist/angular/index.js +1 -8
- package/dist/angular/proxies.d.ts +1 -8
- package/dist/angular/proxies.js +1 -8
- package/dist/components/ds-agent-activity.js +1 -1
- package/dist/components/ds-agent-response.js +1 -1
- package/dist/components/ds-agent-source-list.js +1 -1
- package/dist/components/ds-agent-tool-call.js +1 -1
- package/dist/components/ds-attachment-list.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-nav.js.map +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-title.js.map +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-breadcrumb.js +1 -1
- package/dist/components/ds-button-filled.js +1 -1
- package/dist/components/ds-button-unfilled.js +1 -1
- package/dist/components/{ds-select-multi.d.ts → ds-card-data-viz.d.ts} +4 -4
- package/dist/components/ds-card-data-viz.js +2 -0
- package/dist/components/ds-card-data-viz.js.map +1 -0
- package/dist/components/ds-card-overview.js +1 -1
- package/dist/components/ds-card-overview.js.map +1 -1
- package/dist/components/ds-card-setting.js +1 -1
- package/dist/components/ds-chart-bar.js +1 -1
- package/dist/components/ds-chart-bar.js.map +1 -1
- package/dist/components/ds-chart-donut.js +1 -1
- package/dist/components/ds-chart-donut.js.map +1 -1
- package/dist/components/ds-chart-legend.js +1 -1
- package/dist/components/ds-chart-legend.js.map +1 -1
- package/dist/components/ds-chart-line.js +1 -1
- package/dist/components/ds-chart-line.js.map +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-chip.js.map +1 -1
- package/dist/components/ds-code-block.js +1 -1
- package/dist/components/ds-conversation-list-item.js +1 -1
- package/dist/components/ds-conversation-list-item.js.map +1 -1
- package/dist/components/ds-conversation-list-section.js +1 -1
- package/dist/components/ds-conversation-list.js +1 -1
- package/dist/components/ds-divider.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-input.js.map +1 -1
- package/dist/components/ds-markdown.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-message-bubble.js +1 -1
- package/dist/components/ds-message-composer.js +1 -1
- package/dist/components/ds-message-composer.js.map +1 -1
- package/dist/components/ds-message-scroller.js +1 -1
- package/dist/components/ds-message.js +1 -1
- package/dist/components/ds-mobile-bar-nav.js +1 -1
- package/dist/components/ds-mobile-bar-nav.js.map +1 -1
- package/dist/components/ds-mobile-header.js +1 -1
- package/dist/components/ds-mobile-section-switcher.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js +1 -1
- package/dist/components/ds-mobile-sheet-nav.js.map +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-nav.js.map +1 -1
- package/dist/components/ds-panel-sub-nav.js +1 -1
- package/dist/components/ds-panel-sub-nav.js.map +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-radio.js +1 -1
- package/dist/components/ds-radio.js.map +1 -1
- package/dist/components/ds-scroll-overlay.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-select.js.map +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-app.js.map +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-page.js.map +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-shell-tools.js.map +1 -1
- package/dist/components/ds-skeleton.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-slider.js.map +1 -1
- package/dist/components/ds-swatch-picker.js +1 -1
- package/dist/components/ds-switch.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-tag.js.map +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-tooltip-data-viz.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/{p-CgEYdMee.js → p-6qXDqCj_.js} +2 -2
- package/dist/components/{p-Cr3tEx7o.js → p-Ay1AX7Ql.js} +2 -2
- package/dist/components/p-B-qDb_Zo.js +2 -0
- package/dist/components/p-B-qDb_Zo.js.map +1 -0
- package/dist/components/{p-BOCfICln.js → p-B1XW4VSn.js} +2 -2
- package/dist/components/p-B9p6w-_T.js +2 -0
- package/dist/components/p-B9p6w-_T.js.map +1 -0
- package/dist/components/p-BBFEhNzM.js +2 -0
- package/dist/components/p-BBFEhNzM.js.map +1 -0
- package/dist/components/p-BGUK8-Qm.js +2 -0
- package/dist/components/p-BGUK8-Qm.js.map +1 -0
- package/dist/components/p-BO4cj3iX.js +2 -0
- package/dist/components/{p-DCvA3Dc9.js.map → p-BO4cj3iX.js.map} +1 -1
- package/dist/components/{p-BKOlvCGS.js → p-BUZXuod2.js} +2 -2
- package/dist/components/p-BYWkksDO.js +2 -0
- package/dist/components/p-BYWkksDO.js.map +1 -0
- package/dist/components/p-BbMJTilO.js +2 -0
- package/dist/components/p-BbMJTilO.js.map +1 -0
- package/dist/components/{p-BO5aqZcf.js → p-BcTYhmku.js} +2 -2
- package/dist/components/p-BnQXAbif.js +2 -0
- package/dist/components/p-BnQXAbif.js.map +1 -0
- package/dist/components/p-BnbbnuVC.js +2 -0
- package/dist/components/p-BnbbnuVC.js.map +1 -0
- package/dist/components/{p-BDKtu6Wb.js → p-BroVnDzE.js} +2 -2
- package/dist/components/{p-CIiBpjCn.js → p-BsoqsbBh.js} +2 -2
- package/dist/components/p-BybqPD8K.js +2 -0
- package/dist/components/p-BybqPD8K.js.map +1 -0
- package/dist/components/{p-CCzG5xDD.js → p-C-aCzwhA.js} +2 -2
- package/dist/components/p-C-aCzwhA.js.map +1 -0
- package/dist/components/p-C-kzknkR.js +2 -0
- package/dist/components/p-C-kzknkR.js.map +1 -0
- package/dist/components/p-C-vRkDyn.js +2 -0
- package/dist/components/p-C-vRkDyn.js.map +1 -0
- package/dist/components/p-C4uYvyq6.js +2 -0
- package/dist/components/p-C4uYvyq6.js.map +1 -0
- package/dist/components/{p-C8HU4W8q.js → p-CBazaRlE.js} +2 -2
- package/dist/components/p-CH9yArA1.js +2 -0
- package/dist/components/p-CH9yArA1.js.map +1 -0
- package/dist/components/p-CHpNyAo1.js +2 -0
- package/dist/components/{p-CChcuL04.js.map → p-CHpNyAo1.js.map} +1 -1
- package/dist/components/{p-BhV5e6vF.js → p-CHuh71-U.js} +2 -2
- package/dist/components/p-CJDTh9Tg.js +2 -0
- package/dist/components/p-CJDTh9Tg.js.map +1 -0
- package/dist/components/p-CJbLY1tu.js +2 -0
- package/dist/components/p-CJbLY1tu.js.map +1 -0
- package/dist/components/p-CreGah0j.js +2 -0
- package/dist/components/p-Cvxm1OZB.js +2 -0
- package/dist/components/p-Cvxm1OZB.js.map +1 -0
- package/dist/components/p-D2xWcVv6.js +2 -0
- package/dist/components/p-D2xWcVv6.js.map +1 -0
- package/dist/components/p-DAW6G8Sl.js +2 -0
- package/dist/components/p-DAW6G8Sl.js.map +1 -0
- package/dist/components/p-DEaszb15.js +2 -0
- package/dist/components/p-DEaszb15.js.map +1 -0
- package/dist/components/{p-BgGP65Nn.js → p-DOUk4KoT.js} +1 -1
- package/dist/components/p-DOUk4KoT.js.map +1 -0
- package/dist/components/p-DbQr7Kwr.js +2 -0
- package/dist/components/p-DbQr7Kwr.js.map +1 -0
- package/dist/components/{p-Cuih680v.js → p-Df17mJqI.js} +2 -2
- package/dist/components/p-DhEdICfD.js +2 -0
- package/dist/components/p-DhEdICfD.js.map +1 -0
- package/dist/components/p-bxoryr-3.js +2 -0
- package/dist/components/p-bxoryr-3.js.map +1 -0
- package/dist/components/p-eAfNzlMa.js +2 -0
- package/dist/components/p-eAfNzlMa.js.map +1 -0
- package/dist/components/p-gZni7A2L.js +2 -0
- package/dist/components/p-gZni7A2L.js.map +1 -0
- package/dist/components/{p-YR1XeuZf.js → p-ohsLFap_.js} +2 -2
- package/dist/components/{p-DjbTFy2B.js → p-ymtdhOU5.js} +1 -1
- package/dist/components/p-ymtdhOU5.js.map +1 -0
- package/dist/docs/components.json +929 -2371
- package/dist/framework/angular.d.ts +0 -1
- package/dist/framework/angular.js +0 -1
- package/dist/lib/components/BarTitle/bar-title-types.d.ts +28 -0
- package/dist/lib/components/Breadcrumb/breadcrumb-types.d.ts +16 -0
- package/dist/lib/components/ButtonFilled/ButtonFilled.d.ts +83 -0
- package/dist/lib/components/MobileHeader/mobile-header-types.d.ts +4 -0
- package/dist/lib/components/PanelTools/panel-tools-types.d.ts +21 -7
- package/dist/lib/components/ShellApp/shell-app-types.d.ts +91 -0
- package/dist/lib/shell/index.d.ts +5 -13
- package/dist/lib/shell/index.js +87 -358
- package/dist/lib/shell/index.js.map +3 -3
- package/dist/lib/shell/shell-responsive.d.ts +2 -1
- package/dist/lib/utils/button-render.d.ts +17 -0
- package/dist/lib/utils/button-types.d.ts +10 -0
- package/dist/lib/utils/choice-list.d.ts +25 -0
- package/dist/lib/utils/control-press.d.ts +8 -0
- package/dist/lib/utils/form-association.d.ts +22 -0
- package/dist/lib/utils/index.d.ts +4 -0
- package/dist/lib/utils/index.js +63 -0
- package/dist/lib/utils/index.js.map +3 -3
- package/dist/lib/utils/required-validity.d.ts +1 -1
- package/dist/lib/utils/safe-url.d.ts +11 -0
- package/dist/mcp/cli.js +38 -10
- package/dist/mcp-data/registry/agent-activity.json +1 -1
- package/dist/mcp-data/registry/agent-response.json +1 -1
- package/dist/mcp-data/registry/agent-source-list.json +2 -2
- package/dist/mcp-data/registry/agent-tool-call.json +1 -1
- package/dist/mcp-data/registry/attachment-list.json +2 -2
- package/dist/mcp-data/registry/avatar.json +1 -1
- package/dist/mcp-data/registry/badge.json +1 -1
- package/dist/mcp-data/registry/bar-nav.json +4 -22
- package/dist/mcp-data/registry/bar-title.json +1 -1
- package/dist/mcp-data/registry/bar-workflow.json +1 -1
- package/dist/mcp-data/registry/breadcrumb.json +2 -2
- package/dist/mcp-data/registry/button-filled.json +5 -20
- package/dist/mcp-data/registry/button-unfilled.json +15 -12
- package/dist/mcp-data/registry/card-data-viz.json +241 -0
- package/dist/mcp-data/registry/card-overview.json +39 -11
- package/dist/mcp-data/registry/card-setting.json +3 -3
- package/dist/mcp-data/registry/chart-bar.json +67 -19
- package/dist/mcp-data/registry/chart-donut.json +8 -8
- package/dist/mcp-data/registry/chart-legend.json +9 -11
- package/dist/mcp-data/registry/chart-line.json +2 -3
- package/dist/mcp-data/registry/checkbox.json +3 -3
- package/dist/mcp-data/registry/chip.json +1 -1
- package/dist/mcp-data/registry/code-block.json +1 -1
- package/dist/mcp-data/registry/conversation-list-item.json +2 -2
- package/dist/mcp-data/registry/conversation-list-section.json +1 -1
- package/dist/mcp-data/registry/conversation-list.json +1 -1
- package/dist/mcp-data/registry/divider.json +1 -1
- package/dist/mcp-data/registry/empty-state.json +1 -1
- package/dist/mcp-data/registry/field.json +3 -4
- package/dist/mcp-data/registry/icon.json +1 -1
- package/dist/mcp-data/registry/input.json +2 -2
- package/dist/mcp-data/registry/loader.json +1 -1
- package/dist/mcp-data/registry/markdown.json +2 -2
- package/dist/mcp-data/registry/menu.json +11 -22
- package/dist/mcp-data/registry/message-bubble.json +1 -1
- package/dist/mcp-data/registry/message-composer.json +1 -1
- package/dist/mcp-data/registry/message-scroller.json +1 -1
- package/dist/mcp-data/registry/message.json +1 -1
- package/dist/mcp-data/registry/mobile-bar-nav.json +2 -2
- package/dist/mcp-data/registry/mobile-header.json +2 -38
- package/dist/mcp-data/registry/mobile-section-switcher.json +2 -20
- package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
- package/dist/mcp-data/registry/modal.json +1 -1
- package/dist/mcp-data/registry/panel-nav.json +3 -3
- package/dist/mcp-data/registry/panel-sub-nav.json +1 -1
- package/dist/mcp-data/registry/panel-tool-header.json +1 -1
- package/dist/mcp-data/registry/panel-tool-search.json +2 -2
- package/dist/mcp-data/registry/panel-tools.json +20 -56
- package/dist/mcp-data/registry/radio.json +2 -2
- package/dist/mcp-data/registry/registry.json +295 -1175
- package/dist/mcp-data/registry/scroll-overlay.json +1 -1
- package/dist/mcp-data/registry/select.json +60 -26
- package/dist/mcp-data/registry/shell-app.json +346 -21
- package/dist/mcp-data/registry/shell-page.json +2 -2
- package/dist/mcp-data/registry/shell-tools.json +13 -44
- package/dist/mcp-data/registry/skeleton.json +1 -1
- package/dist/mcp-data/registry/slider.json +2 -2
- package/dist/mcp-data/registry/swatch-picker.json +1 -1
- package/dist/mcp-data/registry/switch.json +2 -2
- package/dist/mcp-data/registry/tab-group.json +1 -1
- package/dist/mcp-data/registry/tag.json +1 -1
- package/dist/mcp-data/registry/text.json +1 -1
- package/dist/mcp-data/registry/toast.json +1 -1
- package/dist/mcp-data/registry/tooltip-data-viz.json +1 -1
- package/dist/mcp-data/registry/tooltip.json +1 -1
- package/dist/mcp-data/registry/typing-indicator.json +1 -1
- package/dist/react/components.d.ts +1 -8
- package/dist/react/components.js +1 -8
- package/dist/react/ds-card-data-viz.d.ts +12 -0
- package/dist/react/ds-card-data-viz.js +12 -0
- package/dist/react/ds-menu.d.ts +1 -2
- package/dist/react/ds-menu.js +0 -1
- package/dist/react/ds-select.d.ts +2 -2
- package/dist/react/ds-shell-app.d.ts +24 -1
- package/dist/react/ds-shell-app.js +16 -1
- package/dist/types/components/AgentSourceList/AgentSourceList.d.ts +0 -1
- package/dist/types/components/BarNav/BarNav.d.ts +0 -2
- package/dist/types/components/BarNav/bar-nav-utils.d.ts +2 -4
- package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +5 -11
- package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +13 -10
- package/dist/types/components/CardDataViz/CardDataViz.d.ts +32 -0
- package/dist/types/components/CardDataViz/index.d.ts +2 -0
- package/dist/types/components/CardOverview/CardOverview.d.ts +17 -0
- package/dist/types/components/CardOverview/card-overview-controller.d.ts +16 -0
- package/dist/types/components/ChartBar/ChartBar.d.ts +12 -1
- package/dist/types/components/ChartBar/index.d.ts +2 -1
- package/dist/types/components/Menu/Menu.d.ts +0 -4
- package/dist/types/components/Menu/menu-sections.d.ts +4 -0
- package/dist/types/components/Menu/menu-types.d.ts +2 -11
- package/dist/types/components/MobileHeader/MobileHeader.d.ts +0 -5
- package/dist/types/components/MobileSectionSwitcher/MobileSectionSwitcher.d.ts +0 -2
- package/dist/types/components/PanelTools/PanelTools.d.ts +0 -4
- package/dist/types/components/PanelTools/index.d.ts +1 -1
- package/dist/types/components/PanelTools/panel-tools-types.d.ts +21 -7
- package/dist/types/components/PanelTools/panel-tools-utils.d.ts +1 -1
- package/dist/types/components/Select/Select.d.ts +12 -4
- package/dist/types/components/Select/index.d.ts +1 -1
- package/dist/types/components/ShellApp/ShellApp.d.ts +98 -0
- package/dist/types/components/ShellApp/shell-app-types.d.ts +92 -0
- package/dist/types/components/ShellPage/ShellPage.d.ts +5 -0
- package/dist/types/components/ShellTools/ShellTools.d.ts +2 -2
- package/dist/types/components.d.ts +210 -889
- package/dist/types/shell/index.d.ts +5 -13
- package/dist/types/shell/shell-responsive.d.ts +2 -1
- package/dist/types/utils/button-render.d.ts +18 -0
- package/dist/types/utils/button-story-foundation.d.ts +9 -0
- package/dist/types/utils/button-types.d.ts +11 -0
- package/dist/types/utils/form-association.d.ts +23 -0
- package/dist/types/utils/index.d.ts +4 -0
- package/dist/types/utils/required-validity.d.ts +1 -1
- package/dist/types/utils/safe-url.d.ts +12 -0
- package/dist/types/utils/select-controller.d.ts +1 -1
- package/package.json +18 -10
- package/dist/.build-stamp +0 -1
- package/dist/angular/ds-card-data-viz-bar.d.ts +0 -18
- package/dist/angular/ds-card-data-viz-bar.js +0 -42
- package/dist/angular/ds-card-data-viz-donut.d.ts +0 -18
- package/dist/angular/ds-card-data-viz-line.d.ts +0 -18
- package/dist/angular/ds-card-data-viz-line.js +0 -42
- package/dist/angular/ds-card-shell-data-viz.d.ts +0 -12
- package/dist/angular/ds-card-shell-data-viz.js +0 -38
- package/dist/angular/ds-chart-bar-stacked.d.ts +0 -12
- package/dist/angular/ds-chart-bar-stacked.js +0 -38
- package/dist/angular/ds-select-multi.d.ts +0 -28
- package/dist/angular/ds-select-multi.js +0 -49
- package/dist/angular/ds-shell-gradient-picker.d.ts +0 -16
- package/dist/angular/ds-shell-gradient-picker.js +0 -42
- package/dist/angular/ds-shell-gradient-swatch.d.ts +0 -16
- package/dist/angular/ds-shell-gradient-swatch.js +0 -42
- package/dist/components/ds-card-data-viz-bar.d.ts +0 -11
- package/dist/components/ds-card-data-viz-bar.js +0 -2
- package/dist/components/ds-card-data-viz-bar.js.map +0 -1
- package/dist/components/ds-card-data-viz-donut.d.ts +0 -11
- package/dist/components/ds-card-data-viz-donut.js +0 -2
- package/dist/components/ds-card-data-viz-donut.js.map +0 -1
- package/dist/components/ds-card-data-viz-line.d.ts +0 -11
- package/dist/components/ds-card-data-viz-line.js +0 -2
- package/dist/components/ds-card-data-viz-line.js.map +0 -1
- package/dist/components/ds-card-shell-data-viz.d.ts +0 -11
- package/dist/components/ds-card-shell-data-viz.js +0 -2
- package/dist/components/ds-card-shell-data-viz.js.map +0 -1
- package/dist/components/ds-chart-bar-stacked.d.ts +0 -11
- package/dist/components/ds-chart-bar-stacked.js +0 -2
- package/dist/components/ds-chart-bar-stacked.js.map +0 -1
- package/dist/components/ds-select-multi.js +0 -2
- package/dist/components/ds-select-multi.js.map +0 -1
- package/dist/components/ds-shell-gradient-picker.d.ts +0 -11
- package/dist/components/ds-shell-gradient-picker.js +0 -2
- package/dist/components/ds-shell-gradient-picker.js.map +0 -1
- package/dist/components/ds-shell-gradient-swatch.d.ts +0 -11
- package/dist/components/ds-shell-gradient-swatch.js +0 -2
- package/dist/components/ds-shell-gradient-swatch.js.map +0 -1
- package/dist/components/p-2foAUsDN.js +0 -2
- package/dist/components/p-2foAUsDN.js.map +0 -1
- package/dist/components/p-8GK7B7K4.js +0 -2
- package/dist/components/p-8GK7B7K4.js.map +0 -1
- package/dist/components/p-B5tMlca-.js +0 -2
- package/dist/components/p-B5tMlca-.js.map +0 -1
- package/dist/components/p-B88n0pO3.js +0 -2
- package/dist/components/p-B88n0pO3.js.map +0 -1
- package/dist/components/p-BBePKRHC.js +0 -2
- package/dist/components/p-BBePKRHC.js.map +0 -1
- package/dist/components/p-BOQnc-M4.js +0 -2
- package/dist/components/p-BOQnc-M4.js.map +0 -1
- package/dist/components/p-BgGP65Nn.js.map +0 -1
- package/dist/components/p-BrusnEYP.js +0 -2
- package/dist/components/p-BrusnEYP.js.map +0 -1
- package/dist/components/p-C61yJkfr.js +0 -2
- package/dist/components/p-C61yJkfr.js.map +0 -1
- package/dist/components/p-CChcuL04.js +0 -2
- package/dist/components/p-CCzG5xDD.js.map +0 -1
- package/dist/components/p-CGVXbXv9.js +0 -2
- package/dist/components/p-CGVXbXv9.js.map +0 -1
- package/dist/components/p-CH3w-P2H.js +0 -2
- package/dist/components/p-CH3w-P2H.js.map +0 -1
- package/dist/components/p-Cuqxh-Tq.js +0 -2
- package/dist/components/p-Cuqxh-Tq.js.map +0 -1
- package/dist/components/p-D1HFMTdC.js +0 -2
- package/dist/components/p-D1HFMTdC.js.map +0 -1
- package/dist/components/p-D2x17ei-.js +0 -2
- package/dist/components/p-D2x17ei-.js.map +0 -1
- package/dist/components/p-DCvA3Dc9.js +0 -2
- package/dist/components/p-DLZm_-sU.js +0 -2
- package/dist/components/p-DLZm_-sU.js.map +0 -1
- package/dist/components/p-DRnIuCiT.js +0 -2
- package/dist/components/p-DjKkSqrG.js +0 -2
- package/dist/components/p-DjKkSqrG.js.map +0 -1
- package/dist/components/p-DjbTFy2B.js.map +0 -1
- package/dist/components/p-DlscFn2m.js +0 -2
- package/dist/components/p-DlscFn2m.js.map +0 -1
- package/dist/components/p-DxkEC0hR.js +0 -2
- package/dist/components/p-DxkEC0hR.js.map +0 -1
- package/dist/components/p-Re2kPrXm.js +0 -2
- package/dist/components/p-Re2kPrXm.js.map +0 -1
- package/dist/components/p-XMZCaXs7.js +0 -2
- package/dist/components/p-XMZCaXs7.js.map +0 -1
- package/dist/components/p-dyUp3Adm.js +0 -2
- package/dist/components/p-dyUp3Adm.js.map +0 -1
- package/dist/components/p-nkYN0Bub.js +0 -2
- package/dist/components/p-nkYN0Bub.js.map +0 -1
- package/dist/framework/select-multi-value-accessor.d.ts +0 -9
- package/dist/framework/select-multi-value-accessor.js +0 -40
- package/dist/lib/shell/badge-gradient-ring.d.ts +0 -27
- package/dist/lib/shell/chrome-transition.d.ts +0 -32
- package/dist/lib/shell/shell-chrome-metrics.d.ts +0 -11
- package/dist/lib/shell/shell-gradient.d.ts +0 -41
- package/dist/lib/shell/shell-view-transition.d.ts +0 -23
- package/dist/mcp-data/registry/card-data-viz-bar.json +0 -213
- package/dist/mcp-data/registry/card-data-viz-donut.json +0 -214
- package/dist/mcp-data/registry/card-data-viz-line.json +0 -207
- package/dist/mcp-data/registry/card-shell-data-viz.json +0 -180
- package/dist/mcp-data/registry/chart-bar-stacked.json +0 -230
- package/dist/mcp-data/registry/select-multi.json +0 -758
- package/dist/mcp-data/registry/shell-gradient-picker.json +0 -195
- package/dist/mcp-data/registry/shell-gradient-swatch.json +0 -215
- package/dist/react/ds-card-data-viz-bar.d.ts +0 -12
- package/dist/react/ds-card-data-viz-bar.js +0 -12
- package/dist/react/ds-card-data-viz-donut.d.ts +0 -12
- package/dist/react/ds-card-data-viz-donut.js +0 -12
- package/dist/react/ds-card-data-viz-line.d.ts +0 -12
- package/dist/react/ds-card-data-viz-line.js +0 -12
- package/dist/react/ds-card-shell-data-viz.d.ts +0 -9
- package/dist/react/ds-card-shell-data-viz.js +0 -12
- package/dist/react/ds-chart-bar-stacked.d.ts +0 -9
- package/dist/react/ds-chart-bar-stacked.js +0 -12
- package/dist/react/ds-select-multi.d.ts +0 -14
- package/dist/react/ds-select-multi.js +0 -16
- package/dist/react/ds-shell-gradient-picker.d.ts +0 -12
- package/dist/react/ds-shell-gradient-picker.js +0 -12
- package/dist/react/ds-shell-gradient-swatch.d.ts +0 -12
- package/dist/react/ds-shell-gradient-swatch.js +0 -12
- package/dist/types/components/CardDataVizBar/CardDataVizBar.d.ts +0 -24
- package/dist/types/components/CardDataVizBar/index.d.ts +0 -2
- package/dist/types/components/CardDataVizDonut/CardDataVizDonut.d.ts +0 -28
- package/dist/types/components/CardDataVizDonut/index.d.ts +0 -2
- package/dist/types/components/CardDataVizLine/CardDataVizLine.d.ts +0 -24
- package/dist/types/components/CardDataVizLine/index.d.ts +0 -2
- package/dist/types/components/CardShellDataViz/CardShellDataViz.d.ts +0 -13
- package/dist/types/components/CardShellDataViz/index.d.ts +0 -2
- package/dist/types/components/ChartBarStacked/ChartBarStacked.d.ts +0 -26
- package/dist/types/components/ChartBarStacked/index.d.ts +0 -3
- package/dist/types/components/SelectMulti/SelectMulti.d.ts +0 -121
- package/dist/types/components/SelectMulti/index.d.ts +0 -2
- package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +0 -12
- package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +0 -15
- package/dist/types/components/ShellGradientSwatch/shell-gradient-swatch-types.d.ts +0 -4
- /package/dist/components/{p-CgEYdMee.js.map → p-6qXDqCj_.js.map} +0 -0
- /package/dist/components/{p-Cr3tEx7o.js.map → p-Ay1AX7Ql.js.map} +0 -0
- /package/dist/components/{p-BOCfICln.js.map → p-B1XW4VSn.js.map} +0 -0
- /package/dist/components/{p-BKOlvCGS.js.map → p-BUZXuod2.js.map} +0 -0
- /package/dist/components/{p-BO5aqZcf.js.map → p-BcTYhmku.js.map} +0 -0
- /package/dist/components/{p-BDKtu6Wb.js.map → p-BroVnDzE.js.map} +0 -0
- /package/dist/components/{p-CIiBpjCn.js.map → p-BsoqsbBh.js.map} +0 -0
- /package/dist/components/{p-C8HU4W8q.js.map → p-CBazaRlE.js.map} +0 -0
- /package/dist/components/{p-BhV5e6vF.js.map → p-CHuh71-U.js.map} +0 -0
- /package/dist/components/{p-DRnIuCiT.js.map → p-CreGah0j.js.map} +0 -0
- /package/dist/components/{p-Cuih680v.js.map → p-Df17mJqI.js.map} +0 -0
- /package/dist/components/{p-YR1XeuZf.js.map → p-ohsLFap_.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "card-overview",
|
|
4
4
|
"title": "CardOverview",
|
|
5
|
-
"description": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional compact 48px presentation.",
|
|
5
|
+
"description": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional elevated scroll-collapse surface and compact 48px presentation.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "general",
|
|
15
15
|
"status": "experimental",
|
|
16
|
-
"summary": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional compact 48px presentation.",
|
|
16
|
+
"summary": "Page-level summary card pairing one headline score with a reporting period, an application-owned period control, and a responsive grid of comparable measures, with an optional elevated scroll-collapse surface and compact 48px presentation.",
|
|
17
17
|
"useWhen": [
|
|
18
18
|
"A page opens with one headline figure that the surrounding measures explain.",
|
|
19
19
|
"Every measure is compared against the same period, so one caption describes the comparison for all of them.",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"alternatives": [
|
|
29
29
|
{
|
|
30
30
|
"when": "A measure needs a chart or other data visualization.",
|
|
31
|
-
"component": "component:ds-card-
|
|
32
|
-
"reason": "
|
|
31
|
+
"component": "component:ds-card-data-viz",
|
|
32
|
+
"reason": "CardDataViz owns the data-visualization card contract; the overview grid presents values and change only."
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"when": "The surface is a settings section with an edit workflow.",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"Loading replaces the score and the measures with skeletons and does not alter the slotted period control.",
|
|
60
60
|
"At most six measures are shown. Keep larger collections outside the summary bar rather than turning it into a dense dashboard.",
|
|
61
61
|
"The period control is slotted rather than owned, because the choice of stepper, select, or date range varies by product surface.",
|
|
62
|
+
"During a page-controlled scroll collapse, the component preserves its expanded flow height while the elevated visual surface shrinks toward 48px and the full content translates upward inside an internal clip. The elevation remains fitted to the visible surface instead of being clipped with the content.",
|
|
62
63
|
"compact is exactly 48px high and keeps only the score label, score value, trend, reporting copy, and slotted period control. It omits the score band, measures, and footer."
|
|
63
64
|
],
|
|
64
65
|
"responsiveBehavior": [
|
|
@@ -70,20 +71,24 @@
|
|
|
70
71
|
"In that stacked summary, the score and trend share one line at the start, while the score band is hidden to preserve space. The period control stays at the upper end, while the current period and comparison sit on separate right-aligned lines beside the score.",
|
|
71
72
|
"Every measure occupies a full-width row in the stacked state.",
|
|
72
73
|
"Set layout to stacked when the owning page shell enters a responsive mode before the card's own container reaches its narrow threshold.",
|
|
73
|
-
"CardOverview does not observe scroll or
|
|
74
|
+
"CardOverview does not observe scroll, choose sticky offsets, or switch its own presentation. The page owner supplies collapse progress, keeps the card sticky at the appropriate shell inset, and swaps to compact at the 48px endpoint.",
|
|
75
|
+
"Leave scroll collapse inactive on page modes that do not support sticky compact summary chrome."
|
|
74
76
|
],
|
|
75
77
|
"frameworkCaveats": {
|
|
76
78
|
"customElements": [
|
|
77
79
|
"Assign score and metrics as JavaScript properties; both are objects and cannot be expressed as HTML attributes.",
|
|
78
|
-
"Place the period control in the filter slot; the component reserves space for it but never renders one."
|
|
80
|
+
"Place the period control in the filter slot; the component reserves space for it but never renders one.",
|
|
81
|
+
"When driving scroll collapse, update scrollCollapseProgress from page-owned geometry and swap to a separate compact presentation at the endpoint."
|
|
79
82
|
],
|
|
80
83
|
"react": [
|
|
81
84
|
"Pass score and metrics as props and read the selected measure from the onDsMetricSelect event detail.",
|
|
82
|
-
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision."
|
|
85
|
+
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision.",
|
|
86
|
+
"Keep sticky geometry and scrollCollapseProgress in the page owner; CardOverview only renders the visual collapse."
|
|
83
87
|
],
|
|
84
88
|
"angular": [
|
|
85
89
|
"Bind [score] and [metrics] as properties and read the selected measure from $event.detail on (dsMetricSelect).",
|
|
86
|
-
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision."
|
|
90
|
+
"Derive each trend with resolveMetricTrend so tone stays an explicit product decision.",
|
|
91
|
+
"Keep sticky geometry and [scrollCollapseProgress] in the page owner; CardOverview only renders the visual collapse."
|
|
87
92
|
]
|
|
88
93
|
},
|
|
89
94
|
"references": [
|
|
@@ -176,6 +181,15 @@
|
|
|
176
181
|
"mutable": false,
|
|
177
182
|
"description": "Message shown in place of the score when its figure cannot be resolved."
|
|
178
183
|
},
|
|
184
|
+
"scrollCollapseProgress": {
|
|
185
|
+
"type": "number",
|
|
186
|
+
"resolvedType": "number",
|
|
187
|
+
"attribute": "scroll-collapse-progress",
|
|
188
|
+
"default": "0",
|
|
189
|
+
"required": false,
|
|
190
|
+
"mutable": false,
|
|
191
|
+
"description": "Page-controlled visual collapse from the full card (`0`) toward its 48px\ncompact handoff height (`1`). The component preserves its expanded flow\nheight, keeps elevation on the shrinking surface, and clips translated\ncontent internally. The page still owns sticky positioning and the final\nswap to `variant=\"compact\"`."
|
|
192
|
+
},
|
|
179
193
|
"variant": {
|
|
180
194
|
"type": "CardOverviewVariant",
|
|
181
195
|
"resolvedType": "\"compact\" | \"default\"",
|
|
@@ -277,6 +291,15 @@
|
|
|
277
291
|
"mutable": false,
|
|
278
292
|
"description": "Message shown in place of the score when its figure cannot be resolved."
|
|
279
293
|
},
|
|
294
|
+
"scrollCollapseProgress": {
|
|
295
|
+
"type": "number",
|
|
296
|
+
"resolvedType": "number",
|
|
297
|
+
"attribute": "scroll-collapse-progress",
|
|
298
|
+
"default": "0",
|
|
299
|
+
"required": false,
|
|
300
|
+
"mutable": false,
|
|
301
|
+
"description": "Page-controlled visual collapse from the full card (`0`) toward its 48px\ncompact handoff height (`1`). The component preserves its expanded flow\nheight, keeps elevation on the shrinking surface, and clips translated\ncontent internally. The page still owns sticky positioning and the final\nswap to `variant=\"compact\"`."
|
|
302
|
+
},
|
|
280
303
|
"variant": {
|
|
281
304
|
"type": "CardOverviewVariant",
|
|
282
305
|
"resolvedType": "\"compact\" | \"default\"",
|
|
@@ -322,7 +345,7 @@
|
|
|
322
345
|
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: metrics, score.",
|
|
323
346
|
"peerDependencies": {
|
|
324
347
|
"required": [
|
|
325
|
-
"@ds-mo/tokens ^5.
|
|
348
|
+
"@ds-mo/tokens ^5.1.0",
|
|
326
349
|
"@ds-mo/icons ^6.4.0"
|
|
327
350
|
],
|
|
328
351
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -343,12 +366,17 @@
|
|
|
343
366
|
"files": [
|
|
344
367
|
{
|
|
345
368
|
"path": "src/wc/components/CardOverview/CardOverview.css",
|
|
346
|
-
"content": "@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../styles/control-elevation.css';\n\n/*\n * A permanently dark surface, independent of the page theme, so the summary bar\n * reads the same on every product surface. Every foreground, border, and\n * interaction token below therefore comes from the always-dark family rather\n * than the theme-reactive one.\n */\n\n:host {\n display: block;\n box-sizing: border-box;\n /* Drives the narrow layout below from the card's own width, not the viewport's. */\n container-type: inline-size;\n /* A summary is read, not copied; stray selection while clicking cells is noise. */\n -webkit-user-select: none;\n user-select: none;\n /* Width a metric may shrink to before the grid drops a column. */\n --ds-card-overview-metric-min: calc(var(--dimension-size-400) * 5);\n --ds-card-overview-divider-width: var(--dimension-stroke-width-012);\n --ds-card-overview-divider-color: var(--color-always-dark-border-tertiary);\n --ds-card-overview-divider: var(--ds-card-overview-divider-width) solid\n var(--ds-card-overview-divider-color);\n border-radius: var(--dimension-radius-050);\n background: var(--color-always-dark-background);\n color: var(--color-always-dark-foreground-primary);\n /*\n * Clips the square-cornered metric washes to the card radius. The outer\n * elevation shadow is painted outside the border box and is unaffected, and\n * the inset highlight is meant to sit exactly on this edge.\n */\n overflow: hidden;\n}\n\n.card-overview__layout {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n align-items: stretch;\n min-width: 0;\n}\n\n.card-overview__layout--has-score {\n grid-template-columns: auto minmax(0, 1fr);\n}\n\n/*\n * Transparent at regular widths so the score and header participate in the\n * inner layout grid directly. At the narrow breakpoint it becomes the unified\n * summary block above the metrics.\n */\n.card-overview__summary {\n display: contents;\n}\n\n/* ── Score ────────────────────────────────────────────────────────────────── */\n\n/*\n * Hugs its content and never shrinks, so the headline figure keeps its size and\n * the metric grid absorbs the remaining width.\n */\n/*\n * Top aligned rather than centred, and padded to match the header, so the score\n * label sits in the same 32px box at the same offset as the period text beside\n * it. Both use the same type variant, so their baselines land together.\n */\n.card-overview__score {\n display: flex;\n grid-row: 1 / span 2;\n grid-column: 1;\n flex: 0 0 auto;\n flex-direction: column;\n gap: 0;\n min-width: 0;\n padding: var(--dimension-space-100) var(--dimension-space-200);\n border-inline-end: var(--ds-card-overview-divider);\n}\n\n.card-overview__score--resolved {\n min-inline-size: max-content;\n}\n\n/* The control-height box that aligns this label with the period text. */\n.card-overview__score-label-row {\n display: flex;\n align-items: center;\n min-height: var(--ds-control-height);\n}\n\n/*\n * The score is its own column, so its rows cannot baseline-align with the measure\n * rows through flex or grid — those only align items that share a line. These two\n * offsets pull the column onto the measure baselines instead.\n *\n * They are compensation for how much lower a display-scale figure sits in its line\n * box than the body text it aligns to, so they are coupled to the type pairing:\n * change the score or measure variants and both need re-measuring.\n */\n.card-overview__score-figure {\n display: flex;\n align-items: baseline;\n gap: var(--dimension-space-050);\n min-width: 0;\n margin-block-start: calc(-1 * (var(--dimension-space-150) + var(--dimension-space-012)));\n}\n\n.card-overview__score-value {\n flex: 0 0 auto;\n}\n\n/* Second half of the baseline compensation described above the score figure. */\n.card-overview__score-band {\n margin-block-start: calc(-1 * var(--dimension-space-025));\n}\n\n/*\n * A skeleton bar is a solid block, so in a baseline-aligned row it aligns on its\n * bottom edge while the text it stands in for aligns on its baseline. Next to a\n * display-scale figure that difference is the descent under the figure's\n * baseline, which drops the bar below where the resolved trend sits.\n *\n * The offset is applied with relative positioning rather than a margin: a margin\n * is not part of a synthesized baseline, so it grows the figure instead of moving\n * the bar, which then pushes the band below it out of place.\n *\n * Only needed beside the score figure; the measure rows pair body variants whose\n * bottom edge and baseline already agree.\n */\n.card-overview__skeleton-baseline {\n position: relative;\n inset-block-start: calc(-1 * var(--dimension-space-150));\n}\n\n.card-overview__score--error {\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-050);\n color: var(--color-always-dark-foreground-negative);\n}\n\n/* ── Body ─────────────────────────────────────────────────────────────────── */\n\n/*\n * Takes the remaining inline space. The minimum is what makes the host wrap: once\n * the score and roughly two measure columns no longer fit side by side, the body\n * drops below the score instead of squeezing it. `min()` keeps that from becoming\n * an overflow floor on very narrow viewports.\n */\n.card-overview__body {\n display: flex;\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n min-width: 0;\n}\n\n.card-overview__body--has-score {\n grid-column: 2;\n}\n\n/*\n * One control row: 8px of padding on every side around a 32px md control, so the\n * bar is 8 + 32 + 8. Both ends of the bar are control-shaped boxes — the period\n * text on the left and the slotted control on the right — so the row reads\n * balanced rather than text on one side and a control on the other.\n */\n.card-overview__header {\n display: flex;\n grid-row: 1;\n grid-column: 1;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n padding: var(--dimension-space-100);\n border-block-end: var(--ds-card-overview-divider);\n box-sizing: border-box;\n}\n\n.card-overview__header--has-score {\n grid-column: 2;\n}\n\n/*\n * Matches the control's box on the opposite end, including its inline padding, so\n * the text lands at the same optical inset the slotted control does:\n * 8 from the bar + 6 from this box + 2 from the label inset = 16.\n *\n * Nowrap keeps the row at the control height; the period truncates rather than\n * growing the bar to two lines.\n */\n.card-overview__period {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n gap: var(--dimension-space-050);\n min-height: var(--ds-control-height);\n min-width: 0;\n padding-inline: var(--ds-control-padding-inline);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The period control is slotted, so only reserve space for it. */\n.card-overview__filter {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n}\n\n/* ── Metrics ──────────────────────────────────────────────────────────────── */\n\n/*\n * `auto-fit` gives every occupied track an equal share while all metrics fit on\n * one row. Once wrapping creates another row, its cells stay on those same\n * tracks, so a partial final row leaves empty space instead of stretching one\n * measure wider than its peers.\n *\n * Dividers are painted outward from each cell and clipped by this grid. That\n * leaves only the lines between cells: anything emitted from the final column or\n * final row lands outside the grid and is clipped without needing to detect the\n * number of tracks.\n *\n * `flex: 1 1 auto` makes the grid absorb whatever height the body has\n * left, so cells reach the card bottom and their hover wash has no dead strip.\n */\n.card-overview__metrics {\n display: grid;\n flex: 1 1 auto;\n grid-template-columns: repeat(\n auto-fit,\n minmax(min(100%, var(--ds-card-overview-metric-min)), 1fr)\n );\n align-content: stretch;\n min-width: 0;\n overflow: clip;\n}\n\n/*\n * Square by design. Cells tile edge to edge against their dividers, so a radius\n * would break the seam; the card's own radius clips the outer corners instead.\n *\n * Padding is plain rather than control-derived: a cell is a block of content, not\n * a control box that has to optically match something on the far side.\n */\n.card-overview__metric {\n display: flex;\n position: relative;\n flex-direction: column;\n gap: var(--dimension-space-050);\n min-width: 0;\n padding: var(--dimension-space-175) var(--dimension-space-200);\n border-radius: 0;\n box-shadow:\n var(--ds-card-overview-divider-width) 0 0 0 var(--ds-card-overview-divider-color),\n 0 var(--ds-card-overview-divider-width) 0 0 var(--ds-card-overview-divider-color);\n}\n\n.card-overview__metric[role='button'] {\n cursor: default;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.card-overview__metric-label {\n min-width: 0;\n}\n\n.card-overview__metric-figure {\n display: flex;\n align-items: baseline;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n/* ── Trend ────────────────────────────────────────────────────────────────── */\n\n/*\n * Tone arrives resolved from the caller: the arrow direction and the sentiment\n * are independent, so a falling metric can read positive or neutral. The arrow is\n * a glyph inside this text node, so it always matches the surrounding weight.\n */\n.card-overview__trend {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n/* ── Narrow layout ────────────────────────────────────────────────────────── */\n\n/*\n * Below roughly two measure columns the card becomes a full stack. The summary\n * wrapper owns one block above the metrics: score label, figure, and trend stay\n * together at the start; the period control sits at the upper end; and the period\n * copy follows the score instead of becoming a separate divided row.\n */\n/*\n * Selectors are host-qualified purely for specificity: the compiled sheet places\n * these rules ahead of the base ones, so a plain class here would only ever add\n * properties and never override them.\n */\n@container (max-width: 480px) {\n :host .card-overview__layout {\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n }\n\n :host .card-overview__summary {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto;\n border-block-end: var(--ds-card-overview-divider);\n }\n\n :host .card-overview__score {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-rows: auto auto;\n grid-template-columns: auto auto;\n justify-content: start;\n column-gap: var(--dimension-space-100);\n width: auto;\n padding-block-end: var(--dimension-space-100);\n padding-inline-end: var(--dimension-space-100);\n border-inline-end: none;\n }\n\n :host .card-overview__score-label-row {\n grid-row: 1;\n grid-column: 1 / -1;\n min-height: var(--ds-control-height);\n }\n\n /*\n * The wide score uses optical offsets to meet the neighbouring metric\n * baselines. In the stacked summary those neighbours are gone, so natural\n * document flow keeps the label, figure, band, and period clearly ordered.\n */\n :host .card-overview__score-band,\n :host .card-overview__score-figure {\n grid-row: 2;\n margin-block-start: 0;\n }\n\n :host .card-overview__score-figure {\n grid-column: 1;\n align-self: baseline;\n gap: var(--dimension-space-100);\n }\n\n :host .card-overview__score-band {\n grid-column: 2;\n align-self: baseline;\n display: none;\n }\n\n :host .card-overview__header {\n display: grid;\n grid-row: 1;\n grid-column: 2;\n grid-template-rows: auto auto;\n align-content: start;\n gap: 0;\n padding: 0;\n border-block-end: none;\n }\n\n :host .card-overview__filter {\n grid-row: 1;\n grid-column: 1;\n align-self: start;\n justify-self: end;\n padding: var(--dimension-space-100) var(--dimension-space-100) 0 0;\n }\n\n :host .card-overview__period {\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n align-items: flex-end;\n justify-self: stretch;\n gap: 0;\n min-height: 0;\n padding-block: var(--dimension-space-150) var(--dimension-space-100);\n padding-inline: var(--dimension-space-100) var(--dimension-space-200);\n overflow: visible;\n text-align: end;\n white-space: nowrap;\n }\n\n :host .card-overview__body {\n grid-row: 2;\n grid-column: 1;\n }\n}\n\n/*\n * Page shells may enter a responsive mode before this card itself becomes\n * narrow. The explicit stacked layout applies the same composition without\n * coupling CardOverview to a viewport breakpoint.\n */\n:host(.card-overview--stacked) .card-overview__layout {\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n}\n\n:host(.card-overview--stacked) .card-overview__summary {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto;\n border-block-end: var(--ds-card-overview-divider);\n}\n\n:host(.card-overview--stacked) .card-overview__score {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-rows: auto auto;\n grid-template-columns: auto auto;\n justify-content: start;\n column-gap: var(--dimension-space-100);\n width: auto;\n padding-block-end: var(--dimension-space-100);\n padding-inline-end: var(--dimension-space-100);\n border-inline-end: none;\n}\n\n:host(.card-overview--stacked) .card-overview__score-label-row {\n grid-row: 1;\n grid-column: 1 / -1;\n min-height: var(--ds-control-height);\n}\n\n:host(.card-overview--stacked) .card-overview__score-band,\n:host(.card-overview--stacked) .card-overview__score-figure {\n grid-row: 2;\n margin-block-start: 0;\n}\n\n:host(.card-overview--stacked) .card-overview__score-figure {\n grid-column: 1;\n align-self: baseline;\n gap: var(--dimension-space-100);\n}\n\n:host(.card-overview--stacked) .card-overview__score-band {\n grid-column: 2;\n align-self: baseline;\n display: none;\n}\n\n:host(.card-overview--stacked) .card-overview__header {\n display: grid;\n grid-row: 1;\n grid-column: 2;\n grid-template-rows: auto auto;\n align-content: start;\n gap: 0;\n padding: 0;\n border-block-end: none;\n}\n\n:host(.card-overview--stacked) .card-overview__filter {\n grid-row: 1;\n grid-column: 1;\n align-self: start;\n justify-self: end;\n padding: var(--dimension-space-100) var(--dimension-space-100) 0 0;\n}\n\n:host(.card-overview--stacked) .card-overview__period {\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n align-items: flex-end;\n justify-self: stretch;\n gap: 0;\n min-height: 0;\n padding-block: var(--dimension-space-150) var(--dimension-space-100);\n padding-inline: var(--dimension-space-100) var(--dimension-space-200);\n overflow: visible;\n text-align: end;\n white-space: nowrap;\n}\n\n:host(.card-overview--stacked) .card-overview__body {\n grid-row: 2;\n grid-column: 1;\n}\n\n:host(.card-overview--stacked) .card-overview__metrics {\n grid-template-columns: minmax(0, 1fr);\n}\n\n/* ── Compact presentation ───────────────────────────────────────────────── */\n\n/*\n * Matches the shared 48px bar contract: one 32px control row with 8px above\n * and below. The score remains a distinct leading cell, while reporting copy\n * absorbs the available width and the application-owned control never shrinks.\n */\n:host(.card-overview--compact) {\n height: var(--dimension-size-600);\n min-height: var(--dimension-size-600);\n}\n\n:host(.card-overview--compact) .card-overview__layout,\n:host(.card-overview--compact) .card-overview__summary {\n display: flex;\n width: 100%;\n height: 100%;\n min-width: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score {\n display: flex;\n flex: 0 0 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-100);\n width: auto;\n height: 100%;\n min-width: 0;\n padding: var(--dimension-space-100) var(--dimension-space-200);\n border-inline-end: var(--ds-card-overview-divider);\n box-sizing: border-box;\n}\n\n:host(.card-overview--compact) .card-overview__score-label-row {\n display: contents;\n min-height: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score-label,\n:host(.card-overview--compact) .card-overview__score-figure,\n:host(.card-overview--compact) .card-overview__trend {\n flex: 0 0 auto;\n}\n\n:host(.card-overview--compact) .card-overview__score-figure {\n display: flex;\n align-self: center;\n align-items: baseline;\n gap: var(--dimension-space-050);\n margin-block-start: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score-band {\n display: none;\n}\n\n:host(.card-overview--compact) .card-overview__header {\n display: flex;\n flex: 1 1 auto;\n grid-row: auto;\n grid-column: auto;\n align-items: center;\n gap: var(--dimension-space-100);\n height: 100%;\n min-width: 0;\n padding: var(--dimension-space-100);\n border-block-end: none;\n box-sizing: border-box;\n}\n\n:host(.card-overview--compact) .card-overview__period {\n display: flex;\n flex: 1 1 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-050);\n min-width: 0;\n min-height: var(--ds-control-height);\n padding-inline: var(--ds-control-padding-inline);\n overflow: hidden;\n text-align: start;\n white-space: nowrap;\n}\n\n:host(.card-overview--compact) .card-overview__period-current,\n:host(.card-overview--compact) .card-overview__period-comparison {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n:host(.card-overview--compact) .card-overview__period-current {\n flex: 0 1 auto;\n}\n\n:host(.card-overview--compact) .card-overview__period-comparison {\n flex: 1 1 auto;\n}\n\n:host(.card-overview--compact) .card-overview__filter {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n padding: 0;\n}\n",
|
|
369
|
+
"content": "@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../styles/control-elevation.css';\n\n/*\n * A permanently dark surface, independent of the page theme, so the summary bar\n * reads the same on every product surface. Every foreground, border, and\n * interaction token below therefore comes from the always-dark family rather\n * than the theme-reactive one.\n */\n\n:host {\n display: block;\n box-sizing: border-box;\n /* Drives the narrow layout below from the card's own width, not the viewport's. */\n container-type: inline-size;\n /* A summary is read, not copied; stray selection while clicking cells is noise. */\n -webkit-user-select: none;\n user-select: none;\n /* Width a metric may shrink to before the grid drops a column. */\n --ds-card-overview-metric-min: calc(var(--dimension-size-400) * 5);\n --ds-card-overview-divider-width: var(--dimension-stroke-width-012);\n --ds-card-overview-divider-color: var(--color-always-dark-border-tertiary);\n --ds-card-overview-divider: var(--ds-card-overview-divider-width) solid\n var(--ds-card-overview-divider-color);\n color: var(--color-always-dark-foreground-primary);\n}\n\n.card-overview__surface {\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n border-radius: var(--dimension-radius-050);\n background: var(--color-always-dark-background);\n color: var(--color-always-dark-foreground-primary);\n overflow-anchor: none;\n}\n\n/*\n * The visual surface owns elevation outside this clipping viewport. Keeping\n * those layers separate lets a page shrink the surface while content moves and\n * clips inside it without cutting off the shadow or inset highlight.\n */\n.card-overview__clip {\n height: 100%;\n min-width: 0;\n border-radius: inherit;\n background: inherit;\n overflow: clip;\n}\n\n:host(.card-overview--scroll-collapsing) {\n height: var(--ds-card-overview-expanded-height);\n pointer-events: none;\n}\n\n:host(.card-overview--scroll-collapsing) .card-overview__surface {\n height: var(--ds-card-overview-visible-height);\n pointer-events: auto;\n will-change: height;\n}\n\n:host(.card-overview--scroll-collapsing) .card-overview__layout {\n transform: translateY(calc(-1 * var(--ds-card-overview-content-offset)));\n will-change: transform;\n}\n\n.card-overview__layout {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n align-items: stretch;\n min-width: 0;\n}\n\n.card-overview__layout--has-score {\n grid-template-columns: auto minmax(0, 1fr);\n}\n\n/*\n * Transparent at regular widths so the score and header participate in the\n * inner layout grid directly. At the narrow breakpoint it becomes the unified\n * summary block above the metrics.\n */\n.card-overview__summary {\n display: contents;\n}\n\n/* ── Score ────────────────────────────────────────────────────────────────── */\n\n/*\n * Hugs its content and never shrinks, so the headline figure keeps its size and\n * the metric grid absorbs the remaining width.\n */\n/*\n * Top aligned rather than centred, and padded to match the header, so the score\n * label sits in the same 32px box at the same offset as the period text beside\n * it. Both use the same type variant, so their baselines land together.\n */\n.card-overview__score {\n display: flex;\n grid-row: 1 / span 2;\n grid-column: 1;\n flex: 0 0 auto;\n flex-direction: column;\n gap: 0;\n min-width: 0;\n padding: var(--dimension-space-100) var(--dimension-space-200);\n border-inline-end: var(--ds-card-overview-divider);\n}\n\n.card-overview__score--resolved {\n min-inline-size: max-content;\n}\n\n/* The control-height box that aligns this label with the period text. */\n.card-overview__score-label-row {\n display: flex;\n align-items: center;\n min-height: var(--ds-control-height);\n}\n\n/*\n * The score is its own column, so its rows cannot baseline-align with the measure\n * rows through flex or grid — those only align items that share a line. These two\n * offsets pull the column onto the measure baselines instead.\n *\n * They are compensation for how much lower a display-scale figure sits in its line\n * box than the body text it aligns to, so they are coupled to the type pairing:\n * change the score or measure variants and both need re-measuring.\n */\n.card-overview__score-figure {\n display: flex;\n align-items: baseline;\n gap: var(--dimension-space-050);\n min-width: 0;\n margin-block-start: calc(-1 * (var(--dimension-space-150) + var(--dimension-space-012)));\n}\n\n.card-overview__score-value {\n flex: 0 0 auto;\n}\n\n/* Second half of the baseline compensation described above the score figure. */\n.card-overview__score-band {\n margin-block-start: calc(-1 * var(--dimension-space-025));\n}\n\n/*\n * A skeleton bar is a solid block, so in a baseline-aligned row it aligns on its\n * bottom edge while the text it stands in for aligns on its baseline. Next to a\n * display-scale figure that difference is the descent under the figure's\n * baseline, which drops the bar below where the resolved trend sits.\n *\n * The offset is applied with relative positioning rather than a margin: a margin\n * is not part of a synthesized baseline, so it grows the figure instead of moving\n * the bar, which then pushes the band below it out of place.\n *\n * Only needed beside the score figure; the measure rows pair body variants whose\n * bottom edge and baseline already agree.\n */\n.card-overview__skeleton-baseline {\n position: relative;\n inset-block-start: calc(-1 * var(--dimension-space-150));\n}\n\n.card-overview__score--error {\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-050);\n color: var(--color-reference-dark-red-30-l70-c20-bold);\n}\n\n/* ── Body ─────────────────────────────────────────────────────────────────── */\n\n/*\n * Takes the remaining inline space. The minimum is what makes the host wrap: once\n * the score and roughly two measure columns no longer fit side by side, the body\n * drops below the score instead of squeezing it. `min()` keeps that from becoming\n * an overflow floor on very narrow viewports.\n */\n.card-overview__body {\n display: flex;\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n min-width: 0;\n}\n\n.card-overview__body--has-score {\n grid-column: 2;\n}\n\n/*\n * One control row: 8px of padding on every side around a 32px md control, so the\n * bar is 8 + 32 + 8. Both ends of the bar are control-shaped boxes — the period\n * text on the left and the slotted control on the right — so the row reads\n * balanced rather than text on one side and a control on the other.\n */\n.card-overview__header {\n display: flex;\n grid-row: 1;\n grid-column: 1;\n align-items: center;\n gap: var(--dimension-space-100);\n min-width: 0;\n padding: var(--dimension-space-100);\n border-block-end: var(--ds-card-overview-divider);\n box-sizing: border-box;\n}\n\n.card-overview__header--has-score {\n grid-column: 2;\n}\n\n/*\n * Matches the control's box on the opposite end, including its inline padding, so\n * the text lands at the same optical inset the slotted control does:\n * 8 from the bar + 6 from this box + 2 from the label inset = 16.\n *\n * Nowrap keeps the row at the control height; the period truncates rather than\n * growing the bar to two lines.\n */\n.card-overview__period {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n gap: var(--dimension-space-050);\n min-height: var(--ds-control-height);\n min-width: 0;\n padding-inline: var(--ds-control-padding-inline);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The period control is slotted, so only reserve space for it. */\n.card-overview__filter {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n}\n\n/* ── Metrics ──────────────────────────────────────────────────────────────── */\n\n/*\n * `auto-fit` gives every occupied track an equal share while all metrics fit on\n * one row. Once wrapping creates another row, its cells stay on those same\n * tracks, so a partial final row leaves empty space instead of stretching one\n * measure wider than its peers.\n *\n * Dividers are painted outward from each cell and clipped by this grid. That\n * leaves only the lines between cells: anything emitted from the final column or\n * final row lands outside the grid and is clipped without needing to detect the\n * number of tracks.\n *\n * `flex: 1 1 auto` makes the grid absorb whatever height the body has\n * left, so cells reach the card bottom and their hover wash has no dead strip.\n */\n.card-overview__metrics {\n display: grid;\n flex: 1 1 auto;\n grid-template-columns: repeat(\n auto-fit,\n minmax(min(100%, var(--ds-card-overview-metric-min)), 1fr)\n );\n align-content: stretch;\n min-width: 0;\n overflow: clip;\n}\n\n/*\n * Square by design. Cells tile edge to edge against their dividers, so a radius\n * would break the seam; the card's own radius clips the outer corners instead.\n *\n * Padding is plain rather than control-derived: a cell is a block of content, not\n * a control box that has to optically match something on the far side.\n */\n.card-overview__metric {\n display: flex;\n position: relative;\n flex-direction: column;\n gap: var(--dimension-space-050);\n min-width: 0;\n padding: var(--dimension-space-175) var(--dimension-space-200);\n border-radius: 0;\n box-shadow:\n var(--ds-card-overview-divider-width) 0 0 0 var(--ds-card-overview-divider-color),\n 0 var(--ds-card-overview-divider-width) 0 0 var(--ds-card-overview-divider-color);\n}\n\n.card-overview__metric[role='button'] {\n cursor: default;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.card-overview__metric-label {\n min-width: 0;\n}\n\n.card-overview__metric-figure {\n display: flex;\n align-items: baseline;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n/* ── Trend ────────────────────────────────────────────────────────────────── */\n\n/*\n * Tone arrives resolved from the caller: the arrow direction and the sentiment\n * are independent, so a falling metric can read positive or neutral. The arrow is\n * a glyph inside this text node, so it always matches the surrounding weight.\n */\n.card-overview__trend {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n/* ── Narrow layout ────────────────────────────────────────────────────────── */\n\n/*\n * Below roughly two measure columns the card becomes a full stack. The summary\n * wrapper owns one block above the metrics: score label, figure, and trend stay\n * together at the start; the period control sits at the upper end; and the period\n * copy follows the score instead of becoming a separate divided row.\n */\n/*\n * Selectors are host-qualified purely for specificity: the compiled sheet places\n * these rules ahead of the base ones, so a plain class here would only ever add\n * properties and never override them.\n */\n@container (max-width: 480px) {\n :host .card-overview__layout {\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n }\n\n :host .card-overview__summary {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto;\n border-block-end: var(--ds-card-overview-divider);\n }\n\n :host .card-overview__score {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-rows: auto auto;\n grid-template-columns: auto auto;\n justify-content: start;\n column-gap: var(--dimension-space-100);\n width: auto;\n padding-block-end: var(--dimension-space-100);\n padding-inline-end: var(--dimension-space-100);\n border-inline-end: none;\n }\n\n :host .card-overview__score-label-row {\n grid-row: 1;\n grid-column: 1 / -1;\n min-height: var(--ds-control-height);\n }\n\n /*\n * The wide score uses optical offsets to meet the neighbouring metric\n * baselines. In the stacked summary those neighbours are gone, so natural\n * document flow keeps the label, figure, band, and period clearly ordered.\n */\n :host .card-overview__score-band,\n :host .card-overview__score-figure {\n grid-row: 2;\n margin-block-start: 0;\n }\n\n :host .card-overview__score-figure {\n grid-column: 1;\n align-self: baseline;\n gap: var(--dimension-space-100);\n }\n\n :host .card-overview__score-band {\n grid-column: 2;\n align-self: baseline;\n display: none;\n }\n\n :host .card-overview__header {\n display: grid;\n grid-row: 1;\n grid-column: 2;\n grid-template-rows: auto auto;\n align-content: start;\n gap: 0;\n padding: 0;\n border-block-end: none;\n }\n\n :host .card-overview__filter {\n grid-row: 1;\n grid-column: 1;\n align-self: start;\n justify-self: end;\n padding: var(--dimension-space-100) var(--dimension-space-100) 0 0;\n }\n\n :host .card-overview__period {\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n align-items: flex-end;\n justify-self: stretch;\n gap: 0;\n min-height: 0;\n padding-block: var(--dimension-space-150) var(--dimension-space-100);\n padding-inline: var(--dimension-space-100) var(--dimension-space-200);\n overflow: visible;\n text-align: end;\n white-space: nowrap;\n }\n\n :host .card-overview__body {\n grid-row: 2;\n grid-column: 1;\n }\n}\n\n/*\n * Page shells may enter a responsive mode before this card itself becomes\n * narrow. The explicit stacked layout applies the same composition without\n * coupling CardOverview to a viewport breakpoint.\n */\n:host(.card-overview--stacked) .card-overview__layout {\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n}\n\n:host(.card-overview--stacked) .card-overview__summary {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-columns: minmax(0, 1fr) auto;\n grid-template-rows: auto;\n border-block-end: var(--ds-card-overview-divider);\n}\n\n:host(.card-overview--stacked) .card-overview__score {\n display: grid;\n grid-row: 1;\n grid-column: 1;\n grid-template-rows: auto auto;\n grid-template-columns: auto auto;\n justify-content: start;\n column-gap: var(--dimension-space-100);\n width: auto;\n padding-block-end: var(--dimension-space-100);\n padding-inline-end: var(--dimension-space-100);\n border-inline-end: none;\n}\n\n:host(.card-overview--stacked) .card-overview__score-label-row {\n grid-row: 1;\n grid-column: 1 / -1;\n min-height: var(--ds-control-height);\n}\n\n:host(.card-overview--stacked) .card-overview__score-band,\n:host(.card-overview--stacked) .card-overview__score-figure {\n grid-row: 2;\n margin-block-start: 0;\n}\n\n:host(.card-overview--stacked) .card-overview__score-figure {\n grid-column: 1;\n align-self: baseline;\n gap: var(--dimension-space-100);\n}\n\n:host(.card-overview--stacked) .card-overview__score-band {\n grid-column: 2;\n align-self: baseline;\n display: none;\n}\n\n:host(.card-overview--stacked) .card-overview__header {\n display: grid;\n grid-row: 1;\n grid-column: 2;\n grid-template-rows: auto auto;\n align-content: start;\n gap: 0;\n padding: 0;\n border-block-end: none;\n}\n\n:host(.card-overview--stacked) .card-overview__filter {\n grid-row: 1;\n grid-column: 1;\n align-self: start;\n justify-self: end;\n padding: var(--dimension-space-100) var(--dimension-space-100) 0 0;\n}\n\n:host(.card-overview--stacked) .card-overview__period {\n grid-row: 2;\n grid-column: 1;\n flex-direction: column;\n align-items: flex-end;\n justify-self: stretch;\n gap: 0;\n min-height: 0;\n padding-block: var(--dimension-space-150) var(--dimension-space-100);\n padding-inline: var(--dimension-space-100) var(--dimension-space-200);\n overflow: visible;\n text-align: end;\n white-space: nowrap;\n}\n\n:host(.card-overview--stacked) .card-overview__body {\n grid-row: 2;\n grid-column: 1;\n}\n\n:host(.card-overview--stacked) .card-overview__metrics {\n grid-template-columns: minmax(0, 1fr);\n}\n\n/* ── Compact presentation ───────────────────────────────────────────────── */\n\n/*\n * Matches the shared 48px bar contract: one 32px control row with 8px above\n * and below. The score remains a distinct leading cell, while reporting copy\n * absorbs the available width and the application-owned control never shrinks.\n */\n:host(.card-overview--compact) {\n height: var(--dimension-size-600);\n min-height: var(--dimension-size-600);\n}\n\n:host(.card-overview--compact) .card-overview__surface {\n height: 100%;\n}\n\n:host(.card-overview--compact) .card-overview__layout,\n:host(.card-overview--compact) .card-overview__summary {\n display: flex;\n width: 100%;\n height: 100%;\n min-width: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score {\n display: flex;\n flex: 0 0 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-100);\n width: auto;\n height: 100%;\n min-width: 0;\n padding: var(--dimension-space-100) var(--dimension-space-200);\n border-inline-end: var(--ds-card-overview-divider);\n box-sizing: border-box;\n}\n\n:host(.card-overview--compact) .card-overview__score-label-row {\n display: contents;\n min-height: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score-label,\n:host(.card-overview--compact) .card-overview__score-figure,\n:host(.card-overview--compact) .card-overview__trend {\n flex: 0 0 auto;\n}\n\n:host(.card-overview--compact) .card-overview__score-figure {\n display: flex;\n align-self: center;\n align-items: baseline;\n gap: var(--dimension-space-050);\n margin-block-start: 0;\n}\n\n:host(.card-overview--compact) .card-overview__score-band {\n display: none;\n}\n\n:host(.card-overview--compact) .card-overview__header {\n display: flex;\n flex: 1 1 auto;\n grid-row: auto;\n grid-column: auto;\n align-items: center;\n gap: var(--dimension-space-100);\n height: 100%;\n min-width: 0;\n padding: var(--dimension-space-100);\n border-block-end: none;\n box-sizing: border-box;\n}\n\n:host(.card-overview--compact) .card-overview__period {\n display: flex;\n flex: 1 1 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--dimension-space-050);\n min-width: 0;\n min-height: var(--ds-control-height);\n padding-inline: var(--ds-control-padding-inline);\n overflow: hidden;\n text-align: start;\n white-space: nowrap;\n}\n\n:host(.card-overview--compact) .card-overview__period-current,\n:host(.card-overview--compact) .card-overview__period-comparison {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n:host(.card-overview--compact) .card-overview__period-current {\n flex: 0 1 auto;\n}\n\n:host(.card-overview--compact) .card-overview__period-comparison {\n flex: 1 1 auto;\n}\n\n:host(.card-overview--compact) .card-overview__filter {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n padding: 0;\n}\n",
|
|
347
370
|
"type": "registry:ui"
|
|
348
371
|
},
|
|
349
372
|
{
|
|
350
373
|
"path": "src/wc/components/CardOverview/CardOverview.tsx",
|
|
351
|
-
"content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport type { TextColor, TextVariant } from '../Text/text-types';\nimport type { MetricTrend } from '../../utils/metric-change';\nimport type {\n CardOverviewLayout,\n CardOverviewVariant,\n OverviewMetric,\n OverviewScore,\n} from './card-overview-types';\n\n/** Metric cells below this width drop a grid column rather than compress further. */\nconst DEFAULT_METRIC_MIN_WIDTH = 'var(--dimension-menu-width-xs)';\n\n/** Skeleton cells rendered while loading with no metrics supplied yet. */\nconst LOADING_PLACEHOLDER_COUNT = 4;\n\n/** Maximum number of comparable measures the summary bar presents at once. */\nconst MAX_METRIC_COUNT = 6;\n\nconst TREND_COLORS: Record<MetricTrend['tone'], TextColor> = {\n positive: 'var(--color-always-dark-foreground-positive)',\n negative: 'var(--color-always-dark-foreground-negative)',\n neutral: 'var(--color-always-dark-foreground-secondary)',\n};\n\nconst ALWAYS_DARK_PRIMARY: TextColor = 'var(--color-always-dark-foreground-primary)';\nconst ALWAYS_DARK_SECONDARY: TextColor = 'var(--color-always-dark-foreground-secondary)';\n\n@Component({\n tag: 'ds-card-overview',\n styleUrl: 'CardOverview.css',\n scoped: true,\n})\nexport class CardOverview {\n @Element() el!: HTMLElement;\n\n /** Full summary card, or the condensed 48px summary bar. */\n @Prop() variant: CardOverviewVariant = 'default';\n\n /** Intrinsic responsive layout, or a page-owned single-column stack. */\n @Prop() layout: CardOverviewLayout = 'auto';\n\n /** Leading summary block. Omit to render the bar without a headline figure. */\n @Prop() score: OverviewScore | undefined;\n\n /** Current reporting period, for example `Jun 29, 2026 – Jul 26, 2026`. */\n @Prop() periodLabel: string = '';\n\n /** Comparison caption, for example `vs Previous period`. */\n @Prop() comparisonLabel: string = '';\n\n /** Measures rendered in the responsive grid. Only the first six are shown. */\n @Prop() metrics: OverviewMetric[] = [];\n\n /**\n * Width a metric cell may shrink to before the grid drops a column. The grid\n * reflows and then stacks from this alone, so no measurement is required.\n */\n @Prop() metricMinWidth: string = DEFAULT_METRIC_MIN_WIDTH;\n\n /** Replace the score and metrics with skeletons while data resolves. */\n @Prop() isLoading: boolean = false;\n\n /** Message shown in place of the score when its figure cannot be resolved. */\n @Prop() scoreErrorMessage: string | undefined;\n\n /** Accessible name for the region. */\n @Prop() overviewLabel: string = 'Overview';\n\n /** Emitted when a metric that is not inactive is activated. */\n @Event() dsMetricSelect!: EventEmitter<OverviewMetric>;\n\n /** Roving tab stop across selectable metrics. */\n @State() private focusedMetricIndex: number = 0;\n\n private get hasScore(): boolean {\n return this.score !== undefined || this.scoreErrorMessage !== undefined || this.isLoading;\n }\n\n private get visibleMetrics(): OverviewMetric[] {\n return this.metrics.slice(0, MAX_METRIC_COUNT);\n }\n\n /** Index of the roving tab stop, skipping inactive metrics. */\n private get rovingIndex(): number {\n const first = this.visibleMetrics.findIndex(metric => !metric.isInactive);\n if (first < 0) return -1;\n const current = this.visibleMetrics[this.focusedMetricIndex];\n return current && !current.isInactive ? this.focusedMetricIndex : first;\n }\n\n private selectMetric(metric: OverviewMetric, index: number) {\n if (metric.isInactive) return;\n this.focusedMetricIndex = index;\n this.dsMetricSelect.emit(metric);\n }\n\n /** Move the roving tab stop, wrapping and skipping inactive metrics. */\n private moveFocus(from: number, step: number) {\n const metrics = this.visibleMetrics;\n const count = metrics.length;\n if (count === 0) return;\n\n for (let offset = 1; offset <= count; offset += 1) {\n // Positive modulo so a backwards step from the first metric wraps to the last.\n const next = (((from + step * offset) % count) + count) % count;\n if (metrics[next]?.isInactive) continue;\n\n this.focusedMetricIndex = next;\n const cells = this.el.querySelectorAll<HTMLElement>('.card-overview__metric');\n cells[next]?.focus();\n return;\n }\n }\n\n private handleMetricKeyDown = (event: KeyboardEvent, index: number) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n this.moveFocus(index, 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n this.moveFocus(index, -1);\n break;\n default:\n break;\n }\n };\n\n /**\n * A skeleton bar matching the type metrics of the text it stands in for, so the\n * loading state holds the same shape and rhythm as the resolved content.\n *\n * `background` is always passed: the default base is the theme's quaternary\n * foreground, which is black and therefore invisible on this permanently dark\n * surface.\n */\n private bar(textVariant: TextVariant, width: string, className?: string) {\n return (\n <ds-skeleton\n class={className}\n variant=\"text\"\n textVariant={textVariant}\n width={width}\n background=\"always-dark\"\n />\n );\n }\n\n /**\n * Arrow and change in one text node.\n *\n * The arrow is a typeface glyph rather than an icon so it inherits the exact\n * size, weight, and baseline of the value beside it; an icon would need its\n * own size scale and would drift whenever the type scale changed.\n *\n * Tone is supplied by the caller and never inferred here.\n */\n private renderTrend(trend: MetricTrend | undefined, variant: TextVariant, emphasis = false) {\n if (!trend) return null;\n return (\n <ds-text\n as=\"span\"\n class={{\n 'card-overview__trend': true,\n [`card-overview__trend--${trend.tone}`]: true,\n }}\n variant={variant}\n emphasis={emphasis}\n color={TREND_COLORS[trend.tone]}\n fontFeature=\"tabular-nums\"\n >\n {trend.direction === 'up' ? '↑' : '↓'} {trend.value}\n </ds-text>\n );\n }\n\n private renderScore(compact: boolean) {\n if (this.isLoading) {\n if (compact) {\n return (\n <div class=\"card-overview__score ds-control--md\" part=\"score\">\n {this.bar('text-body-medium', '40px', 'card-overview__score-label')}\n <div class=\"card-overview__score-figure\">\n {this.bar('text-body-medium', '24px')}\n {this.bar('text-body-medium', '32px')}\n </div>\n </div>\n );\n }\n\n return (\n <div class=\"card-overview__score ds-control--md\" part=\"score\">\n {/*\n * The label row is kept but left empty. It carries the control-height\n * box that aligns the score block with the period control, so dropping\n * it would shift the whole block up; a bar inside it would collide with\n * the figure, which sits under the label by design.\n */}\n <div class=\"card-overview__score-label-row\" />\n <div class=\"card-overview__score-figure\">\n {this.bar('text-display-medium', '56px')}\n {this.bar('text-body-small', '24px', 'card-overview__skeleton-baseline')}\n </div>\n {/*\n * Class goes on the skeleton itself, matching the resolved band. In a\n * wrapper the bar picks up inline leading and drops below the resolved\n * band position.\n */}\n {this.bar(\n 'text-caption',\n '80px',\n 'card-overview__score-band card-overview__score-band--loading'\n )}\n </div>\n );\n }\n\n if (this.scoreErrorMessage) {\n return (\n <div class=\"card-overview__score card-overview__score--error\" part=\"score\" role=\"alert\">\n <ds-icon name=\"CircleExclamation\" size=\"sm\" color=\"inherit\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"inherit\">\n {this.scoreErrorMessage}\n </ds-text>\n </div>\n );\n }\n\n const score = this.score;\n if (!score) return null;\n\n return (\n <div class=\"card-overview__score card-overview__score--resolved ds-control--md\" part=\"score\">\n <div class=\"card-overview__score-label-row\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-label\"\n variant=\"text-body-medium\"\n emphasis={!compact}\n color={ALWAYS_DARK_SECONDARY}\n >\n {score.label}\n </ds-text>\n </div>\n <div class=\"card-overview__score-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-value\"\n variant={compact ? 'text-body-medium' : 'text-display-medium'}\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {score.value}\n </ds-text>\n {this.renderTrend(\n score.trend,\n compact ? 'text-body-medium' : 'text-body-small',\n !compact\n )}\n </div>\n {score.band && !compact && (\n <ds-text\n as=\"span\"\n class=\"card-overview__score-band\"\n variant=\"text-caption\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {score.band}\n </ds-text>\n )}\n </div>\n );\n }\n\n private renderMetric(metric: OverviewMetric, index: number) {\n const selectable = !metric.isInactive;\n const label = (\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-label\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {metric.label}\n </ds-text>\n );\n\n return (\n <div\n class={{\n 'card-overview__metric': true,\n 'card-overview__metric--inactive': !selectable,\n // Selection targets get the shared wash; press scaling is not used here\n // so the grid keeps its columns aligned. See docs/control-press-policy.md.\n 'ds-interaction-fill': selectable,\n // The card is permanently dark, so interaction tokens come from that family.\n 'ds-interaction-fill--on-always-dark': selectable,\n 'ds-focus-ring-inset': selectable,\n }}\n part=\"metric\"\n role={selectable ? 'button' : undefined}\n tabIndex={selectable && index === this.rovingIndex ? 0 : -1}\n onClick={() => this.selectMetric(metric, index)}\n onKeyDown={event => this.handleMetricKeyDown(event, index)}\n onFocusin={() => {\n if (selectable) this.focusedMetricIndex = index;\n }}\n >\n {metric.labelTooltip ? (\n <ds-tooltip label={metric.labelTooltip} side=\"top\" size=\"sm\">\n {label}\n </ds-tooltip>\n ) : (\n label\n )}\n <div class=\"card-overview__metric-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-value\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {metric.value}\n </ds-text>\n {this.renderTrend(metric.trend, 'text-body-medium')}\n </div>\n </div>\n );\n }\n\n private renderMetrics() {\n if (this.isLoading && this.metrics.length === 0) {\n return Array.from({ length: LOADING_PLACEHOLDER_COUNT }, (_, index) => (\n <div class=\"card-overview__metric\" key={`loading-${index}`}>\n {this.bar('text-body-small', '70%', 'card-overview__metric-label')}\n <div class=\"card-overview__metric-figure\">\n {this.bar('text-body-medium', '36px')}\n {/* text-body-medium, matching renderTrend for a metric — a smaller\n * variant here leaves the trend bar short of the value beside it. */}\n {this.bar('text-body-medium', '40px')}\n </div>\n </div>\n ));\n }\n\n return this.visibleMetrics.map((metric, index) => this.renderMetric(metric, index));\n }\n\n render() {\n const hasHeader = Boolean(this.periodLabel || this.comparisonLabel);\n const compact = this.variant === 'compact';\n\n return (\n <Host\n class={{\n 'card-overview': true,\n 'card-overview--has-score': this.hasScore,\n 'card-overview--compact': compact,\n 'card-overview--stacked': !compact && this.layout === 'stacked',\n // Split shadow and inset highlight, so the opaque surface cannot cover\n // the highlight the way a combined elevation shadow would.\n 'ds-control-elevation': true,\n 'ds-control-elevation--sm': !compact,\n 'ds-control-elevation--floating': compact,\n }}\n role=\"region\"\n aria-label={this.overviewLabel}\n aria-busy={this.isLoading ? 'true' : undefined}\n style={{ '--ds-card-overview-metric-min': this.metricMinWidth }}\n >\n <div\n class={{\n 'card-overview__layout': true,\n 'card-overview__layout--has-score': this.hasScore,\n }}\n >\n <div class=\"card-overview__summary\">\n {this.hasScore && this.renderScore(compact)}\n\n {/* `ds-control--md` supplies the 32px height and the label inset below. */}\n <div\n class={{\n 'card-overview__header': true,\n 'card-overview__header--has-score': this.hasScore,\n 'ds-control--md': true,\n }}\n >\n {this.isLoading ? (\n <div class=\"card-overview__period ds-control-label-box\">\n {this.bar('text-body-medium', '184px')}\n {this.bar('text-body-medium', '248px')}\n </div>\n ) : (\n hasHeader && (\n <div class=\"card-overview__period ds-control-label-box\">\n {this.periodLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-current ds-control-label-box\"\n variant=\"text-body-medium\"\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n >\n {this.periodLabel}\n </ds-text>\n )}\n {this.comparisonLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-comparison ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {this.comparisonLabel}\n </ds-text>\n )}\n </div>\n )\n )}\n <div class=\"card-overview__filter\">\n {/*\n Period control is application owned: it varies by product surface.\n While loading it is stood in for by a control-shaped skeleton so the\n header keeps its resolved proportions.\n */}\n {this.isLoading ? (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"md\"\n width=\"128px\"\n background=\"always-dark\"\n />\n ) : (\n <slot name=\"filter\" />\n )}\n </div>\n </div>\n </div>\n\n {!compact && (\n <div\n class={{\n 'card-overview__body': true,\n 'card-overview__body--has-score': this.hasScore,\n }}\n >\n <div class=\"card-overview__metrics\" part=\"metrics\">\n {this.renderMetrics()}\n </div>\n\n <slot name=\"footer\" />\n </div>\n )}\n </div>\n </Host>\n );\n }\n}\n",
|
|
374
|
+
"content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport { resolveCssLengthPx, TOKEN_DEFAULTS } from '../../utils';\nimport type { TextColor, TextVariant } from '../Text/text-types';\nimport type { MetricTrend } from '../../utils/metric-change';\nimport type {\n CardOverviewLayout,\n CardOverviewVariant,\n OverviewMetric,\n OverviewScore,\n} from './card-overview-types';\nimport {\n findNextOverviewMetricIndex,\n resolveCardOverviewCollapseGeometry,\n resolveOverviewRovingIndex,\n} from './card-overview-controller';\n\n/** Metric cells below this width drop a grid column rather than compress further. */\nconst DEFAULT_METRIC_MIN_WIDTH = 'var(--dimension-menu-width-xs)';\n\n/** Skeleton cells rendered while loading with no metrics supplied yet. */\nconst LOADING_PLACEHOLDER_COUNT = 4;\n\n/** Maximum number of comparable measures the summary bar presents at once. */\nconst MAX_METRIC_COUNT = 6;\n\nconst TREND_COLORS: Record<MetricTrend['tone'], TextColor> = {\n // CardOverview is permanently always-dark. Reference-level dark-surface\n // intent tokens avoid theme overrides and retain AA contrast at body size.\n positive: 'var(--color-reference-dark-green-145-l70-c19-bold)',\n negative: 'var(--color-reference-dark-red-30-l70-c20-bold)',\n neutral: 'var(--color-always-dark-foreground-secondary)',\n};\n\nconst ALWAYS_DARK_PRIMARY: TextColor = 'var(--color-always-dark-foreground-primary)';\nconst ALWAYS_DARK_SECONDARY: TextColor = 'var(--color-always-dark-foreground-secondary)';\n\n@Component({\n tag: 'ds-card-overview',\n styleUrl: 'CardOverview.css',\n scoped: true,\n})\nexport class CardOverview {\n @Element() el!: HTMLElement;\n\n /** Full summary card, or the condensed 48px summary bar. */\n @Prop() variant: CardOverviewVariant = 'default';\n\n /** Intrinsic responsive layout, or a page-owned single-column stack. */\n @Prop() layout: CardOverviewLayout = 'auto';\n\n /** Leading summary block. Omit to render the bar without a headline figure. */\n @Prop() score: OverviewScore | undefined;\n\n /** Current reporting period, for example `Jun 29, 2026 – Jul 26, 2026`. */\n @Prop() periodLabel: string = '';\n\n /** Comparison caption, for example `vs Previous period`. */\n @Prop() comparisonLabel: string = '';\n\n /** Measures rendered in the responsive grid. Only the first six are shown. */\n @Prop() metrics: OverviewMetric[] = [];\n\n /**\n * Width a metric cell may shrink to before the grid drops a column. The grid\n * reflows and then stacks from this alone, so no measurement is required.\n */\n @Prop() metricMinWidth: string = DEFAULT_METRIC_MIN_WIDTH;\n\n /** Replace the score and metrics with skeletons while data resolves. */\n @Prop() isLoading: boolean = false;\n\n /** Message shown in place of the score when its figure cannot be resolved. */\n @Prop() scoreErrorMessage: string | undefined;\n\n /** Accessible name for the region. */\n @Prop() overviewLabel: string = 'Overview';\n\n /**\n * Page-controlled visual collapse from the full card (`0`) toward its 48px\n * compact handoff height (`1`). The component preserves its expanded flow\n * height, keeps elevation on the shrinking surface, and clips translated\n * content internally. The page still owns sticky positioning and the final\n * swap to `variant=\"compact\"`.\n */\n @Prop() scrollCollapseProgress: number = 0;\n\n /** Emitted when a metric that is not inactive is activated. */\n @Event() dsMetricSelect!: EventEmitter<OverviewMetric>;\n\n /** Roving tab stop across selectable metrics. */\n @State() private focusedMetricIndex: number = 0;\n @State() private expandedHeight: number = 0;\n\n private layoutEl?: HTMLElement;\n private layoutResizeObserver: ResizeObserver | null = null;\n\n componentDidLoad() {\n this.observeLayout();\n }\n\n componentDidRender() {\n // Re-bind after HMR when DidLoad's observer was dropped.\n if (!this.layoutResizeObserver) this.observeLayout();\n }\n\n disconnectedCallback() {\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = null;\n }\n\n private get hasScore(): boolean {\n return this.score !== undefined || this.scoreErrorMessage !== undefined || this.isLoading;\n }\n\n private get visibleMetrics(): OverviewMetric[] {\n return this.metrics.slice(0, MAX_METRIC_COUNT);\n }\n\n private get resolvedScrollCollapseProgress(): number {\n if (this.variant === 'compact' || !Number.isFinite(this.scrollCollapseProgress)) return 0;\n return Math.min(1, Math.max(0, this.scrollCollapseProgress));\n }\n\n private get scrollCollapseGeometry() {\n const compactHeight = resolveCssLengthPx(TOKEN_DEFAULTS.size600, 48);\n return resolveCardOverviewCollapseGeometry({\n variant: this.variant,\n progress: this.resolvedScrollCollapseProgress,\n expandedHeight: this.expandedHeight,\n compactHeight,\n });\n }\n\n private observeLayout() {\n const layout = this.layoutEl;\n if (!layout) return;\n\n const updateHeight = (height: number) => {\n if (height > 0 && Math.abs(height - this.expandedHeight) >= 0.5) {\n this.expandedHeight = height;\n }\n };\n\n updateHeight(layout.getBoundingClientRect().height);\n if (typeof ResizeObserver === 'undefined') return;\n\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = new ResizeObserver(entries => {\n const height = entries[0]?.contentRect.height;\n if (height !== undefined) updateHeight(height);\n });\n this.layoutResizeObserver.observe(layout);\n }\n\n /** Index of the roving tab stop, skipping inactive metrics. */\n private get rovingIndex(): number {\n return resolveOverviewRovingIndex(this.visibleMetrics, this.focusedMetricIndex);\n }\n\n private selectMetric(metric: OverviewMetric, index: number) {\n if (metric.isInactive) return;\n this.focusedMetricIndex = index;\n this.dsMetricSelect.emit(metric);\n }\n\n /** Move the roving tab stop, wrapping and skipping inactive metrics. */\n private moveFocus(from: number, step: number) {\n const metrics = this.visibleMetrics;\n const next = findNextOverviewMetricIndex(metrics, from, step < 0 ? -1 : 1);\n if (next < 0) return;\n\n this.focusedMetricIndex = next;\n const cells = this.el.querySelectorAll<HTMLElement>('.card-overview__metric');\n cells[next]?.focus();\n }\n\n private handleMetricKeyDown = (event: KeyboardEvent, index: number) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n this.moveFocus(index, 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n this.moveFocus(index, -1);\n break;\n default:\n break;\n }\n };\n\n /**\n * A skeleton bar matching the type metrics of the text it stands in for, so the\n * loading state holds the same shape and rhythm as the resolved content.\n *\n * `background` is always passed: the default base is the theme's quaternary\n * foreground, which is black and therefore invisible on this permanently dark\n * surface.\n */\n private bar(textVariant: TextVariant, width: string, className?: string) {\n return (\n <ds-skeleton\n class={className}\n variant=\"text\"\n textVariant={textVariant}\n width={width}\n background=\"always-dark\"\n />\n );\n }\n\n /**\n * Arrow and change in one text node.\n *\n * The arrow is a typeface glyph rather than an icon so it inherits the exact\n * size, weight, and baseline of the value beside it; an icon would need its\n * own size scale and would drift whenever the type scale changed.\n *\n * Tone is supplied by the caller and never inferred here.\n */\n private renderTrend(trend: MetricTrend | undefined, variant: TextVariant, emphasis = false) {\n if (!trend) return null;\n return (\n <ds-text\n as=\"span\"\n class={{\n 'card-overview__trend': true,\n [`card-overview__trend--${trend.tone}`]: true,\n }}\n variant={variant}\n emphasis={emphasis}\n color={TREND_COLORS[trend.tone]}\n fontFeature=\"tabular-nums\"\n >\n {trend.direction === 'up' ? '↑' : '↓'} {trend.value}\n </ds-text>\n );\n }\n\n private renderScore(compact: boolean) {\n if (this.isLoading) {\n if (compact) {\n return (\n <div class=\"card-overview__score ds-control--md\" part=\"score\">\n {this.bar('text-body-medium', '40px', 'card-overview__score-label')}\n <div class=\"card-overview__score-figure\">\n {this.bar('text-body-medium', '24px')}\n {this.bar('text-body-medium', '32px')}\n </div>\n </div>\n );\n }\n\n return (\n <div class=\"card-overview__score ds-control--md\" part=\"score\">\n {/*\n * The label row is kept but left empty. It carries the control-height\n * box that aligns the score block with the period control, so dropping\n * it would shift the whole block up; a bar inside it would collide with\n * the figure, which sits under the label by design.\n */}\n <div class=\"card-overview__score-label-row\" />\n <div class=\"card-overview__score-figure\">\n {this.bar('text-display-medium', '56px')}\n {this.bar('text-body-small', '24px', 'card-overview__skeleton-baseline')}\n </div>\n {/*\n * Class goes on the skeleton itself, matching the resolved band. In a\n * wrapper the bar picks up inline leading and drops below the resolved\n * band position.\n */}\n {this.bar(\n 'text-caption',\n '80px',\n 'card-overview__score-band card-overview__score-band--loading'\n )}\n </div>\n );\n }\n\n if (this.scoreErrorMessage) {\n return (\n <div class=\"card-overview__score card-overview__score--error\" part=\"score\" role=\"alert\">\n <ds-icon name=\"CircleExclamation\" size=\"sm\" color=\"inherit\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"inherit\">\n {this.scoreErrorMessage}\n </ds-text>\n </div>\n );\n }\n\n const score = this.score;\n if (!score) return null;\n\n return (\n <div class=\"card-overview__score card-overview__score--resolved ds-control--md\" part=\"score\">\n {/* The row owns cross-layout baseline geometry and becomes display: contents in compact mode. */}\n {/* eslint-disable-next-line local/prefer-direct-ds-text */}\n <div class=\"card-overview__score-label-row\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-label\"\n variant=\"text-body-medium\"\n emphasis={!compact}\n color={ALWAYS_DARK_SECONDARY}\n >\n {score.label}\n </ds-text>\n </div>\n <div class=\"card-overview__score-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-value\"\n variant={compact ? 'text-body-medium' : 'text-display-medium'}\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {score.value}\n </ds-text>\n {this.renderTrend(\n score.trend,\n compact ? 'text-body-medium' : 'text-body-small',\n !compact\n )}\n </div>\n {score.band && !compact && (\n <ds-text\n as=\"span\"\n class=\"card-overview__score-band\"\n variant=\"text-caption\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {score.band}\n </ds-text>\n )}\n </div>\n );\n }\n\n private renderMetric(metric: OverviewMetric, index: number) {\n const selectable = !metric.isInactive;\n const label = (\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-label\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {metric.label}\n </ds-text>\n );\n\n return (\n <div\n class={{\n 'card-overview__metric': true,\n 'card-overview__metric--inactive': !selectable,\n // Selection targets get the shared wash; press scaling is not used here\n // so the grid keeps its columns aligned. See docs/control-press-policy.md.\n 'ds-interaction-fill': selectable,\n // The card is permanently dark, so interaction tokens come from that family.\n 'ds-interaction-fill--on-always-dark': selectable,\n 'ds-focus-ring-inset': selectable,\n }}\n part=\"metric\"\n role={selectable ? 'button' : undefined}\n tabIndex={selectable && index === this.rovingIndex ? 0 : -1}\n onClick={() => this.selectMetric(metric, index)}\n onKeyDown={event => this.handleMetricKeyDown(event, index)}\n onFocusin={() => {\n if (selectable) this.focusedMetricIndex = index;\n }}\n >\n {metric.labelTooltip ? (\n <ds-tooltip label={metric.labelTooltip} side=\"top\" size=\"sm\">\n {label}\n </ds-tooltip>\n ) : (\n label\n )}\n <div class=\"card-overview__metric-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-value\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {metric.value}\n </ds-text>\n {this.renderTrend(metric.trend, 'text-body-medium')}\n </div>\n </div>\n );\n }\n\n private renderMetrics() {\n if (this.isLoading && this.metrics.length === 0) {\n return Array.from({ length: LOADING_PLACEHOLDER_COUNT }, (_, index) => (\n <div class=\"card-overview__metric\" key={`loading-${index}`}>\n {this.bar('text-body-small', '70%', 'card-overview__metric-label')}\n <div class=\"card-overview__metric-figure\">\n {this.bar('text-body-medium', '36px')}\n {/* text-body-medium, matching renderTrend for a metric — a smaller\n * variant here leaves the trend bar short of the value beside it. */}\n {this.bar('text-body-medium', '40px')}\n </div>\n </div>\n ));\n }\n\n return this.visibleMetrics.map((metric, index) => this.renderMetric(metric, index));\n }\n\n render() {\n const hasHeader = Boolean(this.periodLabel || this.comparisonLabel);\n const compact = this.variant === 'compact';\n const collapse = this.scrollCollapseGeometry;\n\n return (\n <Host\n class={{\n 'card-overview': true,\n 'card-overview--has-score': this.hasScore,\n 'card-overview--compact': compact,\n 'card-overview--stacked': !compact && this.layout === 'stacked',\n 'card-overview--scroll-collapsing': collapse.active,\n }}\n role=\"region\"\n aria-label={this.overviewLabel}\n aria-busy={this.isLoading ? 'true' : undefined}\n style={{\n '--ds-card-overview-metric-min': this.metricMinWidth,\n '--ds-card-overview-expanded-height': collapse.active\n ? `${collapse.expandedHeight}px`\n : undefined,\n '--ds-card-overview-visible-height': collapse.active\n ? `${collapse.visibleHeight}px`\n : undefined,\n '--ds-card-overview-content-offset': collapse.active\n ? `${collapse.offset}px`\n : undefined,\n }}\n >\n <div\n class={{\n 'card-overview__surface': true,\n // Split shadow and inset highlight, so the opaque surface cannot cover\n // the highlight the way a combined elevation shadow would.\n 'ds-control-elevation': true,\n 'ds-control-elevation--sm': !compact,\n 'ds-control-elevation--floating': compact,\n }}\n >\n <div class=\"card-overview__clip\">\n <div\n ref={element => {\n this.layoutEl = (element as HTMLElement) ?? undefined;\n }}\n class={{\n 'card-overview__layout': true,\n 'card-overview__layout--has-score': this.hasScore,\n }}\n >\n <div class=\"card-overview__summary\">\n {this.hasScore && this.renderScore(compact)}\n\n {/* `ds-control--md` supplies the 32px height and the label inset below. */}\n <div\n class={{\n 'card-overview__header': true,\n 'card-overview__header--has-score': this.hasScore,\n 'ds-control--md': true,\n }}\n >\n {this.isLoading ? (\n <div class=\"card-overview__period ds-control-label-box\">\n {this.bar('text-body-medium', '184px')}\n {this.bar('text-body-medium', '248px')}\n </div>\n ) : (\n hasHeader && (\n <div class=\"card-overview__period ds-control-label-box\">\n {this.periodLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-current ds-control-label-box\"\n variant=\"text-body-medium\"\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n >\n {this.periodLabel}\n </ds-text>\n )}\n {this.comparisonLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-comparison ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {this.comparisonLabel}\n </ds-text>\n )}\n </div>\n )\n )}\n <div class=\"card-overview__filter\">\n {/*\n Period control is application owned: it varies by product surface.\n While loading it is stood in for by a control-shaped skeleton so the\n header keeps its resolved proportions.\n */}\n {this.isLoading ? (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"md\"\n width=\"128px\"\n background=\"always-dark\"\n />\n ) : (\n <slot name=\"filter\" />\n )}\n </div>\n </div>\n </div>\n\n {!compact && (\n <div\n class={{\n 'card-overview__body': true,\n 'card-overview__body--has-score': this.hasScore,\n }}\n >\n <div class=\"card-overview__metrics\" part=\"metrics\">\n {this.renderMetrics()}\n </div>\n\n <slot name=\"footer\" />\n </div>\n )}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
|
|
375
|
+
"type": "registry:ui"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"path": "src/wc/components/CardOverview/card-overview-controller.ts",
|
|
379
|
+
"content": "import type { CardOverviewVariant, OverviewMetric } from './card-overview-types';\n\nexport interface CardOverviewCollapseGeometry {\n active: boolean;\n expandedHeight: number;\n offset: number;\n visibleHeight: number;\n}\n\nexport function resolveCardOverviewCollapseGeometry(options: {\n variant: CardOverviewVariant;\n progress: number;\n expandedHeight: number;\n compactHeight: number;\n}): CardOverviewCollapseGeometry {\n const compactHeight = Math.max(0, options.compactHeight);\n const expandedHeight = Math.max(options.expandedHeight, compactHeight);\n const progress =\n options.variant === 'compact' || !Number.isFinite(options.progress)\n ? 0\n : Math.min(1, Math.max(0, options.progress));\n const distance = Math.max(0, expandedHeight - compactHeight);\n const offset = distance * progress;\n\n return {\n active: progress > 0 && distance > 0,\n expandedHeight,\n offset,\n visibleHeight: expandedHeight - offset,\n };\n}\n\nexport function resolveOverviewRovingIndex(\n metrics: readonly OverviewMetric[],\n focusedIndex: number\n): number {\n const first = metrics.findIndex(metric => !metric.isInactive);\n if (first < 0) return -1;\n return metrics[focusedIndex] && !metrics[focusedIndex].isInactive\n ? focusedIndex\n : first;\n}\n\nexport function findNextOverviewMetricIndex(\n metrics: readonly OverviewMetric[],\n from: number,\n step: -1 | 1\n): number {\n for (let offset = 1; offset <= metrics.length; offset += 1) {\n const next = (((from + step * offset) % metrics.length) + metrics.length) % metrics.length;\n if (!metrics[next]?.isInactive) return next;\n }\n return -1;\n}\n",
|
|
352
380
|
"type": "registry:ui"
|
|
353
381
|
},
|
|
354
382
|
{
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"alternatives": [
|
|
30
30
|
{
|
|
31
31
|
"when": "The surface contains a chart or specialized data-visualization composition.",
|
|
32
|
-
"component": "component:ds-card-
|
|
33
|
-
"reason": "
|
|
32
|
+
"component": "component:ds-card-data-viz",
|
|
33
|
+
"reason": "CardDataViz owns the independently evolving data-visualization card contract."
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"when": "The task must block the interface for one focused decision or short workflow.",
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"required": [
|
|
252
|
-
"@ds-mo/tokens ^5.
|
|
252
|
+
"@ds-mo/tokens ^5.1.0",
|
|
253
253
|
"@ds-mo/icons ^6.4.0"
|
|
254
254
|
],
|
|
255
255
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "chart-bar",
|
|
4
4
|
"title": "ChartBar",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Bar chart for single-series magnitude, additive stacked totals, or normalized 0–100% composition across categories.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,24 +13,20 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "specialized",
|
|
15
15
|
"status": "experimental",
|
|
16
|
-
"summary": "
|
|
16
|
+
"summary": "Bar chart for single-series magnitude, additive stacked totals, or normalized 0–100% composition across categories.",
|
|
17
17
|
"useWhen": [
|
|
18
18
|
"Users need to compare one numeric measure across a small set of categories.",
|
|
19
19
|
"Values are grouped into ordered time buckets such as hours, days, weeks, or months and equal visual spacing is appropriate.",
|
|
20
|
-
"A data-viz card needs a compact magnitude comparison rather than a continuous trend."
|
|
20
|
+
"A data-viz card needs a compact magnitude comparison rather than a continuous trend.",
|
|
21
|
+
"Several consistent series contribute to each category total or its normalized composition."
|
|
21
22
|
],
|
|
22
23
|
"avoidWhen": [
|
|
23
24
|
"Timestamp positions must reflect irregular elapsed time; pre-bucket the data or use a future continuous time-scale chart.",
|
|
24
|
-
"The visualization needs
|
|
25
|
+
"The visualization needs grouped bars or precise comparison of each individual series across categories.",
|
|
25
26
|
"The interaction requires selection, drill-down, brushing, or zoom.",
|
|
26
27
|
"The data communicates a part-to-whole relationship."
|
|
27
28
|
],
|
|
28
29
|
"alternatives": [
|
|
29
|
-
{
|
|
30
|
-
"when": "Each category contains several additive series or needs normalized composition.",
|
|
31
|
-
"component": "component:ds-chart-bar-stacked",
|
|
32
|
-
"reason": "ChartBarStacked compares raw stacked totals or normalized 0–100% composition."
|
|
33
|
-
},
|
|
34
30
|
{
|
|
35
31
|
"when": "Users need to follow one or more trends across ordered categories.",
|
|
36
32
|
"component": "component:ds-chart-line",
|
|
@@ -43,8 +39,8 @@
|
|
|
43
39
|
}
|
|
44
40
|
],
|
|
45
41
|
"commonlyComposedWith": [
|
|
46
|
-
"component:ds-chart-
|
|
47
|
-
"component:ds-card-
|
|
42
|
+
"component:ds-chart-legend",
|
|
43
|
+
"component:ds-card-data-viz",
|
|
48
44
|
"component:ds-tooltip-data-viz"
|
|
49
45
|
],
|
|
50
46
|
"accessibility": [
|
|
@@ -53,7 +49,9 @@
|
|
|
53
49
|
"A future interactive tooltip must supplement rather than replace the chart's accessible value representation."
|
|
54
50
|
],
|
|
55
51
|
"states": [
|
|
56
|
-
"The chart is
|
|
52
|
+
"The chart is static without hover, selection, loading, or empty-state policy.",
|
|
53
|
+
"Single mode reads data. Stacked and percentage modes read aligned series and categories.",
|
|
54
|
+
"Stacked mode preserves raw totals; percentage mode normalizes every non-empty category to 100%.",
|
|
57
55
|
"An empty dataset renders no bars; the owning card or product supplies meaningful empty-state messaging.",
|
|
58
56
|
"Bars use 2px top corner radii while the lower corners touching the zero axis remain square.",
|
|
59
57
|
"Use one consistent bar color across time buckets when every bar is the same measure at a different interval; categorical color changes are for genuinely distinct categories.",
|
|
@@ -67,13 +65,13 @@
|
|
|
67
65
|
],
|
|
68
66
|
"frameworkCaveats": {
|
|
69
67
|
"customElements": [
|
|
70
|
-
"Assign data as
|
|
68
|
+
"Assign data, series, and categories as JavaScript properties; arrays are not supported as HTML attributes."
|
|
71
69
|
],
|
|
72
70
|
"react": [
|
|
73
|
-
"Pass
|
|
71
|
+
"Pass stable data or series/category arrays; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
74
72
|
],
|
|
75
73
|
"angular": [
|
|
76
|
-
"Bind data as
|
|
74
|
+
"Bind data or series/categories as properties; width and height provide standalone intrinsic dimensions while constrained containers reflow automatically."
|
|
77
75
|
]
|
|
78
76
|
},
|
|
79
77
|
"references": [
|
|
@@ -85,6 +83,14 @@
|
|
|
85
83
|
},
|
|
86
84
|
"api": {
|
|
87
85
|
"props": {
|
|
86
|
+
"categories": {
|
|
87
|
+
"type": "string[]",
|
|
88
|
+
"resolvedType": "string[]",
|
|
89
|
+
"default": "[]",
|
|
90
|
+
"required": false,
|
|
91
|
+
"mutable": false,
|
|
92
|
+
"description": "X-axis labels for stacked and percentage variants. Set as a JS property."
|
|
93
|
+
},
|
|
88
94
|
"data": {
|
|
89
95
|
"type": "ChartDatum[]",
|
|
90
96
|
"resolvedType": "ChartDatum[]",
|
|
@@ -102,6 +108,23 @@
|
|
|
102
108
|
"mutable": false,
|
|
103
109
|
"description": "Standalone intrinsic height. Container constraints reflow the plot at rendered pixel size."
|
|
104
110
|
},
|
|
111
|
+
"series": {
|
|
112
|
+
"type": "ChartSeries[]",
|
|
113
|
+
"resolvedType": "ChartSeries[]",
|
|
114
|
+
"default": "[]",
|
|
115
|
+
"required": false,
|
|
116
|
+
"mutable": false,
|
|
117
|
+
"description": "Ordered stack series for stacked and percentage variants. Set as a JS property."
|
|
118
|
+
},
|
|
119
|
+
"variant": {
|
|
120
|
+
"type": "ChartBarVariant",
|
|
121
|
+
"resolvedType": "\"percentage\" | \"single\" | \"stacked\"",
|
|
122
|
+
"attribute": "variant",
|
|
123
|
+
"default": "'single'",
|
|
124
|
+
"required": false,
|
|
125
|
+
"mutable": false,
|
|
126
|
+
"description": "Rendering model. Single uses `data`; stacked and percentage use `series` and `categories`."
|
|
127
|
+
},
|
|
105
128
|
"width": {
|
|
106
129
|
"type": "number",
|
|
107
130
|
"resolvedType": "number",
|
|
@@ -117,6 +140,14 @@
|
|
|
117
140
|
"slots": []
|
|
118
141
|
},
|
|
119
142
|
"props": {
|
|
143
|
+
"categories": {
|
|
144
|
+
"type": "string[]",
|
|
145
|
+
"resolvedType": "string[]",
|
|
146
|
+
"default": "[]",
|
|
147
|
+
"required": false,
|
|
148
|
+
"mutable": false,
|
|
149
|
+
"description": "X-axis labels for stacked and percentage variants. Set as a JS property."
|
|
150
|
+
},
|
|
120
151
|
"data": {
|
|
121
152
|
"type": "ChartDatum[]",
|
|
122
153
|
"resolvedType": "ChartDatum[]",
|
|
@@ -134,6 +165,23 @@
|
|
|
134
165
|
"mutable": false,
|
|
135
166
|
"description": "Standalone intrinsic height. Container constraints reflow the plot at rendered pixel size."
|
|
136
167
|
},
|
|
168
|
+
"series": {
|
|
169
|
+
"type": "ChartSeries[]",
|
|
170
|
+
"resolvedType": "ChartSeries[]",
|
|
171
|
+
"default": "[]",
|
|
172
|
+
"required": false,
|
|
173
|
+
"mutable": false,
|
|
174
|
+
"description": "Ordered stack series for stacked and percentage variants. Set as a JS property."
|
|
175
|
+
},
|
|
176
|
+
"variant": {
|
|
177
|
+
"type": "ChartBarVariant",
|
|
178
|
+
"resolvedType": "\"percentage\" | \"single\" | \"stacked\"",
|
|
179
|
+
"attribute": "variant",
|
|
180
|
+
"default": "'single'",
|
|
181
|
+
"required": false,
|
|
182
|
+
"mutable": false,
|
|
183
|
+
"description": "Rendering model. Single uses `data`; stacked and percentage use `series` and `categories`."
|
|
184
|
+
},
|
|
137
185
|
"width": {
|
|
138
186
|
"type": "number",
|
|
139
187
|
"resolvedType": "number",
|
|
@@ -167,10 +215,10 @@
|
|
|
167
215
|
"import": "import { DsChartBar } from '@ds-mo/ui/angular/ds-chart-bar';",
|
|
168
216
|
"example": "<ds-chart-bar></ds-chart-bar>"
|
|
169
217
|
},
|
|
170
|
-
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: data.",
|
|
218
|
+
"complexPropertyNote": "Assign these non-primitive values as JavaScript properties: categories, data, series.",
|
|
171
219
|
"peerDependencies": {
|
|
172
220
|
"required": [
|
|
173
|
-
"@ds-mo/tokens ^5.
|
|
221
|
+
"@ds-mo/tokens ^5.1.0",
|
|
174
222
|
"@ds-mo/icons ^6.4.0"
|
|
175
223
|
],
|
|
176
224
|
"frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
|
|
@@ -185,12 +233,12 @@
|
|
|
185
233
|
"files": [
|
|
186
234
|
{
|
|
187
235
|
"path": "src/wc/components/ChartBar/ChartBar.css",
|
|
188
|
-
"content": ":host {\n display: inline-block;\n max-width: 100%;\n font-family: var(--typography-font-family);\n}\n\n.chart-bar__svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.chart-bar__gridline {\n stroke: var(--color-border-tertiary);\n stroke-width: var(--dimension-stroke-width-012);\n vector-effect: non-scaling-stroke;\n}\n\n/* Matches ds-text text-caption regular (SVG cannot host ds-text). */\n.chart-bar__axis-label {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-medium);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart-bar__bar--rounded {\n rx: var(--dimension-radius-025);\n}\n",
|
|
236
|
+
"content": ":host {\n display: inline-block;\n max-width: 100%;\n font-family: var(--typography-font-family);\n}\n\n.chart-bar__svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.chart-bar__gridline {\n stroke: var(--color-border-tertiary);\n stroke-width: var(--dimension-stroke-width-012);\n vector-effect: non-scaling-stroke;\n}\n\n/* Matches ds-text text-caption regular (SVG cannot host ds-text). */\n.chart-bar__axis-label {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-medium);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart-bar__bar--rounded {\n rx: var(--dimension-radius-025);\n}\n\n.chart-bar__segment-shape--rounded {\n rx: var(--dimension-radius-025);\n}\n\n.chart-bar__segment-separator {\n stroke: var(--color-background-primary);\n stroke-width: var(--dimension-stroke-width-012);\n stroke-linecap: butt;\n vector-effect: non-scaling-stroke;\n}\n",
|
|
189
237
|
"type": "registry:ui"
|
|
190
238
|
},
|
|
191
239
|
{
|
|
192
240
|
"path": "src/wc/components/ChartBar/ChartBar.tsx",
|
|
193
|
-
"content": "import { Component, Prop, State, Watch, h, Host } from '@stencil/core';\nimport { scaleBand, scaleLinear } from 'd3-scale';\nimport { max } from 'd3-array';\nimport { categoryColor } from '../../utils/chart-colors';\nimport { resolveCartesianChartLayout } from '../../utils/cartesian-chart-layout';\nimport {\n ChartViewportController,\n type ChartViewportSize,\n} from '../../utils/chart-viewport-controller';\nimport type { ChartDatum } from '../../utils/chart-types';\n\n@Component({\n tag: 'ds-chart-bar',\n styleUrl: 'ChartBar.css',\n scoped: true,\n})\nexport class ChartBar {\n /** Bars to render. Set as a JS property (not an HTML attribute). */\n @Prop() data: ChartDatum[] = [];\n /** Standalone intrinsic width. Container constraints reflow the plot at rendered pixel size. */\n @Prop() width: number = 480;\n /** Standalone intrinsic height. Container constraints reflow the plot at rendered pixel size. */\n @Prop() height: number = 240;\n\n @State() private viewport: ChartViewportSize | undefined;\n\n private viewportController: ChartViewportController | null = null;\n private svgEl: SVGElement | null = null;\n\n componentDidLoad() {\n this.connectViewportController();\n }\n\n connectedCallback() {\n if (this.svgEl && !this.viewportController) this.connectViewportController();\n }\n\n componentDidRender() {\n if (!this.viewportController) this.connectViewportController();\n }\n\n private connectViewportController() {\n if (!this.svgEl) return;\n this.viewportController = new ChartViewportController(this.svgEl, viewport => {\n if (\n viewport.width !== this.viewport?.width ||\n viewport.height !== this.viewport?.height\n ) {\n this.viewport = viewport;\n }\n });\n this.viewportController.connect();\n }\n\n disconnectedCallback() {\n this.viewportController?.disconnect();\n this.viewportController = null;\n }\n\n @Watch('width')\n @Watch('height')\n handleIntrinsicSizeChange() {\n this.viewport = undefined;\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this.viewportController?.measure());\n }\n }\n\n render() {\n const viewportWidth = this.viewport?.width ?? this.width;\n const viewportHeight = this.viewport?.height ?? this.height;\n const { margin, axisLabelGap, categoryLabelOffset } =\n resolveCartesianChartLayout();\n const innerWidth = Math.max(0, viewportWidth - margin.left - margin.right);\n const innerHeight = Math.max(0, viewportHeight - margin.top - margin.bottom);\n\n const yMax = max(this.data, d => d.value) ?? 0;\n\n const xScale = scaleBand()\n .domain(this.data.map(d => d.label))\n .range([0, innerWidth])\n .padding(0.35);\n\n const yScale = scaleLinear()\n .domain([0, yMax || 1])\n .range([innerHeight, 0])\n .nice();\n\n const yTicks = yScale.ticks(4);\n\n return (\n <Host class=\"chart-bar\">\n <svg\n class=\"chart-bar__svg\"\n ref={element => {\n this.svgEl = element ?? null;\n }}\n width={viewportWidth}\n height={viewportHeight}\n role=\"img\"\n aria-label={this.data.map(d => `${d.label}: ${d.value}`).join(', ')}\n >\n <g transform={`translate(${margin.left}, ${margin.top})`}>\n {yTicks.map(tick => (\n <g key={`grid-${tick}`} transform={`translate(0, ${yScale(tick)})`}>\n <line class=\"chart-bar__gridline\" x1={0} x2={innerWidth} />\n <text\n class=\"chart-bar__axis-label\"\n x={-axisLabelGap}\n text-anchor=\"end\"\n dominant-baseline=\"middle\"\n >\n {tick}\n </text>\n </g>\n ))}\n\n {this.data.map((d, i) => {\n const barWidth = xScale.bandwidth();\n const barX = xScale(d.label) ?? 0;\n const barY = yScale(d.value);\n const barHeight = innerHeight - barY;\n const squareBaseY = barY + barHeight / 2;\n const squareBaseHeight = barHeight / 2;\n const fill = d.color ?? categoryColor(i);\n return (\n <g key={d.label}>\n <rect\n class=\"chart-bar__bar chart-bar__bar--rounded\"\n x={barX}\n y={barY}\n width={barWidth}\n height={barHeight}\n fill={fill}\n />\n <rect\n class=\"chart-bar__bar chart-bar__bar--square-base\"\n x={barX}\n y={squareBaseY}\n width={barWidth}\n height={squareBaseHeight}\n fill={fill}\n />\n <text\n class=\"chart-bar__axis-label\"\n x={barX + barWidth / 2}\n y={innerHeight + categoryLabelOffset}\n text-anchor=\"middle\"\n >\n {d.label}\n </text>\n </g>\n );\n })}\n </g>\n </svg>\n </Host>\n );\n }\n}\n",
|
|
241
|
+
"content": "import { Component, Prop, State, Watch, h, Host } from '@stencil/core';\nimport { scaleBand, scaleLinear } from 'd3-scale';\nimport { max } from 'd3-array';\nimport { categoryColor } from '../../utils/chart-colors';\nimport { formatPercentage } from '../../utils';\nimport { resolveCartesianChartLayout } from '../../utils/cartesian-chart-layout';\nimport {\n ChartViewportController,\n type ChartViewportSize,\n} from '../../utils/chart-viewport-controller';\nimport type { ChartDatum, ChartSeries } from '../../utils/chart-types';\n\nexport type ChartBarVariant = 'single' | 'stacked' | 'percentage';\n\nconst PERCENTAGE_TICKS = [0, 25, 50, 75, 100];\n\n@Component({\n tag: 'ds-chart-bar',\n styleUrl: 'ChartBar.css',\n scoped: true,\n})\nexport class ChartBar {\n /** Rendering model. Single uses `data`; stacked and percentage use `series` and `categories`. */\n @Prop() variant: ChartBarVariant = 'single';\n /** Bars to render. Set as a JS property (not an HTML attribute). */\n @Prop() data: ChartDatum[] = [];\n /** Ordered stack series for stacked and percentage variants. Set as a JS property. */\n @Prop() series: ChartSeries[] = [];\n /** X-axis labels for stacked and percentage variants. Set as a JS property. */\n @Prop() categories: string[] = [];\n /** Standalone intrinsic width. Container constraints reflow the plot at rendered pixel size. */\n @Prop() width: number = 480;\n /** Standalone intrinsic height. Container constraints reflow the plot at rendered pixel size. */\n @Prop() height: number = 240;\n\n @State() private viewport: ChartViewportSize | undefined;\n\n private viewportController: ChartViewportController | null = null;\n private svgEl: SVGElement | null = null;\n\n componentDidLoad() {\n this.connectViewportController();\n }\n\n connectedCallback() {\n if (this.svgEl && !this.viewportController) this.connectViewportController();\n }\n\n componentDidRender() {\n if (!this.viewportController) this.connectViewportController();\n }\n\n private connectViewportController() {\n if (!this.svgEl) return;\n this.viewportController = new ChartViewportController(this.svgEl, viewport => {\n if (\n viewport.width !== this.viewport?.width ||\n viewport.height !== this.viewport?.height\n ) {\n this.viewport = viewport;\n }\n });\n this.viewportController.connect();\n }\n\n disconnectedCallback() {\n this.viewportController?.disconnect();\n this.viewportController = null;\n }\n\n @Watch('width')\n @Watch('height')\n handleIntrinsicSizeChange() {\n this.viewport = undefined;\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => this.viewportController?.measure());\n }\n }\n\n private renderSingle(\n innerWidth: number,\n innerHeight: number,\n axisLabelGap: number,\n categoryLabelOffset: number\n ) {\n const yMax = max(this.data, d => d.value) ?? 0;\n const xScale = scaleBand()\n .domain(this.data.map(d => d.label))\n .range([0, innerWidth])\n .padding(0.35);\n const yScale = scaleLinear()\n .domain([0, yMax || 1])\n .range([innerHeight, 0])\n .nice();\n const yTicks = yScale.ticks(4);\n\n return (\n <g>\n {yTicks.map(tick => (\n <g key={`grid-${tick}`} transform={`translate(0, ${yScale(tick)})`}>\n <line class=\"chart-bar__gridline\" x1={0} x2={innerWidth} />\n <text\n class=\"chart-bar__axis-label\"\n x={-axisLabelGap}\n text-anchor=\"end\"\n dominant-baseline=\"middle\"\n >\n {tick}\n </text>\n </g>\n ))}\n\n {this.data.map((datum, index) => {\n const barWidth = xScale.bandwidth();\n const barX = xScale(datum.label) ?? 0;\n const barY = yScale(datum.value);\n const barHeight = innerHeight - barY;\n const squareBaseY = barY + barHeight / 2;\n const squareBaseHeight = barHeight / 2;\n const fill = datum.color ?? categoryColor(index);\n return (\n <g key={datum.label}>\n <rect\n class=\"chart-bar__bar chart-bar__bar--rounded\"\n x={barX}\n y={barY}\n width={barWidth}\n height={barHeight}\n fill={fill}\n />\n <rect\n class=\"chart-bar__bar chart-bar__bar--square-base\"\n x={barX}\n y={squareBaseY}\n width={barWidth}\n height={squareBaseHeight}\n fill={fill}\n />\n <text\n class=\"chart-bar__axis-label\"\n x={barX + barWidth / 2}\n y={innerHeight + categoryLabelOffset}\n text-anchor=\"middle\"\n >\n {datum.label}\n </text>\n </g>\n );\n })}\n </g>\n );\n }\n\n private valuesForCategory(categoryIndex: number) {\n return this.series.map(series => Math.max(0, series.data[categoryIndex] ?? 0));\n }\n\n private stackedAccessibleLabel() {\n return this.categories\n .map((category, categoryIndex) => {\n const values = this.valuesForCategory(categoryIndex);\n const total = values.reduce((sum, value) => sum + value, 0);\n const details = this.series\n .map((series, seriesIndex) => {\n const value = values[seriesIndex];\n const visibleValue =\n this.variant === 'percentage'\n ? formatPercentage(total ? value / total : 0, 1)\n : String(value);\n return `${series.name}: ${visibleValue}`;\n })\n .join(', ');\n return `${category}: ${details}`;\n })\n .join('; ');\n }\n\n private renderStacked(\n innerWidth: number,\n innerHeight: number,\n axisLabelGap: number,\n categoryLabelOffset: number\n ) {\n const categoryValues = this.categories.map((_, index) =>\n this.valuesForCategory(index)\n );\n const totals = categoryValues.map(values =>\n values.reduce((sum, value) => sum + value, 0)\n );\n const yMax = this.variant === 'percentage' ? 100 : Math.max(0, ...totals);\n const xScale = scaleBand()\n .domain(this.categories)\n .range([0, innerWidth])\n .padding(0.35);\n const yScale = scaleLinear()\n .domain([0, yMax || 1])\n .range([innerHeight, 0])\n .nice();\n const yTicks =\n this.variant === 'percentage' ? PERCENTAGE_TICKS : yScale.ticks(4);\n\n return (\n <g>\n {yTicks.map(tick => (\n <g key={`grid-${tick}`} transform={`translate(0, ${yScale(tick)})`}>\n <line class=\"chart-bar__gridline\" x1={0} x2={innerWidth} />\n <text\n class=\"chart-bar__axis-label\"\n x={-axisLabelGap}\n text-anchor=\"end\"\n dominant-baseline=\"middle\"\n >\n {this.variant === 'percentage' ? `${tick}%` : tick}\n </text>\n </g>\n ))}\n\n {this.categories.map((category, categoryIndex) => {\n const barWidth = xScale.bandwidth();\n const barX = xScale(category) ?? 0;\n const values = categoryValues[categoryIndex];\n const total = totals[categoryIndex];\n const topSeriesIndex = values.reduce(\n (topIndex, value, seriesIndex) =>\n value > 0 ? seriesIndex : topIndex,\n -1\n );\n let cumulativeValue = 0;\n\n return (\n <g class=\"chart-bar__stack\" data-category={category} key={category}>\n {this.series.map((series, seriesIndex) => {\n const rawValue = values[seriesIndex];\n const value =\n this.variant === 'percentage'\n ? total\n ? (rawValue / total) * 100\n : 0\n : rawValue;\n const segmentStart = cumulativeValue;\n const segmentEnd = cumulativeValue + value;\n cumulativeValue = segmentEnd;\n const segmentY = yScale(segmentEnd);\n const segmentBottom = yScale(segmentStart);\n const segmentHeight = Math.max(0, segmentBottom - segmentY);\n if (!segmentHeight) return null;\n\n const fill = series.color ?? categoryColor(seriesIndex);\n const hasRoundedTop =\n this.variant === 'stacked' && seriesIndex === topSeriesIndex;\n const squareBaseY = segmentY + segmentHeight / 2;\n const squareBaseHeight = segmentHeight / 2;\n\n return (\n <g\n class=\"chart-bar__segment\"\n data-series={series.name}\n key={series.name}\n >\n <rect\n class={{\n 'chart-bar__segment-shape': true,\n 'chart-bar__segment-shape--rounded': hasRoundedTop,\n }}\n x={barX}\n y={segmentY}\n width={barWidth}\n height={segmentHeight}\n fill={fill}\n />\n {hasRoundedTop ? (\n <rect\n class=\"chart-bar__segment-square-base\"\n x={barX}\n y={squareBaseY}\n width={barWidth}\n height={squareBaseHeight}\n fill={fill}\n />\n ) : null}\n {segmentStart > 0 ? (\n <line\n class=\"chart-bar__segment-separator\"\n x1={barX}\n x2={barX + barWidth}\n y1={segmentBottom}\n y2={segmentBottom}\n vector-effect=\"non-scaling-stroke\"\n />\n ) : null}\n </g>\n );\n })}\n <text\n class=\"chart-bar__axis-label\"\n x={barX + barWidth / 2}\n y={innerHeight + categoryLabelOffset}\n text-anchor=\"middle\"\n >\n {category}\n </text>\n </g>\n );\n })}\n </g>\n );\n }\n\n render() {\n const viewportWidth = this.viewport?.width ?? this.width;\n const viewportHeight = this.viewport?.height ?? this.height;\n const { margin, axisLabelGap, categoryLabelOffset } =\n resolveCartesianChartLayout();\n const innerWidth = Math.max(0, viewportWidth - margin.left - margin.right);\n const innerHeight = Math.max(0, viewportHeight - margin.top - margin.bottom);\n const isSingle = this.variant === 'single';\n const accessibleLabel = isSingle\n ? this.data.map(datum => `${datum.label}: ${datum.value}`).join(', ')\n : this.stackedAccessibleLabel();\n\n return (\n <Host\n class={{\n 'chart-bar': true,\n [`chart-bar--${this.variant}`]: true,\n }}\n >\n <svg\n class=\"chart-bar__svg\"\n ref={element => {\n this.svgEl = element ?? null;\n }}\n width={viewportWidth}\n height={viewportHeight}\n role=\"img\"\n aria-label={accessibleLabel}\n >\n <g transform={`translate(${margin.left}, ${margin.top})`}>\n {isSingle\n ? this.renderSingle(\n innerWidth,\n innerHeight,\n axisLabelGap,\n categoryLabelOffset\n )\n : this.renderStacked(\n innerWidth,\n innerHeight,\n axisLabelGap,\n categoryLabelOffset\n )}\n </g>\n </svg>\n </Host>\n );\n }\n}\n",
|
|
194
242
|
"type": "registry:ui"
|
|
195
243
|
}
|
|
196
244
|
]
|