basalt-ui 1.24.0 → 1.26.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/AGENTS.md +8 -6
- package/CHANGELOG.md +25 -0
- package/MIGRATING.md +547 -21
- package/README.md +46 -33
- package/agent/rules/basalt-batteries.md +205 -0
- package/agent/rules/basalt-charts.md +125 -555
- package/agent/rules/basalt-controls.md +155 -0
- package/agent/rules/basalt-mantine.md +146 -439
- package/agent/rules/basalt-state.md +113 -96
- package/agent/rules/basalt-tokens.md +126 -288
- package/agent/skills/basalt-app/SKILL.md +70 -195
- package/agent/skills/basalt-charts/SKILL.md +54 -234
- package/agent/skills/basalt-design/SKILL.md +56 -248
- package/agent/templates/CLAUDE-block.md.tpl +31 -40
- package/agent/templates/DESIGN.md.tpl +22 -51
- package/configs/oxlint-plugin.js +1275 -31
- package/configs/oxlint-plugin.test.ts +947 -11
- package/configs/oxlint.json +11 -3
- package/dist/charts/cursor/resolve.d.ts +18 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -1
- package/dist/charts/cursor/resolve.js +15 -6
- package/dist/charts/cursor/scope.d.ts +6 -3
- package/dist/charts/cursor/scope.d.ts.map +1 -1
- package/dist/charts/cursor/store.d.ts +5 -1
- package/dist/charts/cursor/store.d.ts.map +1 -1
- package/dist/charts/cursor/store.js +4 -4
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +9 -8
- package/dist/charts/index.d.ts +2 -2
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.d.ts +34 -9
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +29 -114
- package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
- package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
- package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
- package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/BarSparkline.js +18 -4
- package/dist/cli/check-coverage.test.d.ts +2 -0
- package/dist/cli/check-coverage.test.d.ts.map +1 -0
- package/dist/cli/index.d.ts +67 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +225 -7
- package/dist/connectivity/connectivity-indicator.d.ts +4 -1
- package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
- package/dist/connectivity/connectivity-indicator.js +1 -1
- package/dist/content/index.d.ts +5 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -2
- package/dist/content/prose.module.css +7 -28
- package/dist/controls/actions.d.ts +169 -0
- package/dist/controls/actions.d.ts.map +1 -0
- package/dist/controls/actions.js +287 -0
- package/dist/controls/actions.module.css +62 -0
- package/dist/controls/actions.module.css.d.ts +13 -0
- package/dist/controls/actions.test.d.ts +2 -0
- package/dist/controls/actions.test.d.ts.map +1 -0
- package/dist/controls/actions.type-guard.test.d.ts +2 -0
- package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
- package/dist/controls/compare-filter.d.ts +41 -0
- package/dist/controls/compare-filter.d.ts.map +1 -0
- package/dist/controls/compare-filter.js +23 -0
- package/dist/controls/control-group.d.ts +19 -0
- package/dist/controls/control-group.d.ts.map +1 -0
- package/dist/controls/control-group.js +20 -0
- package/dist/controls/control-group.test.d.ts +2 -0
- package/dist/controls/control-group.test.d.ts.map +1 -0
- package/dist/controls/controls.module.css +542 -0
- package/dist/controls/controls.module.css.d.ts +35 -0
- package/dist/controls/controls.router.test.d.ts +2 -0
- package/dist/controls/controls.router.test.d.ts.map +1 -0
- package/dist/controls/enum-filter.d.ts +30 -0
- package/dist/controls/enum-filter.d.ts.map +1 -0
- package/dist/controls/enum-filter.js +91 -0
- package/dist/controls/filter-context.d.ts +55 -0
- package/dist/controls/filter-context.d.ts.map +1 -0
- package/dist/controls/filter-context.js +69 -0
- package/dist/controls/filter-pill.d.ts +28 -0
- package/dist/controls/filter-pill.d.ts.map +1 -0
- package/dist/controls/filter-pill.js +70 -0
- package/dist/controls/filter-set.d.ts +13 -0
- package/dist/controls/filter-set.d.ts.map +1 -0
- package/dist/controls/filter-set.js +125 -0
- package/dist/controls/filter-set.test.d.ts +2 -0
- package/dist/controls/filter-set.test.d.ts.map +1 -0
- package/dist/controls/filter-sheet.d.ts +117 -0
- package/dist/controls/filter-sheet.d.ts.map +1 -0
- package/dist/controls/filter-sheet.js +146 -0
- package/dist/controls/glyphs.d.ts +27 -0
- package/dist/controls/glyphs.d.ts.map +1 -0
- package/dist/controls/glyphs.js +49 -0
- package/dist/controls/index.d.ts +19 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +44 -0
- package/dist/controls/multi-select-filter.d.ts +23 -0
- package/dist/controls/multi-select-filter.d.ts.map +1 -0
- package/dist/controls/multi-select-filter.js +89 -0
- package/dist/controls/peer-boundary.test.d.ts +2 -0
- package/dist/controls/peer-boundary.test.d.ts.map +1 -0
- package/dist/controls/props-contract.test.d.ts +2 -0
- package/dist/controls/props-contract.test.d.ts.map +1 -0
- package/dist/controls/range-filter.d.ts +29 -0
- package/dist/controls/range-filter.d.ts.map +1 -0
- package/dist/controls/range-filter.js +120 -0
- package/dist/controls/search-filter.d.ts +10 -0
- package/dist/controls/search-filter.d.ts.map +1 -0
- package/dist/controls/search-filter.js +81 -0
- package/dist/controls/select-filter.d.ts +74 -0
- package/dist/controls/select-filter.d.ts.map +1 -0
- package/dist/controls/select-filter.js +10 -0
- package/dist/controls/sync-button.d.ts +38 -0
- package/dist/controls/sync-button.d.ts.map +1 -0
- package/dist/controls/sync-button.js +119 -0
- package/dist/controls/sync-button.module.css +63 -0
- package/dist/controls/sync-button.module.css.d.ts +11 -0
- package/dist/controls/sync-button.test.d.ts +2 -0
- package/dist/controls/sync-button.test.d.ts.map +1 -0
- package/dist/controls/toggle-filter.d.ts +9 -0
- package/dist/controls/toggle-filter.d.ts.map +1 -0
- package/dist/controls/toggle-filter.js +42 -0
- package/dist/controls/view-tabs.d.ts +23 -0
- package/dist/controls/view-tabs.d.ts.map +1 -0
- package/dist/controls/view-tabs.js +59 -0
- package/dist/controls-dates/date-range-picker.d.ts +4 -0
- package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
- package/dist/controls-dates/date-range-picker.js +18 -0
- package/dist/controls-dates/index.d.ts +9 -0
- package/dist/controls-dates/index.d.ts.map +1 -0
- package/dist/controls-dates/index.js +4 -0
- package/dist/dashboard/delta-badge.d.ts +6 -12
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/delta-badge.js +1 -50
- package/dist/dashboard/query-state.d.ts +1 -1
- package/dist/dashboard/settings-section.d.ts +6 -4
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +23 -16
- package/dist/dashboard/settings-section.module.css +10 -0
- package/dist/dashboard/settings-section.module.css.d.ts +1 -0
- package/dist/dashboard/settings-section.test.d.ts +2 -0
- package/dist/dashboard/settings-section.test.d.ts.map +1 -0
- package/dist/dashboard/stat-card.d.ts +44 -9
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +80 -48
- package/dist/dashboard/stat-card.module.css +82 -0
- package/dist/dashboard/stat-card.module.css.d.ts +11 -0
- package/dist/data/data-table.d.ts +102 -20
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +166 -65
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- package/dist/guard/grace.test.d.ts +20 -0
- package/dist/guard/grace.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts +66 -3
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +82 -7
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -10
- package/dist/nav/types.d.ts +78 -2
- package/dist/nav/types.d.ts.map +1 -1
- package/dist/notifications/bell.d.ts +4 -1
- package/dist/notifications/bell.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts +14 -0
- package/dist/router-tanstack/field.d.ts.map +1 -0
- package/dist/router-tanstack/field.js +4 -0
- package/dist/router-tanstack/index.d.ts +6 -2
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +4 -0
- package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.js +19 -27
- package/dist/router-tanstack/search-param-store.d.ts +3 -87
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-param-store.js +16 -45
- package/dist/router-tanstack/search-store.d.ts +81 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.js +187 -0
- package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.test.d.ts +2 -0
- package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
- package/dist/router-tanstack/use-nav.d.ts +0 -2
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +34 -12
- package/dist/router-tanstack/use-nav.test.d.ts +2 -0
- package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
- package/dist/section/index.d.ts +2 -0
- package/dist/section/index.d.ts.map +1 -0
- package/dist/section/index.js +4 -0
- package/dist/section/section.d.ts +24 -0
- package/dist/section/section.d.ts.map +1 -0
- package/dist/section/section.js +126 -0
- package/dist/section/section.module.css +104 -0
- package/dist/section/section.module.css.d.ts +11 -0
- package/dist/section/section.test.d.ts +2 -0
- package/dist/section/section.test.d.ts.map +1 -0
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +18 -15
- package/dist/shell/app-header.module.css +77 -37
- package/dist/shell/app-header.module.css.d.ts +2 -1
- package/dist/shell/app-mobile-nav.d.ts +9 -2
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +98 -11
- package/dist/shell/app-mobile-nav.module.css +8 -0
- package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/dist/shell/app-sidebar.d.ts +55 -28
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +116 -67
- package/dist/shell/app-sidebar.module.css +303 -20
- package/dist/shell/app-sidebar.module.css.d.ts +19 -1
- package/dist/shell/index.d.ts +55 -21
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +40 -18
- package/dist/shell/mobile-nav-model.d.ts +13 -2
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +15 -1
- package/dist/shell/nav-count-badge.d.ts +1 -1
- package/dist/shell/page-bar.d.ts +51 -0
- package/dist/shell/page-bar.d.ts.map +1 -0
- package/dist/shell/page-bar.js +154 -0
- package/dist/shell/page-bar.module.css +178 -0
- package/dist/shell/page-bar.module.css.d.ts +17 -0
- package/dist/shell/page-bar.test.d.ts +2 -0
- package/dist/shell/page-bar.test.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.d.ts +48 -0
- package/dist/shell/sidebar-block-model.d.ts.map +1 -0
- package/dist/shell/sidebar-block-model.js +39 -0
- package/dist/shell/sidebar-blocks.d.ts +32 -0
- package/dist/shell/sidebar-blocks.d.ts.map +1 -0
- package/dist/shell/sidebar-blocks.js +169 -0
- package/dist/shell/sidebar-search.d.ts +13 -1
- package/dist/shell/sidebar-search.d.ts.map +1 -1
- package/dist/shell/sidebar-search.js +34 -1
- package/dist/shell/sidebar-search.module.css +26 -0
- package/dist/shell/sidebar-search.module.css.d.ts +2 -0
- package/dist/state/fields.d.ts +392 -0
- package/dist/state/fields.d.ts.map +1 -0
- package/dist/state/fields.js +369 -0
- package/dist/state/fields.test.d.ts +2 -0
- package/dist/state/fields.test.d.ts.map +1 -0
- package/dist/state/persisted.d.ts +48 -0
- package/dist/state/persisted.d.ts.map +1 -0
- package/dist/state/persisted.js +105 -0
- package/dist/state.d.ts +11 -56
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +14 -120
- package/dist/styles.css +13 -0
- package/dist/surfaces.d.ts +106 -24
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +155 -19
- package/dist/theme/controls.module.css +16 -1
- package/dist/theme/ctl-theme.d.ts +32 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -0
- package/dist/theme/ctl-theme.js +54 -0
- package/dist/theme/ctl-theme.test.d.ts +2 -0
- package/dist/theme/ctl-theme.test.d.ts.map +1 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
- package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
- package/dist/theme/divider-law.test.d.ts +2 -0
- package/dist/theme/divider-law.test.d.ts.map +1 -0
- package/dist/theme/icon-slot.d.ts +44 -0
- package/dist/theme/icon-slot.d.ts.map +1 -0
- package/dist/theme/icon-slot.js +21 -0
- package/dist/theme/icon-slot.module.css +47 -0
- package/dist/theme/icon-slot.module.css.d.ts +8 -0
- package/dist/theme/icon-slot.test.d.ts +2 -0
- package/dist/theme/icon-slot.test.d.ts.map +1 -0
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +82 -5
- package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
- package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
- package/dist/theme/segmented-control.module.css +20 -0
- package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +12 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +38 -8
- package/dist/tokens/palette.d.ts +97 -61
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +76 -38
- package/dist/tokens.css +10 -2
- package/dist/widget-header/delta-badge.d.ts +13 -0
- package/dist/widget-header/delta-badge.d.ts.map +1 -0
- package/dist/widget-header/delta-badge.js +29 -0
- package/dist/widget-header/delta-badge.test.d.ts +2 -0
- package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
- package/dist/widget-header/index.d.ts +3 -0
- package/dist/widget-header/index.d.ts.map +1 -0
- package/dist/widget-header/index.js +6 -0
- package/dist/widget-header/no-mantine.test.d.ts +2 -0
- package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
- package/dist/widget-header/widget-header.d.ts +37 -0
- package/dist/widget-header/widget-header.d.ts.map +1 -0
- package/dist/widget-header/widget-header.js +77 -0
- package/dist/widget-header/widget-header.module.css +335 -0
- package/dist/widget-header/widget-header.module.css.d.ts +24 -0
- package/dist/widget-header/widget-header.test.d.ts +2 -0
- package/dist/widget-header/widget-header.test.d.ts.map +1 -0
- package/llms.txt +30 -17
- package/package.json +14 -1
- package/src/charts/cursor/resolve.test.ts +29 -1
- package/src/charts/cursor/resolve.ts +38 -6
- package/src/charts/cursor/scope.tsx +6 -3
- package/src/charts/cursor/store.test.ts +19 -1
- package/src/charts/cursor/store.ts +10 -5
- package/src/charts/hooks/useChartCursor.ts +16 -8
- package/src/charts/index.ts +2 -2
- package/src/charts/primitives/CartesianChart.test.tsx +124 -0
- package/src/charts/primitives/ChartCard.test.tsx +66 -0
- package/src/charts/primitives/ChartCard.tsx +86 -146
- package/src/charts/sparklines/BarSparkline.tsx +43 -5
- package/src/cli/check-coverage.test.ts +297 -0
- package/src/cli/check-theme.test.ts +38 -14
- package/src/cli/index.ts +392 -16
- package/src/cli/init-sync.test.ts +97 -0
- package/src/connectivity/connectivity-indicator.tsx +16 -9
- package/src/content/index.ts +16 -3
- package/src/content/prose.module.css +7 -28
- package/src/content/prose.module.css.test.ts +8 -24
- package/src/controls/actions.module.css +62 -0
- package/src/controls/actions.module.css.d.ts +13 -0
- package/src/controls/actions.test.tsx +252 -0
- package/src/controls/actions.tsx +655 -0
- package/src/controls/actions.type-guard.test.ts +63 -0
- package/src/controls/compare-filter.tsx +64 -0
- package/src/controls/control-group.test.tsx +229 -0
- package/src/controls/control-group.tsx +63 -0
- package/src/controls/controls.module.css +542 -0
- package/src/controls/controls.module.css.d.ts +35 -0
- package/src/controls/controls.router.test.tsx +637 -0
- package/src/controls/enum-filter.tsx +159 -0
- package/src/controls/filter-context.tsx +153 -0
- package/src/controls/filter-pill.tsx +120 -0
- package/src/controls/filter-set.test.tsx +472 -0
- package/src/controls/filter-set.tsx +225 -0
- package/src/controls/filter-sheet.tsx +285 -0
- package/src/controls/glyphs.tsx +81 -0
- package/src/controls/index.ts +48 -0
- package/src/controls/multi-select-filter.tsx +140 -0
- package/src/controls/peer-boundary.test.ts +85 -0
- package/src/controls/props-contract.test.tsx +106 -0
- package/src/controls/range-filter.tsx +184 -0
- package/src/controls/search-filter.tsx +123 -0
- package/src/controls/select-filter.tsx +89 -0
- package/src/controls/sync-button.module.css +63 -0
- package/src/controls/sync-button.module.css.d.ts +11 -0
- package/src/controls/sync-button.test.tsx +183 -0
- package/src/controls/sync-button.tsx +197 -0
- package/src/controls/toggle-filter.tsx +60 -0
- package/src/controls/view-tabs.tsx +110 -0
- package/src/controls-dates/date-range-picker.tsx +42 -0
- package/src/controls-dates/index.ts +8 -0
- package/src/dashboard/delta-badge.tsx +4 -74
- package/src/dashboard/query-state.tsx +1 -1
- package/src/dashboard/settings-section.module.css +10 -0
- package/src/dashboard/settings-section.module.css.d.ts +1 -0
- package/src/dashboard/settings-section.test.tsx +56 -0
- package/src/dashboard/settings-section.tsx +33 -30
- package/src/dashboard/stat-card.module.css +82 -0
- package/src/dashboard/stat-card.module.css.d.ts +11 -0
- package/src/dashboard/stat-card.test.tsx +67 -1
- package/src/dashboard/stat-card.tsx +209 -68
- package/src/data/data-table.test.tsx +282 -6
- package/src/data/data-table.tsx +429 -94
- package/src/data/table.ts +2 -0
- package/src/guard/check-source.test.ts +126 -13
- package/src/guard/grace.test.ts +149 -0
- package/src/guard/index.ts +172 -29
- package/src/guard/types.ts +4 -2
- package/src/index.ts +46 -5
- package/src/nav/types.ts +84 -2
- package/src/notifications/bell.tsx +6 -3
- package/src/notifications/index.ts +1 -1
- package/src/router-tanstack/field.ts +31 -0
- package/src/router-tanstack/index.ts +24 -2
- package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
- package/src/router-tanstack/multi-search-param-store.ts +46 -77
- package/src/router-tanstack/search-param-store.ts +42 -159
- package/src/router-tanstack/search-store.router.test.tsx +661 -0
- package/src/router-tanstack/search-store.test.ts +418 -0
- package/src/router-tanstack/search-store.ts +380 -0
- package/src/router-tanstack/use-nav.test.tsx +187 -0
- package/src/router-tanstack/use-nav.ts +72 -14
- package/src/section/index.ts +1 -0
- package/src/section/section.module.css +104 -0
- package/src/section/section.module.css.d.ts +11 -0
- package/src/section/section.test.tsx +204 -0
- package/src/section/section.tsx +220 -0
- package/src/shell/app-breadcrumbs.tsx +44 -31
- package/src/shell/app-header.module.css +77 -37
- package/src/shell/app-header.module.css.d.ts +2 -1
- package/src/shell/app-mobile-nav.module.css +8 -0
- package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.tsx +147 -8
- package/src/shell/app-sidebar-account.tsx +2 -2
- package/src/shell/app-sidebar.module.css +303 -20
- package/src/shell/app-sidebar.module.css.d.ts +19 -1
- package/src/shell/app-sidebar.test.tsx +456 -41
- package/src/shell/app-sidebar.tsx +276 -140
- package/src/shell/index.test.tsx +291 -5
- package/src/shell/index.tsx +137 -38
- package/src/shell/mobile-nav-model.test.ts +228 -7
- package/src/shell/mobile-nav-model.ts +53 -3
- package/src/shell/nav-count-badge.tsx +1 -1
- package/src/shell/page-bar.module.css +178 -0
- package/src/shell/page-bar.module.css.d.ts +17 -0
- package/src/shell/page-bar.test.tsx +316 -0
- package/src/shell/page-bar.tsx +315 -0
- package/src/shell/sidebar-block-model.ts +83 -0
- package/src/shell/sidebar-blocks.tsx +301 -0
- package/src/shell/sidebar-search.module.css +26 -0
- package/src/shell/sidebar-search.module.css.d.ts +2 -0
- package/src/shell/sidebar-search.tsx +71 -2
- package/src/state/fields.test.tsx +258 -0
- package/src/state/fields.ts +892 -0
- package/src/state/persisted.ts +211 -0
- package/src/state.ts +46 -240
- package/src/styles.css +13 -0
- package/src/surfaces.ts +203 -36
- package/src/theme/controls.module.css +16 -1
- package/src/theme/ctl-theme.test.tsx +275 -0
- package/src/theme/ctl-theme.tsx +138 -0
- package/src/theme/ctl-tier-coverage.test.ts +170 -0
- package/src/theme/density-relations.test.ts +106 -51
- package/src/theme/divider-law.test.ts +259 -0
- package/src/theme/icon-slot.module.css +47 -0
- package/src/theme/icon-slot.module.css.d.ts +8 -0
- package/src/theme/icon-slot.test.tsx +275 -0
- package/src/theme/icon-slot.tsx +59 -0
- package/src/theme/index.ts +165 -3
- package/src/theme/layout-rhythm-css.test.ts +72 -0
- package/src/theme/radius.test.ts +11 -1
- package/src/theme/segmented-control.module.css +20 -0
- package/src/theme/shadow-surfaces.test.ts +20 -7
- package/src/theme/spacing.test.ts +75 -27
- package/src/theme/use-basalt-spacing.ts +2 -2
- package/src/tokens/density.test.ts +7 -25
- package/src/tokens/index.ts +48 -8
- package/src/tokens/palette.ts +107 -73
- package/src/widget-header/delta-badge.test.tsx +60 -0
- package/src/widget-header/delta-badge.tsx +57 -0
- package/src/widget-header/index.ts +2 -0
- package/src/widget-header/no-mantine.test.ts +33 -0
- package/src/widget-header/widget-header.module.css +335 -0
- package/src/widget-header/widget-header.module.css.d.ts +24 -0
- package/src/widget-header/widget-header.test.tsx +187 -0
- package/src/widget-header/widget-header.tsx +168 -0
- package/agent/rules/basalt-agent.md +0 -452
- package/agent/rules/basalt-app.md +0 -246
- package/agent/rules/basalt-commands.md +0 -320
- package/agent/rules/basalt-content.md +0 -409
- package/agent/rules/basalt-data.md +0 -269
- package/agent/rules/basalt-forms.md +0 -186
- package/agent/rules/basalt-notifications.md +0 -264
- package/agent/rules/basalt-query.md +0 -187
- package/agent/rules/basalt-router.md +0 -336
- package/dist/content/article-filter-bar.d.ts +0 -40
- package/dist/content/article-filter-bar.d.ts.map +0 -1
- package/dist/content/article-filter-bar.js +0 -56
- package/dist/shell/page-header.d.ts +0 -13
- package/dist/shell/page-header.d.ts.map +0 -1
- package/dist/shell/page-header.js +0 -23
- package/src/content/article-filter-bar.tsx +0 -88
- package/src/shell/page-header.tsx +0 -35
|
@@ -1,53 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Badge } from "@mantine/core";
|
|
3
|
-
import { alpha, VX } from "../tokens/index.js";
|
|
4
|
-
const defaultFormat = (value) => `${Math.abs(value).toFixed(1)}%`;
|
|
5
|
-
function DeltaBadge({
|
|
6
|
-
value,
|
|
7
|
-
format = defaultFormat,
|
|
8
|
-
withGlyph = true,
|
|
9
|
-
period
|
|
10
|
-
}) {
|
|
11
|
-
const tone = value > 0 ? VX.status.good : value < 0 ? VX.status.bad : VX.faint;
|
|
12
|
-
const glyph = value > 0 ? "\u25B2" : value < 0 ? "\u25BC" : void 0;
|
|
13
|
-
const showGlyph = withGlyph && glyph !== void 0;
|
|
14
|
-
return /* @__PURE__ */ jsxs(
|
|
15
|
-
Badge,
|
|
16
|
-
{
|
|
17
|
-
styles: {
|
|
18
|
-
root: {
|
|
19
|
-
backgroundColor: alpha(tone, 0.13),
|
|
20
|
-
padding: "var(--vx-space-badge-inset-y) var(--vx-space-badge-inset-x)",
|
|
21
|
-
height: "auto"
|
|
22
|
-
},
|
|
23
|
-
label: {
|
|
24
|
-
display: "inline-flex",
|
|
25
|
-
// theme-allow inline-display — a Badge `styles.label` part, not a layout node Flex could replace
|
|
26
|
-
alignItems: "center",
|
|
27
|
-
gap: 3,
|
|
28
|
-
// theme-allow inline-spacing — sub-scale glyph-to-label gap, below the smallest scale stop
|
|
29
|
-
fontFamily: "var(--basalt-font-mono)",
|
|
30
|
-
fontSize: VX.text.xs,
|
|
31
|
-
fontWeight: 600,
|
|
32
|
-
textTransform: "none",
|
|
33
|
-
letterSpacing: "normal",
|
|
34
|
-
color: tone
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
children: [
|
|
38
|
-
showGlyph && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8em" }, children: glyph }),
|
|
39
|
-
format(value),
|
|
40
|
-
period && /* @__PURE__ */ jsx(
|
|
41
|
-
"span",
|
|
42
|
-
{
|
|
43
|
-
style: { marginLeft: 4, fontWeight: 500, opacity: 0.6 },
|
|
44
|
-
children: period
|
|
45
|
-
}
|
|
46
|
-
)
|
|
47
|
-
]
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
}
|
|
1
|
+
import { DeltaBadge } from "../widget-header/delta-badge.js";
|
|
51
2
|
export {
|
|
52
3
|
DeltaBadge
|
|
53
4
|
};
|
|
@@ -5,7 +5,7 @@ export type QueryStateVariant = 'page' | 'section';
|
|
|
5
5
|
* The structural subset of a TanStack `UseQueryResult` these components read.
|
|
6
6
|
*
|
|
7
7
|
* Typed as a SUBSET rather than as `UseQueryResult<T>` on purpose: a composed, derived or
|
|
8
|
-
* hand-rolled result must be passable without a cast
|
|
8
|
+
* hand-rolled result must be passable without a cast, and
|
|
9
9
|
* these components stay independent of which `@tanstack/react-query` major a consumer pins. This
|
|
10
10
|
* is NOT a claim that the root barrel is free of that package — `./connectivity` imports
|
|
11
11
|
* `onlineManager` as a value and is re-exported from `src/index.ts`. The cost of the subset is
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
export type SettingsSectionProps = {
|
|
3
|
-
/** Head-font section title
|
|
3
|
+
/** Head-font section title, rendered via `WidgetHeader tier="section"`. */
|
|
4
4
|
title: string;
|
|
5
5
|
/** Optional 13px muted description below the title. */
|
|
6
|
-
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
/** Header-right slot — wrapped in `CtlSlot` (C1/C5). */
|
|
8
|
+
actions?: ReactNode;
|
|
7
9
|
/** Section body — typically a stack of `SettingsRow`s. */
|
|
8
10
|
children: ReactNode;
|
|
9
11
|
};
|
|
10
|
-
export declare function SettingsSection({ title,
|
|
12
|
+
export declare function SettingsSection({ title, subtitle, actions, children }: SettingsSectionProps): import("react").JSX.Element;
|
|
11
13
|
export type SettingsRowProps = {
|
|
12
14
|
/** 13px ink label on the left. */
|
|
13
15
|
label: string;
|
|
@@ -20,5 +22,5 @@ export type SettingsRowProps = {
|
|
|
20
22
|
};
|
|
21
23
|
export declare function SettingsRow({ label, description, control, children }: SettingsRowProps): import("react").JSX.Element;
|
|
22
24
|
export type DangerZoneProps = SettingsSectionProps;
|
|
23
|
-
export declare function DangerZone({ title,
|
|
25
|
+
export declare function DangerZone({ title, subtitle, actions, children }: DangerZoneProps): import("react").JSX.Element;
|
|
24
26
|
//# sourceMappingURL=settings-section.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-section.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"settings-section.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAetC,MAAM,MAAM,oBAAoB,GAAG;IACjC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,oBAAoB,+BAc3F;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAYtF;AAED,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAElD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,+BAoBjF"}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Card, Stack } from "@mantine/core";
|
|
3
|
+
import { WidgetHeader } from "../widget-header/index.js";
|
|
4
|
+
import { CtlSlot } from "../theme/index.js";
|
|
3
5
|
import { alpha, VX } from "../tokens/index.js";
|
|
4
6
|
import classes from "./settings-section.module.css";
|
|
5
|
-
const titleStyle = {
|
|
6
|
-
fontFamily: "var(--basalt-font-head)",
|
|
7
|
-
fontStretch: "88%",
|
|
8
|
-
fontSize: VX.text.md,
|
|
9
|
-
fontWeight: 550,
|
|
10
|
-
color: VX.ink
|
|
11
|
-
};
|
|
12
7
|
const eyebrowStyle = {
|
|
13
8
|
fontFamily: "var(--basalt-font-mono)",
|
|
14
9
|
fontSize: VX.text.micro,
|
|
@@ -17,12 +12,17 @@ const eyebrowStyle = {
|
|
|
17
12
|
textTransform: "uppercase",
|
|
18
13
|
color: VX.status.bad
|
|
19
14
|
};
|
|
20
|
-
function SettingsSection({ title,
|
|
15
|
+
function SettingsSection({ title, subtitle, actions, children }) {
|
|
21
16
|
return /* @__PURE__ */ jsxs(Card, { style: { padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)" }, children: [
|
|
22
|
-
/* @__PURE__ */
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
/* @__PURE__ */ jsx("div", { className: classes.header, children: /* @__PURE__ */ jsx(
|
|
18
|
+
WidgetHeader,
|
|
19
|
+
{
|
|
20
|
+
tier: "section",
|
|
21
|
+
title,
|
|
22
|
+
...subtitle !== void 0 && { subtitle },
|
|
23
|
+
...actions !== void 0 && { actions: /* @__PURE__ */ jsx(CtlSlot, { children: actions }) }
|
|
24
|
+
}
|
|
25
|
+
) }),
|
|
26
26
|
/* @__PURE__ */ jsx("div", { className: classes.rows, children })
|
|
27
27
|
] });
|
|
28
28
|
}
|
|
@@ -35,7 +35,7 @@ function SettingsRow({ label, description, control, children }) {
|
|
|
35
35
|
/* @__PURE__ */ jsx("div", { className: classes.control, children: control ?? children })
|
|
36
36
|
] });
|
|
37
37
|
}
|
|
38
|
-
function DangerZone({ title,
|
|
38
|
+
function DangerZone({ title, subtitle, actions, children }) {
|
|
39
39
|
return /* @__PURE__ */ jsxs(
|
|
40
40
|
Card,
|
|
41
41
|
{
|
|
@@ -44,10 +44,17 @@ function DangerZone({ title, description, children }) {
|
|
|
44
44
|
boxShadow: `${VX.shadowCard}, 0 0 0 1px ${alpha(VX.status.bad, 0.25)}`
|
|
45
45
|
},
|
|
46
46
|
children: [
|
|
47
|
-
/* @__PURE__ */ jsxs(
|
|
47
|
+
/* @__PURE__ */ jsxs("div", { className: classes.header, children: [
|
|
48
48
|
/* @__PURE__ */ jsx("span", { style: eyebrowStyle, children: "Danger Zone" }),
|
|
49
|
-
/* @__PURE__ */ jsx(
|
|
50
|
-
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
WidgetHeader,
|
|
51
|
+
{
|
|
52
|
+
tier: "section",
|
|
53
|
+
title,
|
|
54
|
+
...subtitle !== void 0 && { subtitle },
|
|
55
|
+
...actions !== void 0 && { actions: /* @__PURE__ */ jsx(CtlSlot, { children: actions }) }
|
|
56
|
+
}
|
|
57
|
+
)
|
|
51
58
|
] }),
|
|
52
59
|
/* @__PURE__ */ jsx("div", { className: classes.rows, children })
|
|
53
60
|
]
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* vertical padding and a 1px `--vx-divider` rule below it, EXCEPT the last row in the section —
|
|
4
4
|
* `:not(:last-child)` strips the divider there so the section doesn't end on a trailing line.
|
|
5
5
|
*/
|
|
6
|
+
|
|
7
|
+
/* The WidgetHeader block (plus DangerZone's eyebrow, when present) sits above `.rows` with the
|
|
8
|
+
* same gap the old inline `Stack gap={2} mb="sm"` produced. */
|
|
9
|
+
.header {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 2px;
|
|
13
|
+
margin-bottom: var(--mantine-spacing-sm);
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
.rows > * {
|
|
7
17
|
padding: var(--vx-space-settings-row-inset-y) 0;
|
|
8
18
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-section.test.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-section.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,25 +1,60 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
/** A threshold verdict on the card's value. `undefined` is never tinted — see the module docblock. */
|
|
3
3
|
export type StatCardTone = 'good' | 'warn' | 'bad';
|
|
4
|
+
/**
|
|
5
|
+
* The measured box a `sparkline` render prop receives.
|
|
6
|
+
*
|
|
7
|
+
* A `LineSparkline`/`BarSparkline` takes `width`/`height` as NUMBERS (they are SVG attributes — an
|
|
8
|
+
* svg cannot size itself from CSS and then scale its scales), so "full bleed" was never expressible
|
|
9
|
+
* as a `ReactNode`: the caller had to guess a pixel width, or write its own `useChartSize` wrapper
|
|
10
|
+
* component. Four consumers wrote that wrapper; two of them hardcoded a width and drew a sparkline
|
|
11
|
+
* that stopped short of the card edge on every viewport but the one they built on.
|
|
12
|
+
*/
|
|
13
|
+
export type StatCardSparklineSize = {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
4
17
|
export type StatCardProps = {
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
/**
|
|
18
|
+
/** Head-font title, rendered via `WidgetHeader tier="widget"`. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Optional leading icon, forwarded to `WidgetHeader`. */
|
|
21
|
+
icon?: ReactNode;
|
|
22
|
+
/** Muted line under the hero row, forwarded to `WidgetHeader` — the unit or basis a
|
|
23
|
+
* pre-formatted `value` cannot carry (`per day`, `of 40 planned`). Not a second metric. */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
/** Info tooltip beside the title, forwarded to `WidgetHeader` — how the number is computed. Never
|
|
26
|
+
* part of the heading's accessible name; see `WidgetHeaderProps.info`. */
|
|
27
|
+
info?: string;
|
|
28
|
+
/** Pre-formatted KPI value string (mono ~24px, weight 600, ink) — the hero-row value. */
|
|
8
29
|
value: string;
|
|
9
30
|
/** Signed delta rendered via `DeltaBadge`; omit to hide the trend chip entirely. */
|
|
10
31
|
delta?: number;
|
|
11
32
|
/** Optional comparison timeframe shown after the delta (e.g. `MoM`) — forwarded to `DeltaBadge`. */
|
|
12
33
|
deltaPeriod?: string;
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Optional trend visual. Either a node, or a RENDER PROP receiving the slot's measured box.
|
|
36
|
+
*
|
|
37
|
+
* Prefer the render prop for anything from `basalt-ui/charts`: those take numeric `width`/`height`
|
|
38
|
+
* (SVG attributes), so a plain node has to hardcode a width, and a hardcoded width is not
|
|
39
|
+
* full-bleed on any viewport but the one it was typed on. With `sparklinePlacement="bleed"` the
|
|
40
|
+
* measured width is the card's own inner width including the bled inset; with `'right'` it is the
|
|
41
|
+
* fixed 72×26 slot (see {@link SPARKLINE_RIGHT_WIDTH} for why that one is not measured).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* sparkline={({ width, height }) => <BarSparkline data={history} width={width} height={height} />}
|
|
45
|
+
*/
|
|
46
|
+
sparkline?: ReactNode | ((size: StatCardSparklineSize) => ReactNode);
|
|
47
|
+
/** Where `sparkline` sits. `'bleed'` (default) is today's full-width row bled to the card edges;
|
|
48
|
+
* `'right'` sits it beside the hero value row. Collapses to `'bleed'` below `sm`. */
|
|
49
|
+
sparklinePlacement?: 'bleed' | 'right';
|
|
50
|
+
/** Header-right slot (e.g. a ghost "..." menu trigger) — wrapped in `CtlSlot tier="widget"`, so a
|
|
51
|
+
* raw `ActionIcon` with no `size` lands on the 24px step the 28px header row holds (C1/C5). */
|
|
52
|
+
actions?: ReactNode;
|
|
18
53
|
/** Threshold verdict — draws an accent rail down the card's leading edge and announces itself to
|
|
19
54
|
* assistive tech. Omitting it is NOT `'good'`: omitted covers a reading that is fine AND one that
|
|
20
55
|
* is absent, and stays untinted so a card with nothing measured can never render green. Pass
|
|
21
56
|
* `'good'` only to assert a measured value that has earned the verdict. */
|
|
22
57
|
tone?: StatCardTone;
|
|
23
58
|
};
|
|
24
|
-
export declare function StatCard({
|
|
59
|
+
export declare function StatCard({ title, icon, subtitle, info, value, delta, deltaPeriod, sparkline, sparklinePlacement, actions, tone, }: StatCardProps): import("react").JSX.Element;
|
|
25
60
|
//# sourceMappingURL=stat-card.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"AA6FA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAMrD,sGAAsG;AACtG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAclD;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAkDD,MAAM,MAAM,aAAa,GAAG;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;+FAC2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;8EAC0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC,CAAA;IACpE;yFACqF;IACrF,kBAAkB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACtC;mGAC+F;IAC/F,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;+EAG2E;IAC3E,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,kBAA4B,EAC5B,OAAO,EACP,IAAI,GACL,EAAE,aAAa,+BAmGf"}
|
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Card,
|
|
3
|
-
import {
|
|
2
|
+
import { Box, Card, VisuallyHidden } from "@mantine/core";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { WidgetHeader } from "../widget-header/index.js";
|
|
5
|
+
import { CtlSlot } from "../theme/index.js";
|
|
4
6
|
import { VX } from "../tokens/index.js";
|
|
7
|
+
import classes from "./stat-card.module.css";
|
|
8
|
+
const SPARKLINE_RIGHT_WIDTH = 72;
|
|
9
|
+
const SPARKLINE_RIGHT_HEIGHT = 26;
|
|
10
|
+
function useSlotWidth(active) {
|
|
11
|
+
const [width, setWidth] = useState(0);
|
|
12
|
+
const nodeRef = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const node = nodeRef.current;
|
|
15
|
+
if (!active || node === null) return;
|
|
16
|
+
const measure = () => {
|
|
17
|
+
setWidth(node.getBoundingClientRect().width);
|
|
18
|
+
};
|
|
19
|
+
measure();
|
|
20
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
21
|
+
const observer = new ResizeObserver(measure);
|
|
22
|
+
observer.observe(node);
|
|
23
|
+
return () => {
|
|
24
|
+
observer.disconnect();
|
|
25
|
+
};
|
|
26
|
+
}, [active]);
|
|
27
|
+
return {
|
|
28
|
+
ref: (node) => {
|
|
29
|
+
nodeRef.current = node;
|
|
30
|
+
},
|
|
31
|
+
width
|
|
32
|
+
};
|
|
33
|
+
}
|
|
5
34
|
const TONE_RAIL_WIDTH = 3;
|
|
6
35
|
const TONE_LABEL = {
|
|
7
36
|
good: "Within the good threshold",
|
|
@@ -9,14 +38,22 @@ const TONE_LABEL = {
|
|
|
9
38
|
bad: "Past the severe threshold"
|
|
10
39
|
};
|
|
11
40
|
function StatCard({
|
|
12
|
-
|
|
41
|
+
title,
|
|
42
|
+
icon,
|
|
43
|
+
subtitle,
|
|
44
|
+
info,
|
|
13
45
|
value,
|
|
14
46
|
delta,
|
|
15
47
|
deltaPeriod,
|
|
16
48
|
sparkline,
|
|
17
|
-
|
|
49
|
+
sparklinePlacement = "bleed",
|
|
50
|
+
actions,
|
|
18
51
|
tone
|
|
19
52
|
}) {
|
|
53
|
+
const isRender = typeof sparkline === "function";
|
|
54
|
+
const bleeds = sparklinePlacement === "bleed";
|
|
55
|
+
const slot = useSlotWidth(isRender);
|
|
56
|
+
const measured = { width: slot.width, height: SPARKLINE_RIGHT_HEIGHT };
|
|
20
57
|
return /* @__PURE__ */ jsxs(
|
|
21
58
|
Card,
|
|
22
59
|
{
|
|
@@ -27,12 +64,13 @@ function StatCard({
|
|
|
27
64
|
// shadow-card ring is unaffected (verified — only an ANCESTOR's overflow clips a
|
|
28
65
|
// descendant's shadow). Mantine's Card root already sets `overflow: hidden`; this is
|
|
29
66
|
// explicit for intent.
|
|
67
|
+
//
|
|
68
|
+
// NO `minHeight`. It was 118px, which is ~25px of dead space under a card holding a title
|
|
69
|
+
// row, a hero row and a 26px trend — and it was dead space the card could not use, since the
|
|
70
|
+
// body is `height: 100%` and the sparkline is `margin-top: auto`. The content is what states
|
|
71
|
+
// the height now (~88-96px), and four cards in a `SimpleGrid` still match because a grid row
|
|
72
|
+
// stretches its items.
|
|
30
73
|
padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)",
|
|
31
|
-
minHeight: 118,
|
|
32
|
-
display: "flex",
|
|
33
|
-
// theme-allow inline-display — Card root as a flex column; Mantine Card takes no layout props
|
|
34
|
-
flexDirection: "column",
|
|
35
|
-
gap: "var(--vx-space-stat-card-gap)",
|
|
36
74
|
overflow: "hidden",
|
|
37
75
|
// Anchors the tone rail. Set unconditionally so a card's stacking context does not change
|
|
38
76
|
// depending on whether it happens to have crossed a threshold this render.
|
|
@@ -60,50 +98,44 @@ function StatCard({
|
|
|
60
98
|
}
|
|
61
99
|
)
|
|
62
100
|
] }),
|
|
63
|
-
/* @__PURE__ */ jsxs(
|
|
64
|
-
/* @__PURE__ */ jsx(
|
|
65
|
-
|
|
101
|
+
/* @__PURE__ */ jsxs("div", { className: classes.body, "data-placement": sparklinePlacement, children: [
|
|
102
|
+
/* @__PURE__ */ jsx("div", { className: classes.header, children: /* @__PURE__ */ jsx(
|
|
103
|
+
WidgetHeader,
|
|
66
104
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
|
|
105
|
+
tier: "widget",
|
|
106
|
+
title,
|
|
107
|
+
...icon !== void 0 && { icon },
|
|
108
|
+
...subtitle !== void 0 && { subtitle },
|
|
109
|
+
...info !== void 0 && { info },
|
|
110
|
+
value,
|
|
111
|
+
...delta !== void 0 && { delta },
|
|
112
|
+
...deltaPeriod !== void 0 && { deltaPeriod },
|
|
113
|
+
...actions !== void 0 && {
|
|
114
|
+
actions: /* @__PURE__ */ jsx(CtlSlot, { tier: "widget", children: actions })
|
|
115
|
+
}
|
|
76
116
|
}
|
|
77
|
-
),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/* @__PURE__ */ jsxs(Group, { align: "center", gap: 8, wrap: "nowrap", children: [
|
|
81
|
-
/* @__PURE__ */ jsx(
|
|
82
|
-
"span",
|
|
117
|
+
) }),
|
|
118
|
+
sparkline !== void 0 && /* @__PURE__ */ jsx(
|
|
119
|
+
"div",
|
|
83
120
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
121
|
+
ref: slot.ref,
|
|
122
|
+
className: bleeds ? classes.sparklineBleed : classes.sparklineRight,
|
|
123
|
+
...!bleeds && {
|
|
124
|
+
// CUSTOM PROPERTIES, not `flexBasis`/`height` directly. A React inline style beats
|
|
125
|
+
// every stylesheet rule, so an inline `flexBasis: 72` also applied below `sm` — where
|
|
126
|
+
// `'right'` collapses to a COLUMN and flex-basis is therefore the main-axis HEIGHT.
|
|
127
|
+
// The mobile sparkline rendered in a 72px-tall box holding 26px of bars, which is the
|
|
128
|
+
// dead band under every KPI value on a phone. Handed to CSS as values instead, the
|
|
129
|
+
// media query can reset the box and the numbers still live in one place.
|
|
130
|
+
style: {
|
|
131
|
+
"--basalt-stat-sparkline-w": `${SPARKLINE_RIGHT_WIDTH}px`,
|
|
132
|
+
"--basalt-stat-sparkline-h": `${SPARKLINE_RIGHT_HEIGHT}px`
|
|
133
|
+
}
|
|
90
134
|
},
|
|
91
|
-
children:
|
|
135
|
+
children: isRender ? measured.width > 0 && sparkline(measured) : sparkline
|
|
92
136
|
}
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
] }),
|
|
96
|
-
sparkline && /* @__PURE__ */ jsx(
|
|
97
|
-
"div",
|
|
98
|
-
{
|
|
99
|
-
style: {
|
|
100
|
-
marginTop: "auto",
|
|
101
|
-
marginInline: "calc(-1 * var(--mantine-spacing-sm))",
|
|
102
|
-
marginBottom: "calc(-1 * var(--mantine-spacing-xs))"
|
|
103
|
-
},
|
|
104
|
-
children: sparkline
|
|
105
|
-
}
|
|
106
|
-
)
|
|
137
|
+
)
|
|
138
|
+
] })
|
|
107
139
|
]
|
|
108
140
|
}
|
|
109
141
|
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* StatCard's sparkline placement (docs/CONTROLS-SPEC.md §2.2). `bleed` (default) keeps the historic
|
|
3
|
+
* full-width row beneath the hero value, bled past the card's own inset padding — same negative
|
|
4
|
+
* margins the pre-WidgetHeader version drew inline. `right` sits the sparkline beside the
|
|
5
|
+
* `WidgetHeader`'s hero-value row instead, the reference-design look, and collapses back to
|
|
6
|
+
* `bleed`'s stacked layout below `sm` — CSS only, no JS branch (`47.99375em` is Mantine's own
|
|
7
|
+
* complement of `min-width: 48em`, same value `content/prose.module.css` derives it from).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.body {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: var(--vx-space-stat-card-gap);
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* `flex-end`, not `center`: the sparkline belongs to the HERO ROW, and the hero row is the header
|
|
19
|
+
* block's last line. Centred against the whole body it floated between the title and the value,
|
|
20
|
+
* which reads as a third, unrelated element; bottom-aligned to the header block it sits beside the
|
|
21
|
+
* number it describes, the reference layout. `gap` is the card's own control gap so the trend never
|
|
22
|
+
* touches a long value.
|
|
23
|
+
*/
|
|
24
|
+
.body[data-placement='right'] {
|
|
25
|
+
flex-direction: row;
|
|
26
|
+
align-items: flex-end;
|
|
27
|
+
gap: var(--vx-space-control-gap);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.header {
|
|
31
|
+
min-width: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* `flex-shrink: 0` on the header, `1` on the sparkline slot below: the hero value and its delta keep
|
|
36
|
+
* their width and the TREND gives ground. The other way round, a 4-up KPI grid on a 1440 viewport
|
|
37
|
+
* ellipsized `$1,294,900` to `$1294.…` in order to keep 72px of bars intact, which is the wrong
|
|
38
|
+
* thing to protect. The value still carries `overflow: hidden` as the last resort, and `Card` clips.
|
|
39
|
+
*/
|
|
40
|
+
.body[data-placement='right'] .header {
|
|
41
|
+
flex: 1 0 auto;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sparklineBleed {
|
|
46
|
+
margin-top: auto;
|
|
47
|
+
margin-inline: calc(-1 * var(--mantine-spacing-sm));
|
|
48
|
+
margin-bottom: calc(-1 * var(--mantine-spacing-xs));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Asks for `StatCard`'s `SPARKLINE_RIGHT_WIDTH`/`_HEIGHT` (handed down as custom properties, so this
|
|
52
|
+
* rule and the `< sm` reset below can both reach them) and shrinks below the width before the hero
|
|
53
|
+
* row does — see the `.header` note above. `min-width: 0` is what lets it. */
|
|
54
|
+
.sparklineRight {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex: 0 1 var(--basalt-stat-sparkline-w, 4.5rem);
|
|
57
|
+
min-width: 0;
|
|
58
|
+
height: var(--basalt-stat-sparkline-h, 1.625rem);
|
|
59
|
+
align-items: flex-end;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media (max-width: 47.99375em) {
|
|
64
|
+
.body[data-placement='right'] {
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
align-items: stretch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.body[data-placement='right'] .header {
|
|
70
|
+
flex: initial;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The row became a COLUMN, so `flex-basis` is now the main-axis HEIGHT — the 72px width would
|
|
74
|
+
* become a 72px-tall box around 26px of bars. `flex: none` + the height keeps the bled trend the
|
|
75
|
+
* same 26px it is beside a hero row. */
|
|
76
|
+
.body[data-placement='right'] .sparklineRight {
|
|
77
|
+
flex: none;
|
|
78
|
+
margin-top: auto;
|
|
79
|
+
margin-inline: calc(-1 * var(--mantine-spacing-sm));
|
|
80
|
+
margin-bottom: calc(-1 * var(--mantine-spacing-xs));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
|
|
3
|
+
* signature — so dot access stays verbatim under the package's strict flags.
|
|
4
|
+
*/
|
|
5
|
+
declare const classes: {
|
|
6
|
+
readonly body: string
|
|
7
|
+
readonly header: string
|
|
8
|
+
readonly sparklineBleed: string
|
|
9
|
+
readonly sparklineRight: string
|
|
10
|
+
}
|
|
11
|
+
export default classes
|