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
package/src/tokens/palette.ts
CHANGED
|
@@ -302,6 +302,23 @@ const SPACE_ANCHORS_BASE = {
|
|
|
302
302
|
* Input tracks it at every density level instead of only coinciding at level 0 (the defect this
|
|
303
303
|
* anchor fixes — Mantine's own `--button-height-md`/`--ai-size` are static, density-blind). */
|
|
304
304
|
controlHeight: CONTROL_HEIGHT_BASE,
|
|
305
|
+
/** The `size="icon"` count-tag height (`docs/CONTROLS-SPEC.md` §5) — inline chip / table-cell
|
|
306
|
+
* count badge. Floored at 18px in {@link deriveSpacing} (same "negative level can't collapse a
|
|
307
|
+
* real control below a legible/tappable minimum" reasoning as `sidebarSearchTriggerHeight`'s WCAG
|
|
308
|
+
* floor, one notch tighter since a count tag is a badge, not a primary interactive target). */
|
|
309
|
+
controlHeightTag: 20,
|
|
310
|
+
/** The `size="icon"` ActionIcon height (`docs/CONTROLS-SPEC.md` §5) — `WidgetHeader
|
|
311
|
+
* tier="widget"` actions. Floored at 22px in {@link deriveSpacing}. */
|
|
312
|
+
controlHeightWidget: 24,
|
|
313
|
+
/** The `size="ctl"` Mantine tier's resolved height (`docs/CONTROLS-SPEC.md` §5, C5) —
|
|
314
|
+
* `PageBar`/`Section`/table toolbar/sidebar-block controls. Floored at 28px in
|
|
315
|
+
* {@link deriveSpacing}. */
|
|
316
|
+
controlHeightCtl: 30,
|
|
317
|
+
/** The touch hit-area size (`::before`) every home control expands to below `sm` (C15,
|
|
318
|
+
* `docs/CONTROLS-SPEC.md` §5) — independent of the visible `controlHeightCtl` box, same shape as
|
|
319
|
+
* `mobileNavBarHeight`/`mobileNavRowHeight`'s Apple HIG 44pt / WCAG 2.5.5 floors elsewhere in this
|
|
320
|
+
* table. Floored at 30px in {@link deriveSpacing} (C15's "floor 30 at density −3"). */
|
|
321
|
+
touchControlHeight: 36,
|
|
305
322
|
} as const
|
|
306
323
|
|
|
307
324
|
/**
|
|
@@ -355,14 +372,13 @@ const SPACE_STEP_BASE = {
|
|
|
355
372
|
timelineBullet: 22,
|
|
356
373
|
|
|
357
374
|
// ── Shared across ≥2 sites (structural — must render at the same value) ──────────────────────
|
|
358
|
-
// `stickyHeaderClearance
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
// still carries both keys — see that type's own doc — they're just not part of this BASE table.
|
|
375
|
+
// `stickyHeaderClearance` USED to live here as an independent base literal (84) — it doesn't any
|
|
376
|
+
// more. It is DERIVED, post-`step`, from the AppShell header it exists to clear
|
|
377
|
+
// (`appShellHeaderHeight` below) instead (see {@link deriveSpacing}'s doc for why): an independent
|
|
378
|
+
// literal could drift from that header, which is exactly what happened. `SpaceValues.step` still
|
|
379
|
+
// carries the key — see that type's own doc — it is just not part of this BASE table. The
|
|
380
|
+
// `stickyHeaderClearanceMobile` sibling went with the two-row mobile header in 1.26.0: the header
|
|
381
|
+
// is one 48px row at every viewport now, so one clearance covers both.
|
|
366
382
|
/** NavLink leftSection icon-to-label gap — shared by `theme/nav-link.module.css` (every render
|
|
367
383
|
* path) and `shell/app-sidebar.module.css`'s own `.link` rule (belt-and-suspenders on the same
|
|
368
384
|
* DOM), and reused verbatim for `.footerBtn`/`.accountRow`'s icon gap (their own doc comments call
|
|
@@ -572,36 +588,28 @@ const SPACE_STEP_BASE = {
|
|
|
572
588
|
/** Settings-menu `<Menu width={…}>` (`app-sidebar.tsx`). */
|
|
573
589
|
sidebarSettingsMenuWidth: 200,
|
|
574
590
|
|
|
575
|
-
// ── shell/app-header.module.css ───────────────────────────────────────────────────────────────
|
|
576
|
-
/** Mobile two-row header's page-actions row height. */
|
|
577
|
-
appHeaderMobileActionsHeight: 52,
|
|
578
|
-
|
|
579
591
|
// ── shell/index.tsx (AppShell dimensions) ──────────────────────────────────────────────
|
|
580
592
|
/** AppShell desktop header bar height — sized to hold one row of `size="md"` controls, so it
|
|
581
593
|
* tracks `controlHeight`. Its level-0 coincidence with `appShellNavbarRailWidth` below is a
|
|
582
594
|
* coincidence, not a law: this is a horizontal bar's HEIGHT (governed by control height), that
|
|
583
595
|
* is a vertical rail's WIDTH (governed by icon footprint). They stay separate entries. */
|
|
596
|
+
/** AppShell header bar height — ONE value at every viewport since 1.26.0 (law C14,
|
|
597
|
+
* `docs/CONTROLS-SPEC.md` §2.1). The pre-1.26.0 `appShellHeaderMobileHeight` (97) existed only
|
|
598
|
+
* because the mobile header wrapped to a second, always-reserved page-actions row; `PageBar`
|
|
599
|
+
* folds that row's overflow into a kebab and moves its filters/tabs into the page flow, so the
|
|
600
|
+
* bar is 48px on a phone too and there is no sum to keep in step any more. */
|
|
584
601
|
appShellHeaderHeight: 48,
|
|
585
|
-
/** AppShell
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
* budget collapses 52px -> 12px across the level range.
|
|
602
|
+
/** AppShell navbar width, expanded — ONE entry for both the `base` (mobile, where the navbar is
|
|
603
|
+
* permanently collapsed and unused — see `MobileNav`) and the expanded `sm` value: the same
|
|
604
|
+
* `.root` at full width, genuinely one concept.
|
|
589
605
|
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
|
|
597
|
-
* `stickyHeaderClearance*` already is — post-`step`, from its own addends rather than as an
|
|
598
|
-
* independent literal — would make the drift impossible instead of merely re-tuned. That is the
|
|
599
|
-
* same refactor, and the same reasoning, this PR applied to the clearances; it is worth doing to
|
|
600
|
-
* this one too, and it stays a follow-up only because it moves every non-zero level's value. */
|
|
601
|
-
appShellHeaderMobileHeight: 97,
|
|
602
|
-
/** AppShell navbar width, expanded — ONE entry for both the `base` (mobile drawer) and the
|
|
603
|
-
* expanded `sm` value: the same `.root` at full width, genuinely one concept. */
|
|
604
|
-
appShellNavbarWidth: 216,
|
|
606
|
+
* 256, up from 216. At 216 the column was 196px of usable text after `.root`'s 10px inset a side,
|
|
607
|
+
* and the sidebar carries more than nav rows: a block row is `label · meta`, an account row is
|
|
608
|
+
* `avatar · name · plan`, and a `Recents` entry is a sentence. Every one of them was ellipsizing
|
|
609
|
+
* in the second word. 256 is also the width the shell's own dropdowns already assume
|
|
610
|
+
* (`sidebarAccountMenuWidth` 220 + the row inset), so the menu no longer overhangs the column it
|
|
611
|
+
* belongs to. */
|
|
612
|
+
appShellNavbarWidth: 256,
|
|
605
613
|
/** AppShell navbar width, collapsed icon rail. Separate from `appShellHeaderHeight` above
|
|
606
614
|
* despite sharing 48 at level 0 (see that entry's note). */
|
|
607
615
|
appShellNavbarRailWidth: 48,
|
|
@@ -685,6 +693,33 @@ const SPACE_STEP_BASE = {
|
|
|
685
693
|
virtualRowInsetY: 8,
|
|
686
694
|
/** `data/virtual-list.tsx`'s skeleton row horizontal inset. */
|
|
687
695
|
virtualRowInsetX: 12,
|
|
696
|
+
|
|
697
|
+
// ── Controls — homes/tiers (`docs/CONTROLS-SPEC.md` §5) ──────────────────────────────────────
|
|
698
|
+
/** `PageBar` row height (row 1 or row 2, each measured independently — the bar's own overall
|
|
699
|
+
* height is the sum published as `--basalt-page-bar-h`, computed by `PageBar` itself, not a
|
|
700
|
+
* static token). */
|
|
701
|
+
pageBarRowHeight: 40,
|
|
702
|
+
/** `WidgetHeader tier="section"` row height. */
|
|
703
|
+
sectionHeaderHeight: 36,
|
|
704
|
+
/** `WidgetHeader tier="widget"` row height. */
|
|
705
|
+
widgetHeaderHeight: 28,
|
|
706
|
+
/** Sidebar block list-item row height. */
|
|
707
|
+
sidebarBlockRowHeight: 32,
|
|
708
|
+
/** Gap between adjacent controls inside a home (filter pills, action buttons). */
|
|
709
|
+
controlGap: 6,
|
|
710
|
+
/** Mobile bottom-sheet row height (the `Filters (n)` drawer, a block's More-sheet row) — Apple
|
|
711
|
+
* HIG 44pt / WCAG 2.5.5 AAA, same floor family as `mobileNavRowHeight`. */
|
|
712
|
+
sheetRowHeight: 44,
|
|
713
|
+
/** `BasaltDataTable`'s header row. Its own entry rather than a reuse of `sectionHeaderHeight`
|
|
714
|
+
* (which is 36 too at level 0): one is a heading's box, this is a table's `th`, and a density
|
|
715
|
+
* pass that wants denser table rows must not be forced to shrink every section header with them.
|
|
716
|
+
* Mantine sizes both `th` and `td` from ONE `verticalSpacing` prop, so a 36/40 pair is
|
|
717
|
+
* unreachable through it — both are applied in `theme/index.ts`'s `Table.extend` as JS numbers,
|
|
718
|
+
* which is why (like `progressBarSize`) neither is emitted as a `--vx-*` var. */
|
|
719
|
+
tableHeaderHeight: 36,
|
|
720
|
+
/** `BasaltDataTable`'s body row — one step taller than the header, so the mono uppercase `th`
|
|
721
|
+
* reads as a label band over roomier data rows rather than as the first row of the data. */
|
|
722
|
+
tableRowHeight: 40,
|
|
688
723
|
} as const
|
|
689
724
|
|
|
690
725
|
/**
|
|
@@ -723,17 +758,14 @@ export const SPACE_FIXED = {
|
|
|
723
758
|
export type SpaceValues = {
|
|
724
759
|
anchors: { [K in keyof typeof SPACE_ANCHORS_BASE]: number }
|
|
725
760
|
scale: { [K in keyof typeof SPACE_SCALE_BASE]: number }
|
|
726
|
-
/** `SPACE_STEP_BASE`'s one-offs PLUS `stickyHeaderClearance
|
|
727
|
-
*
|
|
728
|
-
* `appShellHeaderHeight` (desktop) and one from `appShellHeaderMobileHeight` (mobile), see
|
|
761
|
+
/** `SPACE_STEP_BASE`'s one-offs PLUS `stickyHeaderClearance`, which is NOT in that base table —
|
|
762
|
+
* it is computed after the rest of `step`, from `appShellHeaderHeight`, see
|
|
729
763
|
* {@link deriveSpacing}'s doc. Kept on this same `step` object (not split into a third group)
|
|
730
|
-
* because every consumer reads
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
* `content/prose.module.css`/`content/article-layout.module.css` for the CSS-side split. */
|
|
764
|
+
* because every consumer reads it exactly like any other one-off — only its DERIVATION differs.
|
|
765
|
+
* ONE key, not the pre-1.26.0 responsive pair: the AppShell header is a single 48px row at every
|
|
766
|
+
* viewport now (law C14, `docs/CONTROLS-SPEC.md` §2.1), so there is no second header to clear. */
|
|
734
767
|
step: { [K in keyof typeof SPACE_STEP_BASE]: number } & {
|
|
735
768
|
stickyHeaderClearance: number
|
|
736
|
-
stickyHeaderClearanceMobile: number
|
|
737
769
|
}
|
|
738
770
|
/** NavLink row line-height (`theme/index.ts`'s row-geometry styles) — additive, not
|
|
739
771
|
* multiplicative; see {@link deriveSpacing}'s doc for why. */
|
|
@@ -805,8 +837,8 @@ const ROW_LINE_HEIGHT_STEP = 1 / 30
|
|
|
805
837
|
* from a single integer `level` (`docs/DESIGN-SPEC.md` §4-adjacent) — the spacing analog of
|
|
806
838
|
* {@link deriveRadius}. `level: 0` reproduces the shipped identity exactly (locked by
|
|
807
839
|
* `theme/spacing.test.ts` and `tokens/density.test.ts`) for every value EXCEPT
|
|
808
|
-
* `step.stickyHeaderClearance
|
|
809
|
-
*
|
|
840
|
+
* `step.stickyHeaderClearance` — see the third bullet below for why that one is a deliberate
|
|
841
|
+
* exception.
|
|
810
842
|
*
|
|
811
843
|
* The law: a MULTIPLIER, `1 + 0.1 * level`, applied to every `SPACE_ANCHORS_BASE`/
|
|
812
844
|
* `SPACE_SCALE_BASE`/`SPACE_STEP_BASE` constant, then rounded (see {@link scaleSpace}) — a
|
|
@@ -831,30 +863,24 @@ const ROW_LINE_HEIGHT_STEP = 1 / 30
|
|
|
831
863
|
* (and, transitively, the collapsed rail's `ActionIcon` variant, which reads this same resolved
|
|
832
864
|
* value) below the accessible floor (22px at level -3) while also crowding out its fixed-size icon
|
|
833
865
|
* and `Kbd` chip.
|
|
834
|
-
* - `step.stickyHeaderClearance`
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
* token is held to), mobile `96 + 12 = 108` (unchanged from the pre-split single-value law).
|
|
853
|
-
* **Consumer coupling (accepted, not hidden):** both values assume `BasaltShell`'s own AppShell
|
|
854
|
-
* header heights — a `./content` consumer NOT rendering inside `BasaltShell` (e.g. `Prose`/
|
|
855
|
-
* `ArticleLayout` standalone) gets clearance numbers tuned for a header it may not have, and must
|
|
856
|
-
* override `--vx-space-sticky-header-clearance`/`--vx-space-sticky-header-clearance-mobile`
|
|
857
|
-
* itself. Documented on the `./content` surface too (`docs/CONTENT-SPEC.md` §5), not only here.
|
|
866
|
+
* - `step.stickyHeaderClearance` is not scaled off its own base AT ALL — `SPACE_STEP_BASE` has no
|
|
867
|
+
* such key any more (see that table's doc). It is computed AFTER the rest of `step`, as
|
|
868
|
+
* `step.appShellHeaderHeight + anchors.stackMd`. An independent literal can only be tuned against
|
|
869
|
+
* ONE header height and then drifts from it: the original 84 was tuned against a 48px desktop
|
|
870
|
+
* header and under-cleared the pre-1.26.0 96px mobile one by 12px at level 0, before density
|
|
871
|
+
* entered the picture at all. Deriving it removes that whole failure mode — and since 1.26.0 the
|
|
872
|
+
* AppShell header is ONE 48px row at every viewport (law C14), so the `stickyHeaderClearanceMobile`
|
|
873
|
+
* half of the old responsive pair is deleted rather than re-derived. The `+ anchors.stackMd`
|
|
874
|
+
* breathing room (not a bare `+ 0`) keeps a scrolled-to heading off the header's bottom edge, and
|
|
875
|
+
* tracks density like the header height it is added to, so the margin never freezes at one notch.
|
|
876
|
+
* Level 0: `48 + 12 = 60` (a deliberate BREAK from the pre-derivation 84 — see
|
|
877
|
+
* `theme/spacing.test.ts` for why `stickyHeaderClearance` alone is exempt from the byte-identity
|
|
878
|
+
* gate every other spacing token is held to).
|
|
879
|
+
* **Consumer coupling (accepted, not hidden):** the value assumes `BasaltShell`'s own AppShell
|
|
880
|
+
* header height — a `./content` consumer NOT rendering inside `BasaltShell` (e.g. `Prose`/
|
|
881
|
+
* `ArticleLayout` standalone) gets a clearance number tuned for a header it may not have, and must
|
|
882
|
+
* override `--vx-space-sticky-header-clearance` itself. Documented on the `./content` surface too
|
|
883
|
+
* (`docs/CONTENT-SPEC.md` §5), not only here.
|
|
858
884
|
*
|
|
859
885
|
* `rowLineHeight` is the ONE exception to the multiplier entirely: `1.35 + ROW_LINE_HEIGHT_STEP *
|
|
860
886
|
* level`, ADDITIVE, its own gentler, INDEPENDENT coefficient (see {@link ROW_LINE_HEIGHT_STEP}'s own
|
|
@@ -939,6 +965,13 @@ export function deriveSpacing(level: number): SpaceValues {
|
|
|
939
965
|
anchors.stackMd = stackUnit * 3
|
|
940
966
|
anchors.stackLg = stackUnit * 4
|
|
941
967
|
anchors.stackXl = stackUnit * 6
|
|
968
|
+
// Control-tier floors (`docs/CONTROLS-SPEC.md` §5, C15) — same "negative level can't collapse a
|
|
969
|
+
// real control below its accessible/legible minimum" guardrail as `sidebarSearchTriggerHeight`'s
|
|
970
|
+
// WCAG 2.5.8 floor and the mobile-nav Apple HIG floors below.
|
|
971
|
+
anchors.controlHeightTag = Math.max(18, anchors.controlHeightTag)
|
|
972
|
+
anchors.controlHeightWidget = Math.max(22, anchors.controlHeightWidget)
|
|
973
|
+
anchors.controlHeightCtl = Math.max(28, anchors.controlHeightCtl)
|
|
974
|
+
anchors.touchControlHeight = Math.max(30, anchors.touchControlHeight)
|
|
942
975
|
|
|
943
976
|
const mappedStep = mapSpaceGroup(SPACE_STEP_BASE, level, multiplier)
|
|
944
977
|
// WCAG 2.5.8 minimum interactive-target size — see this function's doc for why the trigger's
|
|
@@ -949,14 +982,15 @@ export function deriveSpacing(level: number): SpaceValues {
|
|
|
949
982
|
// sidebar search trigger's 24px floor directly above.
|
|
950
983
|
mappedStep.mobileNavBarHeight = Math.max(48, mappedStep.mobileNavBarHeight)
|
|
951
984
|
mappedStep.mobileNavRowHeight = Math.max(44, mappedStep.mobileNavRowHeight)
|
|
985
|
+
// Same floor family, same reason — `sheetRowHeight`'s own doc names it "same floor family as
|
|
986
|
+
// `mobileNavRowHeight`", but nothing enforced that below level 0 until now.
|
|
987
|
+
mappedStep.sheetRowHeight = Math.max(44, mappedStep.sheetRowHeight)
|
|
952
988
|
const step: SpaceValues['step'] = {
|
|
953
989
|
...mappedStep,
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
// from the level-0 byte-identity gate.
|
|
990
|
+
// DERIVED from the header it clears, plus density-tracking breathing room — see this function's
|
|
991
|
+
// doc (third bullet) for the full rationale, and `theme/spacing.test.ts` for why
|
|
992
|
+
// `stickyHeaderClearance` alone is exempt from the level-0 byte-identity gate.
|
|
958
993
|
stickyHeaderClearance: mappedStep.appShellHeaderHeight + anchors.stackMd,
|
|
959
|
-
stickyHeaderClearanceMobile: mappedStep.appShellHeaderMobileHeight + anchors.stackMd,
|
|
960
994
|
}
|
|
961
995
|
|
|
962
996
|
return {
|
|
@@ -989,9 +1023,9 @@ export const SPACE_SCALE = DEFAULT_SPACE_VALUES.scale
|
|
|
989
1023
|
|
|
990
1024
|
/** Density-tracking one-offs at the shipped identity — see `SPACE_STEP_BASE`'s doc. `deriveSpacing(0)`'s
|
|
991
1025
|
* `step`, UNCHANGED from the shipped identity (locked by `theme/spacing.test.ts`) EXCEPT
|
|
992
|
-
* `stickyHeaderClearance` (originally 84, then a single derived 108, now
|
|
993
|
-
*
|
|
994
|
-
*
|
|
1026
|
+
* `stickyHeaderClearance` (originally 84, then a single derived 108, now 60 — derived from the one
|
|
1027
|
+
* 48px AppShell header) — the deliberate level-0 break in the density work; see `deriveSpacing`'s
|
|
1028
|
+
* doc (third bullet) for why. */
|
|
995
1029
|
export const SPACE_STEP = DEFAULT_SPACE_VALUES.step
|
|
996
1030
|
|
|
997
1031
|
/** NavLink row line-height at the shipped identity (`1.35`) — `deriveSpacing(0)`'s `rowLineHeight`,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DeltaBadge` — sign-driven tone/glyph, `withGlyph`, custom `format`, and `period` (docs/
|
|
3
|
+
* DESIGN-SPEC.md §5). Mantine-free, so no `MantineProvider` wrapper is needed.
|
|
4
|
+
*
|
|
5
|
+
* CSS-module class hashes are unavailable under `bun test` (see `app-mobile-nav.test.tsx`'s doc) —
|
|
6
|
+
* every query below goes through text content, never `classes.*`.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/react'
|
|
9
|
+
import { describe, expect, test } from 'bun:test'
|
|
10
|
+
import { DeltaBadge } from './delta-badge'
|
|
11
|
+
|
|
12
|
+
describe('sign drives tone and glyph', () => {
|
|
13
|
+
test('positive renders the up glyph and the formatted magnitude', () => {
|
|
14
|
+
render(<DeltaBadge value={12.4} />)
|
|
15
|
+
expect(screen.getByText('▲')).toBeDefined()
|
|
16
|
+
expect(screen.getByText('12.4%')).toBeDefined()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('negative renders the down glyph and the formatted magnitude', () => {
|
|
20
|
+
render(<DeltaBadge value={-3.1} />)
|
|
21
|
+
expect(screen.getByText('▼')).toBeDefined()
|
|
22
|
+
expect(screen.getByText('3.1%')).toBeDefined()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('zero renders the formatted magnitude with no glyph', () => {
|
|
26
|
+
render(<DeltaBadge value={0} />)
|
|
27
|
+
expect(screen.getByText('0.0%')).toBeDefined()
|
|
28
|
+
expect(screen.queryByText('▲')).toBeNull()
|
|
29
|
+
expect(screen.queryByText('▼')).toBeNull()
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('withGlyph={false} suppresses the glyph even for a signed value', () => {
|
|
34
|
+
render(<DeltaBadge value={12.4} withGlyph={false} />)
|
|
35
|
+
expect(screen.getByText('12.4%')).toBeDefined()
|
|
36
|
+
expect(screen.queryByText('▲')).toBeNull()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('a zero value never shows a glyph, even with withGlyph={true}', () => {
|
|
40
|
+
render(<DeltaBadge value={0} withGlyph={true} />)
|
|
41
|
+
expect(screen.queryByText('▲')).toBeNull()
|
|
42
|
+
expect(screen.queryByText('▼')).toBeNull()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('a custom format overrides the default percentage label', () => {
|
|
46
|
+
render(<DeltaBadge value={182} format={(v) => `${Math.abs(v)}ms`} withGlyph={false} />)
|
|
47
|
+
expect(screen.getByText('182ms')).toBeDefined()
|
|
48
|
+
expect(screen.queryByText('182.0%')).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('period renders after the value, in the same badge', () => {
|
|
52
|
+
render(<DeltaBadge value={4.2} period="MoM" />)
|
|
53
|
+
expect(screen.getByText('4.2%')).toBeDefined()
|
|
54
|
+
expect(screen.getByText('MoM')).toBeDefined()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('omitting period renders no extra text', () => {
|
|
58
|
+
render(<DeltaBadge value={4.2} />)
|
|
59
|
+
expect(screen.queryByText('MoM')).toBeNull()
|
|
60
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeltaBadge — the KPI trend chip (docs/DESIGN-SPEC.md §5): mono 11.5px weight 600, status-color
|
|
3
|
+
* text on a status-13% tint, control radius, 2px/7px padding, optional ▲/▼ glyph at 9px. Sign
|
|
4
|
+
* alone drives the tone — positive reads as success, negative as danger, zero as a neutral faint
|
|
5
|
+
* tint with no glyph. The label never carries a leading "+"; pass `format` to control the
|
|
6
|
+
* magnitude string (defaults to a one-decimal percentage of the absolute value).
|
|
7
|
+
*
|
|
8
|
+
* Mantine-free (plain `<span>` + this folder's CSS module, docs/CONTROLS-SPEC.md §2.2) so
|
|
9
|
+
* `WidgetHeader` can compose it from inside `charts/` without tripping `basalt/token-layer-
|
|
10
|
+
* boundary` or `check-dist-layering.mjs`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { DeltaBadge } from 'basalt-ui'
|
|
14
|
+
*
|
|
15
|
+
* <DeltaBadge value={12.4} /> // ▲ 12.4%, success tint
|
|
16
|
+
* <DeltaBadge value={-3.1} /> // ▼ 3.1%, danger tint
|
|
17
|
+
* <DeltaBadge value={0} /> // 0.0%, neutral faint tint, no glyph
|
|
18
|
+
* <DeltaBadge value={182} format={(v) => `${Math.abs(v)}ms`} withGlyph={false} />
|
|
19
|
+
*/
|
|
20
|
+
import { alpha, VX } from '../tokens'
|
|
21
|
+
import classes from './widget-header.module.css'
|
|
22
|
+
|
|
23
|
+
export type DeltaBadgeProps = {
|
|
24
|
+
/** Signed delta driving tone (positive/negative/zero) and, by default, the label magnitude. */
|
|
25
|
+
value: number
|
|
26
|
+
/** Formats the value into the label text. Defaults to `${Math.abs(value).toFixed(1)}%`. */
|
|
27
|
+
format?: (value: number) => string
|
|
28
|
+
/** Render the ▲/▼ direction glyph. Defaults to `true`; a zero value never shows a glyph. */
|
|
29
|
+
withGlyph?: boolean
|
|
30
|
+
/** Optional comparison timeframe rendered directly after the value in a dimmer shade of the same
|
|
31
|
+
* tone (e.g. `MoM`, `WoW`, `YTD`) — states what the delta is measured against without a hover. */
|
|
32
|
+
period?: string | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultFormat = (value: number): string => `${Math.abs(value).toFixed(1)}%`
|
|
36
|
+
|
|
37
|
+
export function DeltaBadge({
|
|
38
|
+
value,
|
|
39
|
+
format = defaultFormat,
|
|
40
|
+
withGlyph = true,
|
|
41
|
+
period,
|
|
42
|
+
}: DeltaBadgeProps) {
|
|
43
|
+
const tone = value > 0 ? VX.status.good : value < 0 ? VX.status.bad : VX.faint
|
|
44
|
+
const glyph = value > 0 ? '▲' : value < 0 ? '▼' : undefined
|
|
45
|
+
const showGlyph = withGlyph && glyph !== undefined
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<span
|
|
49
|
+
className={classes.deltaBadge}
|
|
50
|
+
style={{ backgroundColor: alpha(tone, 0.13), color: tone }}
|
|
51
|
+
>
|
|
52
|
+
{showGlyph && <span className={classes.deltaGlyph}>{glyph}</span>}
|
|
53
|
+
{format(value)}
|
|
54
|
+
{period !== undefined && <span className={classes.deltaPeriod}>{period}</span>}
|
|
55
|
+
</span>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/widget-header/**` must stay Mantine-free (docs/CONTROLS-SPEC.md §2.2) — so `ChartCard`
|
|
3
|
+
* (inside the `charts/` boundary) can compose `WidgetHeader`/`DeltaBadge` without tripping
|
|
4
|
+
* `basalt/token-layer-boundary` or `check-dist-layering.mjs`. Source-level regex check, same idiom
|
|
5
|
+
* as `styles.floor.test.ts`'s direct read of shipped text — independent of oxlint, which only runs
|
|
6
|
+
* over changed files locally and could be skipped.
|
|
7
|
+
*
|
|
8
|
+
* The pattern matches an actual import/re-export specifier, not any mention of "@mantine" in
|
|
9
|
+
* prose — this module's own doc comments say "Mantine-free" and name `@mantine/*` on purpose.
|
|
10
|
+
*/
|
|
11
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
12
|
+
import { dirname, join } from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
import { describe, expect, test } from 'bun:test'
|
|
15
|
+
|
|
16
|
+
const WIDGET_HEADER_DIR = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
const MANTINE_IMPORT = /(?:from|import)\s*\(?\s*['"]@mantine\//
|
|
18
|
+
|
|
19
|
+
const sourceFiles = readdirSync(WIDGET_HEADER_DIR).filter(
|
|
20
|
+
(name) =>
|
|
21
|
+
(name.endsWith('.ts') || name.endsWith('.tsx')) &&
|
|
22
|
+
!name.endsWith('.test.ts') &&
|
|
23
|
+
!name.endsWith('.test.tsx'),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
describe('src/widget-header — Mantine-free', () => {
|
|
27
|
+
for (const name of sourceFiles) {
|
|
28
|
+
test(`${name} imports no '@mantine/*' module`, () => {
|
|
29
|
+
const text = readFileSync(join(WIDGET_HEADER_DIR, name), 'utf8')
|
|
30
|
+
expect(text).not.toMatch(MANTINE_IMPORT)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
})
|