basalt-ui 1.25.0 → 1.27.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 +26 -0
- package/MIGRATING.md +815 -23
- package/README.md +45 -32
- package/agent/rules/basalt-batteries.md +205 -0
- package/agent/rules/basalt-charts.md +125 -555
- package/agent/rules/basalt-controls.md +160 -0
- package/agent/rules/basalt-mantine.md +146 -439
- package/agent/rules/basalt-state.md +118 -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 +1465 -36
- package/configs/oxlint-plugin.test.ts +1190 -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 +39 -0
- package/dist/controls/enum-filter.d.ts.map +1 -0
- package/dist/controls/enum-filter.js +93 -0
- package/dist/controls/filter-context.d.ts +60 -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 +20 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +46 -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/number-filter.d.ts +26 -0
- package/dist/controls/number-filter.d.ts.map +1 -0
- package/dist/controls/number-filter.js +143 -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 +43 -0
- package/dist/controls/range-filter.d.ts.map +1 -0
- package/dist/controls/range-filter.js +135 -0
- package/dist/controls/range-filter.type-guard.test.d.ts +2 -0
- package/dist/controls/range-filter.type-guard.test.d.ts.map +1 -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/index.d.ts +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -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 +95 -9
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +108 -49
- package/dist/dashboard/stat-card.module.css +153 -0
- package/dist/dashboard/stat-card.module.css.d.ts +15 -0
- package/dist/data/data-table.d.ts +37 -15
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +96 -58
- 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 +78 -7
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +13 -3
- 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 +84 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -0
- package/dist/router-tanstack/search-store.js +271 -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 +565 -0
- package/dist/state/fields.d.ts.map +1 -0
- package/dist/state/fields.js +405 -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 +60 -0
- package/dist/state/persisted.d.ts.map +1 -0
- package/dist/state/persisted.js +123 -0
- package/dist/state/persisted.test.d.ts +2 -0
- package/dist/state/persisted.test.d.ts.map +1 -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 +156 -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 +67 -0
- package/dist/widget-header/widget-header.d.ts.map +1 -0
- package/dist/widget-header/widget-header.js +89 -0
- package/dist/widget-header/widget-header.module.css +350 -0
- package/dist/widget-header/widget-header.module.css.d.ts +25 -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 +1053 -0
- package/src/controls/enum-filter.tsx +170 -0
- package/src/controls/filter-context.tsx +158 -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 +49 -0
- package/src/controls/multi-select-filter.tsx +140 -0
- package/src/controls/number-filter.tsx +291 -0
- package/src/controls/peer-boundary.test.ts +85 -0
- package/src/controls/props-contract.test.tsx +137 -0
- package/src/controls/range-filter.tsx +237 -0
- package/src/controls/range-filter.type-guard.test.tsx +122 -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/index.ts +6 -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 +153 -0
- package/src/dashboard/stat-card.module.css.d.ts +15 -0
- package/src/dashboard/stat-card.test.tsx +298 -1
- package/src/dashboard/stat-card.tsx +289 -67
- package/src/data/data-table.test.tsx +96 -11
- package/src/data/data-table.tsx +228 -84
- package/src/guard/check-source.test.ts +159 -13
- package/src/guard/grace.test.ts +158 -0
- package/src/guard/index.ts +185 -29
- package/src/guard/types.ts +4 -2
- package/src/index.ts +51 -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 +35 -0
- package/src/router-tanstack/index.ts +28 -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 +882 -0
- package/src/router-tanstack/search-store.test.ts +578 -0
- package/src/router-tanstack/search-store.ts +546 -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 +512 -0
- package/src/state/fields.ts +1150 -0
- package/src/state/persisted.test.ts +91 -0
- package/src/state/persisted.ts +261 -0
- package/src/state.ts +52 -240
- package/src/styles.css +13 -0
- package/src/surfaces.ts +204 -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 +350 -0
- package/src/widget-header/widget-header.module.css.d.ts +25 -0
- package/src/widget-header/widget-header.test.tsx +187 -0
- package/src/widget-header/widget-header.tsx +213 -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 -329
- 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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Drawer } from "@mantine/core";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import classes from "./controls.module.css";
|
|
5
|
+
function FilterSheet({
|
|
6
|
+
opened,
|
|
7
|
+
onClose,
|
|
8
|
+
onResetAll,
|
|
9
|
+
title = "Filters",
|
|
10
|
+
children
|
|
11
|
+
}) {
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
Drawer,
|
|
14
|
+
{
|
|
15
|
+
opened,
|
|
16
|
+
onClose,
|
|
17
|
+
position: "bottom",
|
|
18
|
+
radius: "sm",
|
|
19
|
+
classNames: { header: classes.sheetHeader, title: classes.sheetTitle },
|
|
20
|
+
title: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21
|
+
/* @__PURE__ */ jsx("span", { className: classes.sheetTitleText, children: title }),
|
|
22
|
+
/* @__PURE__ */ jsx(Button, { variant: "subtle", size: "ctl", onClick: onResetAll, children: "Reset all" })
|
|
23
|
+
] }),
|
|
24
|
+
children: /* @__PURE__ */ jsx("div", { className: classes.sheetBody, children })
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function CheckGlyph() {
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
"svg",
|
|
31
|
+
{
|
|
32
|
+
width: 16,
|
|
33
|
+
height: 16,
|
|
34
|
+
viewBox: "0 0 24 24",
|
|
35
|
+
fill: "none",
|
|
36
|
+
stroke: "currentColor",
|
|
37
|
+
strokeWidth: 2.25,
|
|
38
|
+
strokeLinecap: "round",
|
|
39
|
+
strokeLinejoin: "round",
|
|
40
|
+
"aria-hidden": true,
|
|
41
|
+
children: /* @__PURE__ */ jsx("path", { d: "M5 12.5l5 5l9 -11" })
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function SheetOptionList({
|
|
46
|
+
mode,
|
|
47
|
+
selected,
|
|
48
|
+
options,
|
|
49
|
+
onToggle,
|
|
50
|
+
labelId
|
|
51
|
+
}) {
|
|
52
|
+
const name = useId();
|
|
53
|
+
return (
|
|
54
|
+
// A real `<fieldset>`, not `role="group"` on a div — the native element carries the grouping
|
|
55
|
+
// semantics and gets its own box reset in the module (`.sheetList`). `aria-labelledby` points at
|
|
56
|
+
// the `SheetField` heading rather than a `<legend>`: the heading is already visible above the
|
|
57
|
+
// list, and a legend would render it a second time.
|
|
58
|
+
/* @__PURE__ */ jsx("fieldset", { className: classes.sheetList, "aria-labelledby": labelId, children: options.map((option) => {
|
|
59
|
+
const isSelected = selected.includes(option.value);
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"label",
|
|
62
|
+
{
|
|
63
|
+
className: classes.sheetOption,
|
|
64
|
+
...isSelected && { "data-selected": true },
|
|
65
|
+
...option.disabled === true && { "data-disabled": true },
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
"input",
|
|
69
|
+
{
|
|
70
|
+
className: classes.sheetOptionInput,
|
|
71
|
+
type: mode === "single" ? "radio" : "checkbox",
|
|
72
|
+
name,
|
|
73
|
+
value: option.value,
|
|
74
|
+
checked: isSelected,
|
|
75
|
+
disabled: option.disabled === true,
|
|
76
|
+
"aria-label": option.label,
|
|
77
|
+
onChange: () => {
|
|
78
|
+
onToggle(option.value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: classes.sheetOptionLabel, children: option.label }),
|
|
83
|
+
isSelected && /* @__PURE__ */ jsx("span", { className: classes.sheetOptionCheck, "aria-hidden": true, children: /* @__PURE__ */ jsx(CheckGlyph, {}) })
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
option.value
|
|
87
|
+
);
|
|
88
|
+
}) })
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function SheetDisclosure({
|
|
92
|
+
label,
|
|
93
|
+
expanded,
|
|
94
|
+
onToggle,
|
|
95
|
+
children
|
|
96
|
+
}) {
|
|
97
|
+
return /* @__PURE__ */ jsxs("div", { className: classes.sheetList, children: [
|
|
98
|
+
/* @__PURE__ */ jsxs(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
className: classes.sheetOption,
|
|
103
|
+
"aria-expanded": expanded,
|
|
104
|
+
...expanded && { "data-selected": true },
|
|
105
|
+
onClick: onToggle,
|
|
106
|
+
children: [
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: classes.sheetOptionLabel, children: label }),
|
|
108
|
+
expanded && /* @__PURE__ */ jsx("span", { className: classes.sheetOptionCheck, "aria-hidden": true, children: /* @__PURE__ */ jsx(CheckGlyph, {}) })
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
expanded && /* @__PURE__ */ jsx("div", { className: classes.sheetDisclosureBody, children })
|
|
113
|
+
] });
|
|
114
|
+
}
|
|
115
|
+
function SheetField({ label, labelId, children }) {
|
|
116
|
+
return /* @__PURE__ */ jsxs("div", { className: classes.sheetField, children: [
|
|
117
|
+
/* @__PURE__ */ jsx("span", { className: classes.sheetLabel, ...labelId !== void 0 && { id: labelId }, children: label }),
|
|
118
|
+
children
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
function useControlName(label, inSheet) {
|
|
122
|
+
const labelId = useId();
|
|
123
|
+
return {
|
|
124
|
+
labelId,
|
|
125
|
+
nameProps: inSheet ? { "aria-labelledby": labelId } : { "aria-label": label }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function SheetRow({ children }) {
|
|
129
|
+
return /* @__PURE__ */ jsx("div", { className: classes.sheetRow, children });
|
|
130
|
+
}
|
|
131
|
+
const sheetRowClass = classes.sheetRow;
|
|
132
|
+
const sheetRowClassNames = {
|
|
133
|
+
root: classes.sheetRow,
|
|
134
|
+
body: classes.sheetRowBody,
|
|
135
|
+
label: classes.sheetRowLabel
|
|
136
|
+
};
|
|
137
|
+
export {
|
|
138
|
+
FilterSheet,
|
|
139
|
+
SheetDisclosure,
|
|
140
|
+
SheetField,
|
|
141
|
+
SheetOptionList,
|
|
142
|
+
SheetRow,
|
|
143
|
+
sheetRowClass,
|
|
144
|
+
sheetRowClassNames,
|
|
145
|
+
useControlName
|
|
146
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control tier's own inline glyphs (`docs/CONTROLS-SPEC.md` §3).
|
|
3
|
+
*
|
|
4
|
+
* basalt ships NO icon dependency — every `icon` slot in the public API takes a `ReactNode` the
|
|
5
|
+
* consumer fills. These are the few glyphs the FRAMEWORK draws for itself, where a default icon is
|
|
6
|
+
* part of the control's identity rather than a caller's decision: a range filter reads as a date
|
|
7
|
+
* picker because it carries a calendar, a compare filter because it carries the swap arrows, and
|
|
8
|
+
* the mobile `Filters (n)` pill because it carries a funnel. Same convention the shell already uses
|
|
9
|
+
* for its own chrome (`shell/sidebar-search.tsx`, `shell/app-sidebar.tsx`): a 24×24 `viewBox`,
|
|
10
|
+
* `stroke="currentColor"`, sized down by the `size` prop.
|
|
11
|
+
*
|
|
12
|
+
* INTERNAL — not on the `./controls` barrel. A consumer wanting a different glyph passes `icon`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ReactNode } from 'react';
|
|
15
|
+
/** `RangeFilter`'s default leading icon. */
|
|
16
|
+
export declare function CalendarGlyph(): ReactNode;
|
|
17
|
+
/** `CompareFilter`'s default leading icon — the arrows-swap glyph. */
|
|
18
|
+
export declare function SwapGlyph(): ReactNode;
|
|
19
|
+
/** The mobile `Filters (n)` pill's leading icon. */
|
|
20
|
+
export declare function FunnelGlyph(): ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Every pill's trailing affordance — 14px, muted, and an SVG rather than the `⇅` character it
|
|
23
|
+
* replaced. A text glyph rendered at the label's own font metrics, so it sat off the optical centre
|
|
24
|
+
* and shifted with the font stack; the path is centred in its own box.
|
|
25
|
+
*/
|
|
26
|
+
export declare function UpDownGlyph(): ReactNode;
|
|
27
|
+
//# sourceMappingURL=glyphs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../../src/controls/glyphs.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAwBtC,4CAA4C;AAC5C,wBAAgB,aAAa,IAAI,SAAS,CASzC;AAED,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,SAAS,CAOrC;AAED,oDAAoD;AACpD,wBAAgB,WAAW,IAAI,SAAS,CAMvC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,CAOvC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
const ICON_SIZE = 16;
|
|
3
|
+
const AFFORDANCE_SIZE = 14;
|
|
4
|
+
function Stroke({ size = ICON_SIZE, children }) {
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"svg",
|
|
7
|
+
{
|
|
8
|
+
width: size,
|
|
9
|
+
height: size,
|
|
10
|
+
viewBox: "0 0 24 24",
|
|
11
|
+
fill: "none",
|
|
12
|
+
stroke: "currentColor",
|
|
13
|
+
strokeWidth: 1.75,
|
|
14
|
+
strokeLinecap: "round",
|
|
15
|
+
strokeLinejoin: "round",
|
|
16
|
+
"aria-hidden": true,
|
|
17
|
+
children
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function CalendarGlyph() {
|
|
22
|
+
return /* @__PURE__ */ jsxs(Stroke, { children: [
|
|
23
|
+
/* @__PURE__ */ jsx("rect", { x: 3, y: 5, width: 18, height: 16, rx: 2.5 }),
|
|
24
|
+
/* @__PURE__ */ jsx("path", { d: "M3 10h18" }),
|
|
25
|
+
/* @__PURE__ */ jsx("path", { d: "M8 3v4" }),
|
|
26
|
+
/* @__PURE__ */ jsx("path", { d: "M16 3v4" })
|
|
27
|
+
] });
|
|
28
|
+
}
|
|
29
|
+
function SwapGlyph() {
|
|
30
|
+
return /* @__PURE__ */ jsxs(Stroke, { children: [
|
|
31
|
+
/* @__PURE__ */ jsx("path", { d: "M4 8h13l-3.5 -3.5" }),
|
|
32
|
+
/* @__PURE__ */ jsx("path", { d: "M20 16h-13l3.5 3.5" })
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
35
|
+
function FunnelGlyph() {
|
|
36
|
+
return /* @__PURE__ */ jsx(Stroke, { children: /* @__PURE__ */ jsx("path", { d: "M4 4h16l-6.5 8v7l-3 -1.5v-5.5z" }) });
|
|
37
|
+
}
|
|
38
|
+
function UpDownGlyph() {
|
|
39
|
+
return /* @__PURE__ */ jsxs(Stroke, { size: AFFORDANCE_SIZE, children: [
|
|
40
|
+
/* @__PURE__ */ jsx("path", { d: "M8 9l4 -4l4 4" }),
|
|
41
|
+
/* @__PURE__ */ jsx("path", { d: "M8 15l4 4l4 -4" })
|
|
42
|
+
] });
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
CalendarGlyph,
|
|
46
|
+
FunnelGlyph,
|
|
47
|
+
SwapGlyph,
|
|
48
|
+
UpDownGlyph
|
|
49
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `basalt-ui/controls` — the Mantine-coupled control tier (`docs/CONTROLS-SPEC.md` §3).
|
|
3
|
+
*
|
|
4
|
+
* Every control here renders at the `ctl` tier from the home that hosts it and owns its own
|
|
5
|
+
* desktop/mobile swap in CSS (law C9). Actions and sync land in wave 3; the filter/tab family
|
|
6
|
+
* follows in wave 4.
|
|
7
|
+
*/
|
|
8
|
+
export { ActionGroup, OverflowMenu, DESKTOP_SECONDARY_MAX, MOBILE_GLOBAL_BAR_MAX, barActionMobile, globalActionMobile, type BarAction, type BarActionItem, type BarActionMenu, type BarActionCustom, type ActionGroupProps, type GlobalAction, type OverflowMenuProps, } from './actions.js';
|
|
9
|
+
export { SyncButton, formatAge, AGE_REFRESH_MS, type SyncButtonProps } from './sync-button.js';
|
|
10
|
+
export { ControlGroup, type ControlGroupProps } from './control-group.js';
|
|
11
|
+
export { FilterSet, type FilterSetProps } from './filter-set.js';
|
|
12
|
+
export { RangeFilter, type RangeCustomPickerProps, type RangeFilterProps } from './range-filter.js';
|
|
13
|
+
export { CompareFilter, COMPARE_VALUES, type CompareFilterProps, type CompareValue, } from './compare-filter.js';
|
|
14
|
+
export { SelectFilter, type FilterOption, type SelectFilterEnumProps, type SelectFilterProps, type SelectFilterStringProps, } from './select-filter.js';
|
|
15
|
+
export { MultiSelectFilter, type MultiSelectFilterProps } from './multi-select-filter.js';
|
|
16
|
+
export { NumberFilter, type NumberFilterOption, type NumberFilterProps } from './number-filter.js';
|
|
17
|
+
export { SearchFilter, type SearchFilterProps } from './search-filter.js';
|
|
18
|
+
export { ToggleFilter, type ToggleFilterProps } from './toggle-filter.js';
|
|
19
|
+
export { ViewTabs, type ViewTabsOption, type ViewTabsProps } from './view-tabs.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controls/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAMtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAChG,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACtF,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC/F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionGroup,
|
|
3
|
+
OverflowMenu,
|
|
4
|
+
DESKTOP_SECONDARY_MAX,
|
|
5
|
+
MOBILE_GLOBAL_BAR_MAX,
|
|
6
|
+
barActionMobile,
|
|
7
|
+
globalActionMobile
|
|
8
|
+
} from "./actions.js";
|
|
9
|
+
import { SyncButton, formatAge, AGE_REFRESH_MS } from "./sync-button.js";
|
|
10
|
+
import { ControlGroup } from "./control-group.js";
|
|
11
|
+
import { FilterSet } from "./filter-set.js";
|
|
12
|
+
import { RangeFilter } from "./range-filter.js";
|
|
13
|
+
import {
|
|
14
|
+
CompareFilter,
|
|
15
|
+
COMPARE_VALUES
|
|
16
|
+
} from "./compare-filter.js";
|
|
17
|
+
import {
|
|
18
|
+
SelectFilter
|
|
19
|
+
} from "./select-filter.js";
|
|
20
|
+
import { MultiSelectFilter } from "./multi-select-filter.js";
|
|
21
|
+
import { NumberFilter } from "./number-filter.js";
|
|
22
|
+
import { SearchFilter } from "./search-filter.js";
|
|
23
|
+
import { ToggleFilter } from "./toggle-filter.js";
|
|
24
|
+
import { ViewTabs } from "./view-tabs.js";
|
|
25
|
+
export {
|
|
26
|
+
AGE_REFRESH_MS,
|
|
27
|
+
ActionGroup,
|
|
28
|
+
COMPARE_VALUES,
|
|
29
|
+
CompareFilter,
|
|
30
|
+
ControlGroup,
|
|
31
|
+
DESKTOP_SECONDARY_MAX,
|
|
32
|
+
FilterSet,
|
|
33
|
+
MOBILE_GLOBAL_BAR_MAX,
|
|
34
|
+
MultiSelectFilter,
|
|
35
|
+
NumberFilter,
|
|
36
|
+
OverflowMenu,
|
|
37
|
+
RangeFilter,
|
|
38
|
+
SearchFilter,
|
|
39
|
+
SelectFilter,
|
|
40
|
+
SyncButton,
|
|
41
|
+
ToggleFilter,
|
|
42
|
+
ViewTabs,
|
|
43
|
+
barActionMobile,
|
|
44
|
+
formatAge,
|
|
45
|
+
globalActionMobile
|
|
46
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FieldHandle, MultiField } from '../state.js';
|
|
3
|
+
import type { FilterOption } from './select-filter.js';
|
|
4
|
+
export type MultiSelectFilterProps<T extends string> = {
|
|
5
|
+
readonly field: FieldHandle<MultiField<T>>;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly icon?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The plural noun in the `3 channels` count.
|
|
10
|
+
*
|
|
11
|
+
* @default the label, lowercased
|
|
12
|
+
*/
|
|
13
|
+
readonly noun?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Overrides `field.options` at render — a runtime catalogue whose labels carry live data
|
|
16
|
+
* (`web · 1.2k`). The VALUES still belong to the field: a multi field is a closed set, so this
|
|
17
|
+
* relabels and reorders rows, it does not open new ones. Unlike `SelectFilter` there is no
|
|
18
|
+
* string-field shape to make it required.
|
|
19
|
+
*/
|
|
20
|
+
readonly options?: readonly FilterOption[];
|
|
21
|
+
};
|
|
22
|
+
export declare function MultiSelectFilter<T extends string>({ field, label, icon, noun, options: optionsProp, }: MultiSelectFilterProps<T>): ReactNode;
|
|
23
|
+
//# sourceMappingURL=multi-select-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-select-filter.d.ts","sourceRoot":"","sources":["../../src/controls/multi-select-filter.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAMnD,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,MAAM,IAAI;IACrD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;CAC3C,CAAA;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAClD,KAAK,EACL,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,OAAO,EAAE,WAAW,GACrB,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,SAAS,CAsFvC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Checkbox, Stack } from "@mantine/core";
|
|
3
|
+
import classes from "./controls.module.css";
|
|
4
|
+
import { useFilterRegistration, useFilterSurface } from "./filter-context.js";
|
|
5
|
+
import { FilterPill } from "./filter-pill.js";
|
|
6
|
+
import { SheetField, SheetOptionList, useControlName } from "./filter-sheet.js";
|
|
7
|
+
function MultiSelectFilter({
|
|
8
|
+
field,
|
|
9
|
+
label,
|
|
10
|
+
icon,
|
|
11
|
+
noun,
|
|
12
|
+
options: optionsProp
|
|
13
|
+
}) {
|
|
14
|
+
const [value, setValue] = field.use();
|
|
15
|
+
const surface = useFilterSurface();
|
|
16
|
+
const isDefault = field.isDefault(value);
|
|
17
|
+
useFilterRegistration(!isDefault, () => {
|
|
18
|
+
field.clear();
|
|
19
|
+
});
|
|
20
|
+
const options = optionsProp ?? field.options;
|
|
21
|
+
const inSheet = surface === "sheet";
|
|
22
|
+
const carriesInformation = value.length > 0 && value.length < options.length;
|
|
23
|
+
const { labelId } = useControlName(label, inSheet);
|
|
24
|
+
if (inSheet) {
|
|
25
|
+
return /* @__PURE__ */ jsx(SheetField, { label, labelId, children: /* @__PURE__ */ jsx(
|
|
26
|
+
SheetOptionList,
|
|
27
|
+
{
|
|
28
|
+
mode: "multi",
|
|
29
|
+
labelId,
|
|
30
|
+
selected: value,
|
|
31
|
+
options,
|
|
32
|
+
onToggle: (next) => {
|
|
33
|
+
const has = value.includes(next);
|
|
34
|
+
setValue(has ? value.filter((v) => v !== next) : [...value, next]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
) });
|
|
38
|
+
}
|
|
39
|
+
const body = (
|
|
40
|
+
// Mantine's own `label`, for the same reason `enum-filter.tsx` uses it — `Checkbox.Group`
|
|
41
|
+
// overwrites any `aria-label`/`aria-labelledby` with its own. Named from `label`, never from the
|
|
42
|
+
// pill text, which reads `3 channels` and describes the selection rather than the filter.
|
|
43
|
+
/* @__PURE__ */ jsxs(
|
|
44
|
+
Checkbox.Group,
|
|
45
|
+
{
|
|
46
|
+
label,
|
|
47
|
+
classNames: { label: classes.groupLabel },
|
|
48
|
+
value: [...value],
|
|
49
|
+
onChange: (next) => {
|
|
50
|
+
setValue(next);
|
|
51
|
+
},
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Stack, { gap: 2, children: options.map((option) => /* @__PURE__ */ jsx(
|
|
54
|
+
Checkbox,
|
|
55
|
+
{
|
|
56
|
+
value: option.value,
|
|
57
|
+
label: option.label,
|
|
58
|
+
...option.disabled === true && { disabled: true }
|
|
59
|
+
},
|
|
60
|
+
option.value
|
|
61
|
+
)) }),
|
|
62
|
+
!isDefault && /* @__PURE__ */ jsx(
|
|
63
|
+
Button,
|
|
64
|
+
{
|
|
65
|
+
variant: "subtle",
|
|
66
|
+
size: "ctl",
|
|
67
|
+
onClick: () => {
|
|
68
|
+
field.clear();
|
|
69
|
+
},
|
|
70
|
+
children: "Clear"
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
return /* @__PURE__ */ jsx(
|
|
78
|
+
FilterPill,
|
|
79
|
+
{
|
|
80
|
+
label: carriesInformation ? `${value.length} ${noun ?? label.toLowerCase()}` : label,
|
|
81
|
+
active: !isDefault,
|
|
82
|
+
...icon !== void 0 && { icon },
|
|
83
|
+
children: /* @__PURE__ */ jsx("div", { className: classes.optionList, children: body })
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
MultiSelectFilter
|
|
89
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FieldHandle, NumberField } from '../state.js';
|
|
3
|
+
/** One row of a numeric preset set. Narrower than `FilterOption` — a preset is never `disabled`:
|
|
4
|
+
* the set is declared at the call site, so a row that should not be offered is left out. */
|
|
5
|
+
export type NumberFilterOption = {
|
|
6
|
+
readonly value: number;
|
|
7
|
+
readonly label: string;
|
|
8
|
+
};
|
|
9
|
+
export type NumberFilterProps = {
|
|
10
|
+
readonly field: FieldHandle<NumberField>;
|
|
11
|
+
/** The popover/sheet heading and the accessible name — never the pill text, which reads the
|
|
12
|
+
* VALUE (see `EnumFilter`'s doc for why a pill may only print what is selected). */
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly icon?: ReactNode;
|
|
15
|
+
/** Present → the radio-list form. Absent → the stepper form. */
|
|
16
|
+
readonly options?: readonly NumberFilterOption[];
|
|
17
|
+
/**
|
|
18
|
+
* The stepper's increment. Ignored in the `options` form, which has no stepper.
|
|
19
|
+
*
|
|
20
|
+
* @default 1 — and for an `int: true` field that is also the only sane grain, so it is what the
|
|
21
|
+
* field's own declaration produces without this prop.
|
|
22
|
+
*/
|
|
23
|
+
readonly step?: number;
|
|
24
|
+
};
|
|
25
|
+
export declare function NumberFilter({ field, label, icon, options, step }: NumberFilterProps): ReactNode;
|
|
26
|
+
//# sourceMappingURL=number-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-filter.d.ts","sourceRoot":"","sources":["../../src/controls/number-filter.tsx"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAQxD;6FAC6F;AAC7F,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,CAAA;IACxC;yFACqF;IACrF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAsBhG"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { NumberInput } from "@mantine/core";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import classes from "./controls.module.css";
|
|
5
|
+
import { EnumFilter } from "./enum-filter.js";
|
|
6
|
+
import { useFilterRegistration, useFilterSurface } from "./filter-context.js";
|
|
7
|
+
import { FilterPill } from "./filter-pill.js";
|
|
8
|
+
import { SheetField, useControlName } from "./filter-sheet.js";
|
|
9
|
+
function NumberFilter({ field, label, icon, options, step }) {
|
|
10
|
+
if (options !== void 0) {
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
NumberChoice,
|
|
13
|
+
{
|
|
14
|
+
field,
|
|
15
|
+
label,
|
|
16
|
+
options,
|
|
17
|
+
...icon !== void 0 && { icon }
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
NumberStepper,
|
|
23
|
+
{
|
|
24
|
+
field,
|
|
25
|
+
label,
|
|
26
|
+
...icon !== void 0 && { icon },
|
|
27
|
+
...step !== void 0 && { step }
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function NumberChoice({
|
|
32
|
+
field,
|
|
33
|
+
label,
|
|
34
|
+
icon,
|
|
35
|
+
options
|
|
36
|
+
}) {
|
|
37
|
+
const [value, setValue] = field.use();
|
|
38
|
+
const rows = options.map((option) => ({ value: String(option.value), label: option.label }));
|
|
39
|
+
const choice = {
|
|
40
|
+
fallback: String(field.fallback),
|
|
41
|
+
options: rows,
|
|
42
|
+
use: () => [
|
|
43
|
+
String(value),
|
|
44
|
+
(next) => {
|
|
45
|
+
setValue(Number(next));
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
isDefault: (v) => field.isDefault(Number(v)),
|
|
49
|
+
clear: () => {
|
|
50
|
+
field.clear();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsx(
|
|
54
|
+
EnumFilter,
|
|
55
|
+
{
|
|
56
|
+
field: choice,
|
|
57
|
+
label,
|
|
58
|
+
numeric: true,
|
|
59
|
+
options: rows,
|
|
60
|
+
...icon !== void 0 && { icon }
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function NumberStepper({
|
|
65
|
+
field,
|
|
66
|
+
label,
|
|
67
|
+
icon,
|
|
68
|
+
step
|
|
69
|
+
}) {
|
|
70
|
+
const [value, setValue] = field.use();
|
|
71
|
+
const surface = useFilterSurface();
|
|
72
|
+
const inSheet = surface === "sheet";
|
|
73
|
+
const isDefault = field.isDefault(value);
|
|
74
|
+
useFilterRegistration(!isDefault, () => {
|
|
75
|
+
field.clear();
|
|
76
|
+
});
|
|
77
|
+
const { labelId, nameProps } = useControlName(label, inSheet);
|
|
78
|
+
const draft = useCommittedNumber(value, setValue, { min: field.min, max: field.max });
|
|
79
|
+
const input = /* @__PURE__ */ jsx(
|
|
80
|
+
NumberInput,
|
|
81
|
+
{
|
|
82
|
+
...nameProps,
|
|
83
|
+
size: "ctl",
|
|
84
|
+
...field.min !== void 0 && { min: field.min },
|
|
85
|
+
...field.max !== void 0 && { max: field.max },
|
|
86
|
+
...field.int && { allowDecimal: false },
|
|
87
|
+
...step !== void 0 ? { step } : field.int && { step: 1 },
|
|
88
|
+
value: draft.value,
|
|
89
|
+
onChange: draft.set,
|
|
90
|
+
onBlur: draft.commit,
|
|
91
|
+
onKeyDown: (event) => {
|
|
92
|
+
if (event.key !== "Enter") return;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
draft.commit();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
if (inSheet) {
|
|
99
|
+
return /* @__PURE__ */ jsx(SheetField, { label, labelId, children: input });
|
|
100
|
+
}
|
|
101
|
+
return /* @__PURE__ */ jsx(
|
|
102
|
+
FilterPill,
|
|
103
|
+
{
|
|
104
|
+
label: String(value),
|
|
105
|
+
numeric: true,
|
|
106
|
+
active: !isDefault,
|
|
107
|
+
...icon !== void 0 && { icon },
|
|
108
|
+
children: /* @__PURE__ */ jsx("div", { className: classes.optionList, children: input })
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function useCommittedNumber(value, setValue, bounds) {
|
|
113
|
+
const [draft, setDraft] = useState(value);
|
|
114
|
+
const seen = useRef(value);
|
|
115
|
+
const commitRef = useRef(setValue);
|
|
116
|
+
commitRef.current = setValue;
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (seen.current === value) return;
|
|
119
|
+
seen.current = value;
|
|
120
|
+
setDraft(value);
|
|
121
|
+
}, [value]);
|
|
122
|
+
return {
|
|
123
|
+
value: draft,
|
|
124
|
+
set: setDraft,
|
|
125
|
+
commit: () => {
|
|
126
|
+
const parsed = typeof draft === "number" ? draft : Number.parseFloat(draft);
|
|
127
|
+
if (!Number.isFinite(parsed)) {
|
|
128
|
+
setDraft(value);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let next = parsed;
|
|
132
|
+
if (bounds.min !== void 0 && next < bounds.min) next = bounds.min;
|
|
133
|
+
if (bounds.max !== void 0 && next > bounds.max) next = bounds.max;
|
|
134
|
+
setDraft(next);
|
|
135
|
+
if (next === value) return;
|
|
136
|
+
seen.current = next;
|
|
137
|
+
commitRef.current(next);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
NumberFilter
|
|
143
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-boundary.test.d.ts","sourceRoot":"","sources":["../../src/controls/peer-boundary.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props-contract.test.d.ts","sourceRoot":"","sources":["../../src/controls/props-contract.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type { FieldHandle, RangeField } from '../state.js';
|
|
3
|
+
/**
|
|
4
|
+
* The contract a custom-range picker satisfies — `basalt-ui/controls-dates`' `DateRangePicker`, or a
|
|
5
|
+
* consumer's own. Dates are ISO (`YYYY-MM-DD`), the same shape `field.range` stores and
|
|
6
|
+
* `toWindow()` emits, so the picker never learns the store's encoding.
|
|
7
|
+
*/
|
|
8
|
+
export type RangeCustomPickerProps = {
|
|
9
|
+
readonly value: {
|
|
10
|
+
readonly from?: string | undefined;
|
|
11
|
+
readonly to?: string | undefined;
|
|
12
|
+
};
|
|
13
|
+
readonly onChange: (next: {
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
/** Everything about the props that does not depend on the field's `custom` flag. */
|
|
19
|
+
type RangeFilterBase<P extends string, C extends boolean> = {
|
|
20
|
+
/**
|
|
21
|
+
* A range handle from either store factory. Generic over the field's `custom` flag so a
|
|
22
|
+
* `field.range({ ... })` WITHOUT `custom` (whose handle is `RangeField<P, false>`, and whose
|
|
23
|
+
* values therefore never include `'custom'`) binds with no cast — pinning `C` to its default
|
|
24
|
+
* `boolean` here made the setter contravariantly incompatible and forced one at every call site.
|
|
25
|
+
*/
|
|
26
|
+
readonly field: FieldHandle<RangeField<P, C>>;
|
|
27
|
+
/** Leading pill icon. Defaults to a calendar glyph — a range filter reads as a date control, so
|
|
28
|
+
* the glyph is part of the control's identity, not a per-call-site decision. */
|
|
29
|
+
readonly icon?: ReactNode;
|
|
30
|
+
/** Sheet-form heading. The pill's own label is the VALUE, which is what a bar reads. @default 'Range' */
|
|
31
|
+
readonly label?: string;
|
|
32
|
+
};
|
|
33
|
+
export type RangeFilterProps<P extends string, C extends boolean = boolean> = RangeFilterBase<P, C> & {
|
|
34
|
+
/**
|
|
35
|
+
* Rendered when the field declares `custom: true`. Omitted → presets only, which stays LEGAL
|
|
36
|
+
* (a range that only ever shows presets is a real configuration) but warns once in dev when the
|
|
37
|
+
* field allows a custom window: the affordance is then unreachable, and nothing else says so.
|
|
38
|
+
*/
|
|
39
|
+
readonly customPicker?: ComponentType<RangeCustomPickerProps>;
|
|
40
|
+
};
|
|
41
|
+
export declare function RangeFilter<P extends string, C extends boolean = boolean>(props: RangeFilterProps<P, C>): ReactNode;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=range-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range-filter.d.ts","sourceRoot":"","sources":["../../src/controls/range-filter.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAwCnE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;IACxF,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CAChE,CAAA;AAED,oFAAoF;AACpF,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,IAAI;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC7C;qFACiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,yGAAyG;IACzG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,eAAe,CAC3F,CAAC,EACD,CAAC,CACF,GAAG;IACF;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAA;CAC9D,CAAA;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,EACvE,KAAK,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,SAAS,CAyGX"}
|