basalt-ui 1.28.0 → 1.29.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 +11 -18
- package/CHANGELOG.md +13 -0
- package/MIGRATING.md +151 -7
- package/README.md +23 -66
- package/agent/rules/basalt-batteries.md +59 -182
- package/agent/rules/basalt-charts.md +56 -75
- package/agent/rules/basalt-controls.md +38 -69
- package/agent/rules/basalt-mantine.md +58 -103
- package/agent/rules/basalt-state.md +42 -60
- package/agent/rules/basalt-tokens.md +46 -67
- package/agent/skills/basalt-app/SKILL.md +2 -0
- package/agent/templates/CLAUDE-block.md.tpl +14 -14
- package/configs/oxlint-plugin.js +70 -167
- package/configs/oxlint-plugin.test.ts +20 -186
- package/configs/oxlint.json +0 -1
- package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
- package/dist/agent/ai-sdk-transport.js +7 -4
- package/dist/agent/coalesce.js +1 -1
- package/dist/agent/merge.js +1 -1
- package/dist/agent/transport.d.ts +13 -2
- package/dist/agent/transport.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.d.ts +12 -1
- package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.js +20 -6
- package/dist/agent-chat/index.d.ts +1 -1
- package/dist/agent-chat/index.d.ts.map +1 -1
- package/dist/agent-chat/relative-time.js +1 -1
- package/dist/agent-chat/thread-detail-panel.js +1 -1
- package/dist/agent-chat/thread-feed-row.d.ts +32 -7
- package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed-row.js +121 -86
- package/dist/agent-chat/thread-feed.d.ts +11 -1
- package/dist/agent-chat/thread-feed.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed.js +10 -6
- package/dist/agent-chat/thread-message.js +1 -1
- package/dist/agent-chat/virtualize.d.ts +39 -0
- package/dist/agent-chat/virtualize.d.ts.map +1 -1
- package/dist/agent-chat/virtualize.js +15 -0
- package/dist/charts/index.d.ts +3 -3
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +8 -4
- package/dist/charts/kinds/BandStrip.js +1 -1
- package/dist/charts/kinds/Bars.d.ts +1 -3
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +1 -1
- package/dist/charts/kinds/DualPanel.d.ts +3 -1
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +1 -1
- package/dist/charts/kinds/StackedArea.js +1 -1
- package/dist/charts/kinds/ZonedLine.d.ts +1 -3
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.d.ts +21 -2
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +34 -2
- package/dist/charts/primitives/ChartCard.d.ts +20 -4
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +62 -33
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +8 -1
- package/dist/charts/primitives/ChartPending.d.ts +12 -6
- package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
- package/dist/charts/primitives/ChartPending.js +1 -1
- package/dist/charts/utils/format.d.ts +5 -92
- package/dist/charts/utils/format.d.ts.map +1 -1
- package/dist/charts/utils/format.js +10 -80
- package/dist/cli/audit-allows.d.ts +24 -0
- package/dist/cli/audit-allows.d.ts.map +1 -0
- package/dist/cli/audit-allows.js +240 -0
- package/dist/cli/check-theme.d.ts +10 -0
- package/dist/cli/check-theme.d.ts.map +1 -0
- package/dist/cli/check-theme.js +139 -0
- package/dist/cli/doctor.d.ts +47 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +197 -0
- package/dist/cli/guard-hook.d.ts +9 -0
- package/dist/cli/guard-hook.d.ts.map +1 -0
- package/dist/cli/guard-hook.js +93 -0
- package/dist/cli/help.d.ts +9 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +53 -0
- package/dist/cli/index.d.ts +360 -270
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +85 -1901
- package/dist/cli/init.d.ts +4 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +165 -0
- package/dist/cli/sync.d.ts +43 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +206 -0
- package/dist/cli/tokens-css.d.ts +51 -0
- package/dist/cli/tokens-css.d.ts.map +1 -0
- package/dist/cli/tokens-css.js +165 -0
- package/dist/commands/define-commands.js +1 -1
- package/dist/commands/define-overlays.js +1 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +6 -0
- package/dist/commands/overlays-mount.d.ts +7 -9
- package/dist/commands/overlays-mount.d.ts.map +1 -1
- package/dist/commands/shell-bridge.d.ts +57 -0
- package/dist/commands/shell-bridge.d.ts.map +1 -0
- package/dist/commands/shell-bridge.js +32 -0
- package/dist/common/errors.d.ts +35 -0
- package/dist/common/errors.d.ts.map +1 -1
- package/dist/common/errors.js +29 -1
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +6 -0
- package/dist/common/is-dev.d.ts.map +1 -0
- package/dist/{motion/index.d.ts → common/motion.d.ts} +1 -1
- package/dist/common/motion.d.ts.map +1 -0
- package/dist/common/props.d.ts +1 -1
- package/dist/common/props.d.ts.map +1 -1
- package/dist/common/validate.js +1 -1
- package/dist/content/markdown.d.ts +3 -4
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +1 -4
- package/dist/controls/actions.d.ts +1 -1
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/panel-row.js +1 -1
- package/dist/controls/range-filter.js +1 -1
- package/dist/dashboard/delta-badge.d.ts +18 -10
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/delta-badge.js +33 -1
- package/dist/dashboard/empty-state.d.ts +1 -4
- package/dist/dashboard/empty-state.d.ts.map +1 -1
- package/dist/dashboard/empty-state.js +1 -2
- package/dist/dashboard/index.d.ts +4 -2
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +7 -1
- package/dist/dashboard/query-state.d.ts +4 -13
- package/dist/dashboard/query-state.d.ts.map +1 -1
- package/dist/dashboard/query-state.js +4 -7
- package/dist/{section → dashboard}/section.d.ts +2 -2
- package/dist/{section → dashboard}/section.d.ts.map +1 -1
- package/dist/{section → dashboard}/section.js +2 -2
- package/dist/dashboard/settings-section.d.ts +1 -1
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +1 -1
- package/dist/dashboard/stat-card.d.ts +1 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +1 -1
- package/dist/dashboard/widget-header.d.ts.map +1 -0
- package/dist/data/data-table.js +2 -2
- package/dist/data/table.d.ts +1 -1
- package/dist/data/table.d.ts.map +1 -1
- package/dist/data/table.js +2 -16
- package/dist/data/virtual.d.ts +3 -3
- package/dist/dev-dock.d.ts +28 -0
- package/dist/dev-dock.d.ts.map +1 -0
- package/dist/dev-dock.js +85 -0
- package/dist/format/date.d.ts +62 -0
- package/dist/format/date.d.ts.map +1 -0
- package/dist/format/date.js +106 -0
- package/dist/format/index.d.ts +39 -0
- package/dist/format/index.d.ts.map +1 -0
- package/dist/format/index.js +54 -0
- package/dist/format/measure.d.ts +15 -0
- package/dist/format/measure.d.ts.map +1 -0
- package/dist/format/measure.js +16 -0
- package/dist/format/number.d.ts +69 -0
- package/dist/format/number.d.ts.map +1 -0
- package/dist/format/number.js +62 -0
- package/dist/format/shared.d.ts +27 -0
- package/dist/format/shared.d.ts.map +1 -0
- package/dist/format/shared.js +29 -0
- package/dist/forms/field.d.ts +4 -20
- package/dist/forms/field.d.ts.map +1 -1
- package/dist/forms/field.js +0 -4
- package/dist/forms/form-layout.d.ts +1 -1
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.js +1 -1
- package/dist/forms/index.d.ts +1 -1
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +1 -2
- package/dist/forms/use-form-submit.js +1 -1
- package/dist/guard/index.d.ts +2 -9
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +16 -26
- package/dist/guard/types.d.ts +8 -10
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +16 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -29
- package/dist/notifications/index.d.ts +5 -5
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +0 -2
- package/dist/notifications/mount-guard.d.ts.map +1 -1
- package/dist/notifications/mount-guard.js +2 -2
- package/dist/provider/connectivity-indicator.d.ts.map +1 -0
- package/dist/{connectivity → provider}/connectivity-provider.d.ts +1 -1
- package/dist/provider/connectivity-provider.d.ts.map +1 -0
- package/dist/{connectivity/types.d.ts → provider/connectivity-types.d.ts} +1 -1
- package/dist/provider/connectivity-types.d.ts.map +1 -0
- package/dist/provider/error-fallback.module.css +21 -0
- package/dist/provider/error-fallback.module.css.d.ts +9 -0
- package/dist/provider/index.d.ts +20 -36
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +42 -32
- package/dist/provider/lab-theme.js +1 -1
- package/dist/provider/use-connectivity.d.ts +3 -0
- package/dist/provider/use-connectivity.d.ts.map +1 -0
- package/dist/query-client.d.ts +63 -0
- package/dist/query-client.d.ts.map +1 -0
- package/dist/query-client.js +33 -0
- package/dist/{query/devtools.d.ts → query-devtools.d.ts} +2 -2
- package/dist/query-devtools.d.ts.map +1 -0
- package/dist/register.d.ts +17 -3
- package/dist/register.d.ts.map +1 -1
- package/dist/router-tanstack/index.d.ts +4 -7
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +1 -9
- package/dist/router-tanstack/nav.d.ts +1 -1
- package/dist/router-tanstack/nav.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.d.ts +3 -6
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.d.ts +1 -1
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.d.ts +1 -1
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-main.module.css +11 -0
- package/dist/shell/app-mobile-nav.d.ts +1 -1
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +24 -2
- package/dist/shell/app-sidebar.d.ts +1 -1
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +31 -2
- package/dist/shell/app-sidebar.module.css +10 -0
- package/dist/shell/index.d.ts +10 -4
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +13 -17
- package/dist/shell/mobile-nav-model.d.ts +1 -1
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/{nav/types.d.ts → shell/nav-types.d.ts} +1 -1
- package/dist/shell/nav-types.d.ts.map +1 -0
- package/dist/shell/page-aside.d.ts.map +1 -1
- package/dist/shell/page-aside.js +2 -29
- package/dist/shell/page-title.d.ts +14 -0
- package/dist/shell/page-title.d.ts.map +1 -0
- package/dist/shell/page-title.js +25 -0
- package/dist/shell/page-title.module.css +33 -0
- package/dist/shell/page-title.module.css.d.ts +11 -0
- package/dist/shell/sidebar-block-model.d.ts +1 -1
- package/dist/shell/sidebar-block-model.d.ts.map +1 -1
- package/dist/shell/sidebar-blocks.d.ts +1 -1
- package/dist/shell/sidebar-blocks.d.ts.map +1 -1
- package/dist/shell/use-breakpoint.d.ts +11 -0
- package/dist/shell/use-breakpoint.d.ts.map +1 -0
- package/dist/shell/use-breakpoint.js +33 -0
- package/dist/state/fields.d.ts +3 -19
- package/dist/state/fields.d.ts.map +1 -1
- package/dist/state/fields.js +6 -16
- package/dist/styles.css +36 -0
- package/dist/surfaces.d.ts +18 -41
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +46 -59
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/{theme-toggle/index.d.ts → theme/theme-toggle.d.ts} +1 -1
- package/dist/theme/theme-toggle.d.ts.map +1 -0
- package/dist/{theme-toggle/index.js → theme/theme-toggle.js} +1 -1
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/palette.d.ts +12 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +13 -1
- package/dist/tokens.css +1 -0
- package/llms.txt +9 -24
- package/package.json +12 -16
- package/src/agent/ai-sdk-transport.ts +11 -4
- package/src/agent/coalesce.ts +1 -1
- package/src/agent/merge.ts +1 -1
- package/src/agent/transport.ts +15 -2
- package/src/agent/use-agent-thread-runs.ts +65 -15
- package/src/agent-chat/index.ts +6 -4
- package/src/agent-chat/relative-time.ts +1 -1
- package/src/agent-chat/thread-detail-panel.tsx +1 -1
- package/src/agent-chat/thread-feed-row.tsx +105 -30
- package/src/agent-chat/thread-feed.tsx +51 -25
- package/src/agent-chat/thread-message.tsx +1 -1
- package/src/agent-chat/virtualize.ts +48 -0
- package/src/charts/index.ts +7 -10
- package/src/charts/kinds/BandStrip.tsx +1 -1
- package/src/charts/kinds/Bars.tsx +2 -5
- package/src/charts/kinds/DualPanel.tsx +4 -3
- package/src/charts/kinds/StackedArea.tsx +1 -1
- package/src/charts/kinds/ZonedLine.tsx +1 -4
- package/src/charts/primitives/Axes.tsx +49 -0
- package/src/charts/primitives/ChartCard.tsx +49 -6
- package/src/charts/primitives/ChartFrame.tsx +5 -1
- package/src/charts/primitives/ChartPending.tsx +14 -8
- package/src/charts/utils/format.ts +17 -186
- package/src/cli/audit-allows.ts +371 -0
- package/src/cli/check-theme.ts +197 -0
- package/src/cli/doctor.ts +331 -0
- package/src/cli/guard-hook.ts +115 -0
- package/src/cli/help.ts +101 -0
- package/src/cli/index.ts +269 -3439
- package/src/cli/init.ts +247 -0
- package/src/cli/sync.ts +324 -0
- package/src/cli/tokens-css.ts +252 -0
- package/src/commands/define-commands.ts +1 -1
- package/src/commands/define-overlays.ts +1 -1
- package/src/commands/index.ts +10 -1
- package/src/commands/overlays-mount.tsx +8 -10
- package/src/commands/shell-bridge.ts +86 -0
- package/src/common/errors.ts +70 -0
- package/src/common/index.ts +2 -0
- package/src/common/props.ts +1 -1
- package/src/common/validate.ts +1 -1
- package/src/content/markdown.tsx +4 -8
- package/src/controls/actions.tsx +1 -1
- package/src/controls/panel-row.tsx +1 -1
- package/src/controls/range-filter.tsx +1 -1
- package/src/dashboard/delta-badge.tsx +74 -7
- package/src/dashboard/empty-state.tsx +2 -6
- package/src/dashboard/index.ts +11 -2
- package/src/dashboard/query-state.tsx +5 -18
- package/src/{section → dashboard}/section.tsx +4 -4
- package/src/dashboard/settings-section.tsx +2 -2
- package/src/dashboard/stat-card.tsx +2 -2
- package/src/data/data-table.tsx +3 -3
- package/src/data/table.ts +6 -25
- package/src/data/virtual-list.tsx +1 -1
- package/src/data/virtual.ts +3 -3
- package/src/dev-dock.tsx +159 -0
- package/src/format/date.ts +184 -0
- package/src/format/index.ts +69 -0
- package/src/format/measure.ts +31 -0
- package/src/format/number.ts +129 -0
- package/src/format/shared.ts +63 -0
- package/src/forms/field.ts +4 -24
- package/src/forms/form-layout.tsx +2 -2
- package/src/forms/index.ts +1 -2
- package/src/forms/use-form-submit.ts +1 -1
- package/src/guard/index.ts +30 -56
- package/src/guard/types.ts +7 -10
- package/src/index.ts +38 -29
- package/src/notifications/index.ts +5 -7
- package/src/notifications/mount-guard.ts +7 -9
- package/src/{connectivity → provider}/connectivity-indicator.tsx +1 -1
- package/src/{connectivity → provider}/connectivity-provider.tsx +1 -1
- package/src/provider/error-fallback.module.css +21 -0
- package/src/provider/error-fallback.module.css.d.ts +9 -0
- package/src/provider/index.tsx +71 -71
- package/src/provider/lab-theme.ts +1 -1
- package/src/{connectivity → provider}/use-connectivity.ts +3 -3
- package/src/query-client.ts +100 -0
- package/src/{query/devtools.tsx → query-devtools.tsx} +5 -4
- package/src/register.ts +20 -4
- package/src/router-tanstack/index.ts +4 -15
- package/src/router-tanstack/nav.ts +1 -1
- package/src/router-tanstack/search-store.ts +3 -4
- package/src/router-tanstack/use-nav.ts +1 -1
- package/src/shell/app-breadcrumbs.tsx +1 -1
- package/src/shell/app-main.module.css +11 -0
- package/src/shell/app-mobile-nav.tsx +28 -1
- package/src/shell/app-sidebar.module.css +10 -0
- package/src/shell/app-sidebar.tsx +31 -2
- package/src/shell/index.tsx +23 -28
- package/src/shell/mobile-nav-model.ts +1 -1
- package/src/shell/page-aside.tsx +2 -52
- package/src/shell/page-title.module.css +33 -0
- package/src/shell/page-title.module.css.d.ts +11 -0
- package/src/shell/page-title.tsx +57 -0
- package/src/shell/sidebar-block-model.ts +1 -1
- package/src/shell/sidebar-blocks.tsx +1 -1
- package/src/shell/use-breakpoint.ts +73 -0
- package/src/state/fields.ts +7 -43
- package/src/styles.css +36 -0
- package/src/surfaces.ts +45 -60
- package/src/theme/index.ts +7 -0
- package/src/{theme-toggle/index.tsx → theme/theme-toggle.tsx} +1 -1
- package/src/tokens/index.ts +10 -0
- package/src/tokens/palette.ts +12 -0
- package/dist/connectivity/connectivity-indicator.d.ts.map +0 -1
- package/dist/connectivity/connectivity-provider.d.ts.map +0 -1
- package/dist/connectivity/index.d.ts +0 -14
- package/dist/connectivity/index.d.ts.map +0 -1
- package/dist/connectivity/index.js +0 -8
- package/dist/connectivity/types.d.ts.map +0 -1
- package/dist/connectivity/use-connectivity.d.ts +0 -3
- package/dist/connectivity/use-connectivity.d.ts.map +0 -1
- package/dist/data/index.d.ts +0 -38
- package/dist/data/index.d.ts.map +0 -1
- package/dist/data/index.js +0 -24
- package/dist/motion/index.d.ts.map +0 -1
- package/dist/nav/types.d.ts.map +0 -1
- package/dist/notifications/overlay.d.ts +0 -20
- package/dist/notifications/overlay.d.ts.map +0 -1
- package/dist/notifications/overlay.js +0 -14
- package/dist/query/devtools.d.ts.map +0 -1
- package/dist/query/error-message.d.ts +0 -41
- package/dist/query/error-message.d.ts.map +0 -1
- package/dist/query/error-message.js +0 -30
- package/dist/query/index.d.ts +0 -47
- package/dist/query/index.d.ts.map +0 -1
- package/dist/query/index.js +0 -56
- package/dist/router-tanstack/field.d.ts +0 -14
- package/dist/router-tanstack/field.d.ts.map +0 -1
- package/dist/router-tanstack/field.js +0 -4
- package/dist/router-tanstack/multi-search-param-store.d.ts +0 -43
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +0 -1
- package/dist/router-tanstack/multi-search-param-store.js +0 -30
- package/dist/router-tanstack/search-param-store.d.ts +0 -42
- package/dist/router-tanstack/search-param-store.d.ts.map +0 -1
- package/dist/router-tanstack/search-param-store.js +0 -25
- package/dist/section/index.d.ts +0 -2
- package/dist/section/index.d.ts.map +0 -1
- package/dist/section/index.js +0 -4
- package/dist/theme-toggle/index.d.ts.map +0 -1
- package/dist/utils/is-dev.d.ts.map +0 -1
- package/dist/widget-header/delta-badge.d.ts +0 -19
- package/dist/widget-header/delta-badge.d.ts.map +0 -1
- package/dist/widget-header/delta-badge.js +0 -36
- package/dist/widget-header/index.d.ts +0 -3
- package/dist/widget-header/index.d.ts.map +0 -1
- package/dist/widget-header/index.js +0 -8
- package/dist/widget-header/widget-header.d.ts.map +0 -1
- package/src/connectivity/index.ts +0 -19
- package/src/data/index.ts +0 -69
- package/src/notifications/overlay.tsx +0 -66
- package/src/query/error-message.ts +0 -70
- package/src/query/index.ts +0 -97
- package/src/router-tanstack/field.ts +0 -36
- package/src/router-tanstack/multi-search-param-store.ts +0 -106
- package/src/router-tanstack/search-param-store.ts +0 -100
- package/src/section/index.ts +0 -1
- package/src/widget-header/delta-badge.tsx +0 -77
- package/src/widget-header/index.ts +0 -10
- /package/dist/{utils → common}/is-dev.d.ts +0 -0
- /package/dist/{utils → common}/is-dev.js +0 -0
- /package/dist/{motion/index.js → common/motion.js} +0 -0
- /package/dist/{section → dashboard}/section.module.css +0 -0
- /package/dist/{section → dashboard}/section.module.css.d.ts +0 -0
- /package/dist/{widget-header → dashboard}/widget-header.d.ts +0 -0
- /package/dist/{widget-header → dashboard}/widget-header.js +0 -0
- /package/dist/{widget-header → dashboard}/widget-header.module.css +0 -0
- /package/dist/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
- /package/dist/{connectivity → provider}/connectivity-indicator.d.ts +0 -0
- /package/dist/{connectivity → provider}/connectivity-indicator.js +0 -0
- /package/dist/{connectivity → provider}/connectivity-provider.js +0 -0
- /package/dist/{connectivity/types.js → provider/connectivity-types.js} +0 -0
- /package/dist/{connectivity → provider}/use-connectivity.js +0 -0
- /package/dist/{query/devtools.js → query-devtools.js} +0 -0
- /package/dist/{nav/types.js → shell/nav-types.js} +0 -0
- /package/src/{utils → common}/is-dev.ts +0 -0
- /package/src/{motion/index.ts → common/motion.ts} +0 -0
- /package/src/{section → dashboard}/section.module.css +0 -0
- /package/src/{section → dashboard}/section.module.css.d.ts +0 -0
- /package/src/{widget-header → dashboard}/widget-header.module.css +0 -0
- /package/src/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
- /package/src/{widget-header → dashboard}/widget-header.tsx +0 -0
- /package/src/{connectivity/types.ts → provider/connectivity-types.ts} +0 -0
- /package/src/{nav/types.ts → shell/nav-types.ts} +0 -0
|
@@ -37,7 +37,7 @@ import { cx } from '../common/props'
|
|
|
37
37
|
import type { BasaltProps } from '../common/props'
|
|
38
38
|
import { assertRequiredProps } from '../common/validate'
|
|
39
39
|
import type { BrandConfig, SettingsMenuItem } from './index'
|
|
40
|
-
import type { SidebarBlock, SidebarItem, SidebarSection } from '
|
|
40
|
+
import type { SidebarBlock, SidebarItem, SidebarSection } from './nav-types'
|
|
41
41
|
import { NavCountBadge } from './nav-count-badge'
|
|
42
42
|
import { SidebarAccount } from './app-sidebar-account'
|
|
43
43
|
import type { AccountMenuItem, BasaltAccountProps } from './account-types'
|
|
@@ -149,6 +149,27 @@ function IconGear() {
|
|
|
149
149
|
)
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/** The `SettingsMenuItem.active` glyph — no icon package, so a plain inline mark like `IconGear`
|
|
153
|
+
* above. Shared shape with `app-mobile-nav.tsx`'s own copy (that module's More sheet is a
|
|
154
|
+
* different render tree and basalt ships no icon dependency to import between them). */
|
|
155
|
+
function IconCheck() {
|
|
156
|
+
return (
|
|
157
|
+
<svg
|
|
158
|
+
width={14}
|
|
159
|
+
height={14}
|
|
160
|
+
viewBox="0 0 24 24"
|
|
161
|
+
fill="none"
|
|
162
|
+
stroke="currentColor"
|
|
163
|
+
strokeWidth={2.5}
|
|
164
|
+
strokeLinecap="round"
|
|
165
|
+
strokeLinejoin="round"
|
|
166
|
+
aria-hidden
|
|
167
|
+
>
|
|
168
|
+
<path d="M5 12l5 5l10 -10" />
|
|
169
|
+
</svg>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
152
173
|
const HOVER_OPEN_DELAY = 150
|
|
153
174
|
const HOVER_CLOSE_DELAY = 200
|
|
154
175
|
|
|
@@ -465,6 +486,7 @@ export function AppSidebar(props: AppSidebarProps) {
|
|
|
465
486
|
className={classes.footerBtn}
|
|
466
487
|
onClick={entry.onClick}
|
|
467
488
|
aria-label={entry.label}
|
|
489
|
+
aria-current={entry.active ? 'true' : undefined}
|
|
468
490
|
>
|
|
469
491
|
{/* A FIXED slot, so rows align on one icon column whether or not each ships an icon —
|
|
470
492
|
and the gear fallback is functional, not decorative: in the collapsed rail the label
|
|
@@ -477,6 +499,7 @@ export function AppSidebar(props: AppSidebarProps) {
|
|
|
477
499
|
</Text>
|
|
478
500
|
{/* The rail ring rides the FIRST row only — one progress block is one mark. */}
|
|
479
501
|
{index === 0 ? ringMark : null}
|
|
502
|
+
{entry.active ? <IconCheck /> : null}
|
|
480
503
|
</UnstyledButton>
|
|
481
504
|
))}
|
|
482
505
|
{versionLabel !== undefined && (
|
|
@@ -499,7 +522,13 @@ export function AppSidebar(props: AppSidebarProps) {
|
|
|
499
522
|
</Menu.Target>
|
|
500
523
|
<Menu.Dropdown>
|
|
501
524
|
{settingsItems.map((entry) => (
|
|
502
|
-
<Menu.Item
|
|
525
|
+
<Menu.Item
|
|
526
|
+
key={entry.key}
|
|
527
|
+
leftSection={entry.icon}
|
|
528
|
+
rightSection={entry.active ? <IconCheck /> : undefined}
|
|
529
|
+
onClick={entry.onClick}
|
|
530
|
+
aria-current={entry.active ? 'true' : undefined}
|
|
531
|
+
>
|
|
503
532
|
{entry.label}
|
|
504
533
|
</Menu.Item>
|
|
505
534
|
))}
|
package/src/shell/index.tsx
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* `createPersistedState` (`../state`) keyed by `storageKey` — see `collapseStore`.
|
|
19
19
|
*/
|
|
20
20
|
import { AppShell, Box } from '@mantine/core'
|
|
21
|
-
import { Fragment, useMemo } from 'react'
|
|
21
|
+
import { Fragment, useCallback, useEffect, useMemo } from 'react'
|
|
22
22
|
import type { MouseEvent, ReactNode } from 'react'
|
|
23
|
+
import { registerSidebarToggle } from '../commands/shell-bridge'
|
|
23
24
|
import { cx } from '../common/props'
|
|
24
25
|
import type { BasaltProps } from '../common/props'
|
|
25
26
|
import { assertRequiredProps } from '../common/validate'
|
|
@@ -40,7 +41,7 @@ import type {
|
|
|
40
41
|
SidebarBlock,
|
|
41
42
|
SidebarItem,
|
|
42
43
|
SidebarSection,
|
|
43
|
-
} from '
|
|
44
|
+
} from './nav-types'
|
|
44
45
|
import { CtlSlot, useBasaltSpacing } from '../theme'
|
|
45
46
|
import { createPersistedState } from '../state'
|
|
46
47
|
import brandClasses from './app-brand.module.css'
|
|
@@ -78,10 +79,12 @@ export {
|
|
|
78
79
|
export { AppBreadcrumbs, type AppBreadcrumbsProps } from './app-breadcrumbs'
|
|
79
80
|
export { PageBar, type PageBarProps, type PageBarSlot } from './page-bar'
|
|
80
81
|
export { PageAside, type PageAsideProps, type PageAsideSlot } from './page-aside'
|
|
82
|
+
export { useBreakpoint, type BreakpointName } from './use-breakpoint'
|
|
83
|
+
export { PageTitle, type PageTitleProps, type PageTitleSlot } from './page-title'
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
|
-
* The shared nav vocabulary lives in `src/nav
|
|
84
|
-
* type` it without reaching into the Mantine layer. Re-exported here so `SidebarItem` /
|
|
86
|
+
* The shared nav vocabulary lives in `src/shell/nav-types.ts` so the headless router bridge can
|
|
87
|
+
* `import type` it without reaching into the Mantine layer. Re-exported here so `SidebarItem` /
|
|
85
88
|
* `SidebarSection` keep resolving straight from `basalt-ui`, exactly as before.
|
|
86
89
|
*/
|
|
87
90
|
export type {
|
|
@@ -102,7 +105,7 @@ export type {
|
|
|
102
105
|
MobileNavModel,
|
|
103
106
|
MobileNavSlot,
|
|
104
107
|
MobileNavSurface,
|
|
105
|
-
} from '
|
|
108
|
+
} from './nav-types'
|
|
106
109
|
|
|
107
110
|
/** Brand identity shown in the sidebar header (logo + name). */
|
|
108
111
|
export type BrandConfig = {
|
|
@@ -122,6 +125,10 @@ export type SettingsMenuItem = {
|
|
|
122
125
|
label: string
|
|
123
126
|
icon?: ReactNode
|
|
124
127
|
onClick?: (e: MouseEvent) => void
|
|
128
|
+
/** This entry is the current selection (e.g. the active theme radio, the open dev tool) — a
|
|
129
|
+
* trailing check glyph renders in every form (flat footer row, gear `Menu.Item`, the mobile
|
|
130
|
+
* More-sheet row) and the row carries `aria-current="true"`. */
|
|
131
|
+
active?: boolean
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
export type BasaltShellProps = BasaltProps & {
|
|
@@ -276,30 +283,9 @@ const COLLAPSE_VERSION = 1
|
|
|
276
283
|
*/
|
|
277
284
|
const collapseStores = new Map<string, () => readonly [boolean, (next: boolean) => void]>()
|
|
278
285
|
|
|
279
|
-
/**
|
|
280
|
-
* Seeds `basalt:<key>` from the raw pre-1.20.1 key once, so the switch doesn't silently re-expand
|
|
281
|
-
* every consumer's sidebar. Mantine's `useLocalStorage` wrote a bare `JSON.stringify(value)` at the
|
|
282
|
-
* un-namespaced key; only `'true'`/`'false'` are accepted, and only when the house key is empty.
|
|
283
|
-
* A one-upgrade bridge, not a supported format — delete it once no consumer predates 1.20.1.
|
|
284
|
-
*/
|
|
285
|
-
function migrateLegacyCollapse(key: string): void {
|
|
286
|
-
try {
|
|
287
|
-
if (window.localStorage.getItem(`basalt:${key}`) !== null) return
|
|
288
|
-
const legacy = window.localStorage.getItem(key)
|
|
289
|
-
if (legacy !== 'true' && legacy !== 'false') return
|
|
290
|
-
window.localStorage.setItem(
|
|
291
|
-
`basalt:${key}`,
|
|
292
|
-
JSON.stringify({ v: COLLAPSE_VERSION, value: legacy === 'true' }),
|
|
293
|
-
)
|
|
294
|
-
} catch {
|
|
295
|
-
// Storage blocked (private browsing, quota) — defaulting to expanded is a fine outcome.
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
286
|
function collapseStore(key: string): () => readonly [boolean, (next: boolean) => void] {
|
|
300
287
|
const cached = collapseStores.get(key)
|
|
301
288
|
if (cached) return cached
|
|
302
|
-
if (typeof window !== 'undefined') migrateLegacyCollapse(key)
|
|
303
289
|
const store = createPersistedState<boolean>({ key, version: COLLAPSE_VERSION, initial: false })
|
|
304
290
|
collapseStores.set(key, store)
|
|
305
291
|
return store
|
|
@@ -354,11 +340,20 @@ function ShellFrame({
|
|
|
354
340
|
// localStorage-persisted state entirely — the consumer becomes the source of truth.
|
|
355
341
|
const isCollapseControlled = collapsedProp !== undefined
|
|
356
342
|
const collapsed = isCollapseControlled ? collapsedProp : storedCollapsed
|
|
357
|
-
const toggleCollapse = () => {
|
|
343
|
+
const toggleCollapse = useCallback(() => {
|
|
358
344
|
const next = !collapsed
|
|
359
345
|
if (!isCollapseControlled) setStoredCollapsed(next)
|
|
360
346
|
onCollapsedChange?.(next)
|
|
361
|
-
}
|
|
347
|
+
}, [collapsed, isCollapseControlled, setStoredCollapsed, onCollapsedChange])
|
|
348
|
+
// `commands/shell-bridge.ts`'s `toggleSidebar` handle — registered here so a consumer's
|
|
349
|
+
// `commands.tsx` calls it with ZERO module-scope bridge of its own, unlike argo's hand-rolled
|
|
350
|
+
// `sidebar-bridge.ts` this seeds from (see that file's own doc). Re-registered whenever
|
|
351
|
+
// `toggleCollapse` itself changes identity, so the handle always flips the CURRENT state rather
|
|
352
|
+
// than a stale closure's.
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
registerSidebarToggle(toggleCollapse)
|
|
355
|
+
return () => registerSidebarToggle(null)
|
|
356
|
+
}, [toggleCollapse])
|
|
362
357
|
|
|
363
358
|
const activeCrumb = findActiveCrumb(sections)
|
|
364
359
|
// The account row, every settings entry and every mobile-reachable sidebar block render as flat
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
SidebarBlock,
|
|
23
23
|
SidebarItem,
|
|
24
24
|
SidebarSection,
|
|
25
|
-
} from '
|
|
25
|
+
} from './nav-types'
|
|
26
26
|
import { sidebarBlockMobile } from './sidebar-block-model'
|
|
27
27
|
|
|
28
28
|
/** Hard cap on bar slots, INCLUDING More. Five 72px slots is the widest a 360px viewport holds. */
|
package/src/shell/page-aside.tsx
CHANGED
|
@@ -43,14 +43,7 @@
|
|
|
43
43
|
* read stays in JS — through `useSyncExternalStore`, so SSR, hydration and the first paint agree.
|
|
44
44
|
* Recorded in `docs/CONTROLS-SPEC.md` §1 (C9) and `docs/ASIDE-SPEC.md` §0.
|
|
45
45
|
*/
|
|
46
|
-
import {
|
|
47
|
-
createContext,
|
|
48
|
-
useCallback,
|
|
49
|
-
useContext,
|
|
50
|
-
useMemo,
|
|
51
|
-
useState,
|
|
52
|
-
useSyncExternalStore,
|
|
53
|
-
} from 'react'
|
|
46
|
+
import { createContext, useCallback, useContext, useMemo, useState } from 'react'
|
|
54
47
|
import type { ReactNode } from 'react'
|
|
55
48
|
import { createPortal } from 'react-dom'
|
|
56
49
|
import { useMantineTheme } from '@mantine/core'
|
|
@@ -61,6 +54,7 @@ import { usePersistedOrLocal } from '../state/persisted-or-local'
|
|
|
61
54
|
import { FilterSetScope } from '../controls/filter-context'
|
|
62
55
|
import { useAsidePanelSlot } from './page-bar'
|
|
63
56
|
import { useIsomorphicLayoutEffect } from './isomorphic-layout-effect'
|
|
57
|
+
import { useMediaQueryMatches } from './use-breakpoint'
|
|
64
58
|
import classes from './page-aside.module.css'
|
|
65
59
|
|
|
66
60
|
type AsideRegion = {
|
|
@@ -133,50 +127,6 @@ export function AsideOutlet({ className }: { className?: string }) {
|
|
|
133
127
|
return <div ref={setTarget} className={className} />
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
/** `window.matchMedia`, or `null` where there is no window and on a shim that does not ship it. */
|
|
137
|
-
function mediaQueryList(query: string): MediaQueryList | null {
|
|
138
|
-
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return null
|
|
139
|
-
return window.matchMedia(query)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Internal — a media query read through `useSyncExternalStore`, the ONE hook whose server snapshot
|
|
144
|
-
* React honours during BOTH `renderToString` and the hydration pass.
|
|
145
|
-
*
|
|
146
|
-
* That is the whole reason it is not `@mantine/hooks`' `useMediaQuery`, which reads `matchMedia`
|
|
147
|
-
* inside a `useState` INITIALIZER: on the server it returns `initialValue`, on the client's very
|
|
148
|
-
* first render it returns the real match, and when that render is a HYDRATION the two disagree
|
|
149
|
-
* silently — a server-rendered desktop panel hydrating on a phone against a client tree that has
|
|
150
|
-
* already moved the children into the page bar's sheet. `getServerSnapshot` makes React render the
|
|
151
|
-
* server's answer during hydration and re-render with the real one immediately after, which is the
|
|
152
|
-
* documented, warning-free version of the same correction.
|
|
153
|
-
*
|
|
154
|
-
* `fallback` is what the server (and a shim with no `matchMedia`) sees. It stays the DESKTOP
|
|
155
|
-
* answer, because the portalled branch is the one a shell-less or pre-hydration page can always
|
|
156
|
-
* fall back to rendering.
|
|
157
|
-
*/
|
|
158
|
-
function useMediaQueryMatches(query: string, fallback: boolean): boolean {
|
|
159
|
-
// ONE `MediaQueryList` per query, held across renders. `matchMedia()` allocates a fresh live
|
|
160
|
-
// object on every call and `getSnapshot` runs on every render AND after every store
|
|
161
|
-
// notification — so calling it in there churned an object per read, and the list `subscribe`
|
|
162
|
-
// listened on was never the one `getSnapshot` measured. Reading `.matches` off the SAME instance
|
|
163
|
-
// it subscribed to is what makes the snapshot and the notification describe one thing.
|
|
164
|
-
const list = useMemo(() => mediaQueryList(query), [query])
|
|
165
|
-
const subscribe = useCallback(
|
|
166
|
-
(onStoreChange: () => void) => {
|
|
167
|
-
if (list === null) return () => {}
|
|
168
|
-
list.addEventListener('change', onStoreChange)
|
|
169
|
-
return () => {
|
|
170
|
-
list.removeEventListener('change', onStoreChange)
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
[list],
|
|
174
|
-
)
|
|
175
|
-
const getSnapshot = useCallback(() => list?.matches ?? fallback, [list, fallback])
|
|
176
|
-
const getServerSnapshot = useCallback(() => fallback, [fallback])
|
|
177
|
-
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
130
|
/** The three boxes `PageAside` paints, in every projection (`common/props.ts`). */
|
|
181
131
|
export type PageAsideSlot = 'root' | 'header' | 'body'
|
|
182
132
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `PageTitle` — the shell-less page-title primitive (C5 consolidation, `docs/CONTROLS-SPEC.md`
|
|
3
|
+
* law C8's third named spot alongside the breadcrumb and `PageBar.title`). Same treatment as
|
|
4
|
+
* `page-bar.module.css`'s `.title` (head font, 88% stretch, weight 550) — a page is named once,
|
|
5
|
+
* and every named spot reads identically regardless of which one a given surface uses.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.root {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 4px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.titleRow {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--vx-space-nav-icon-gap, 8px);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.title {
|
|
21
|
+
margin: 0;
|
|
22
|
+
font-family: var(--basalt-font-head);
|
|
23
|
+
font-size: var(--vx-text-xl);
|
|
24
|
+
font-weight: 550;
|
|
25
|
+
font-stretch: 88%;
|
|
26
|
+
color: var(--vx-ink);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.subtitle {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-size: var(--vx-text-sm);
|
|
32
|
+
color: var(--vx-faint);
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed CSS-module declaration (the S4 CSS-modules path). Named string props — not an index
|
|
3
|
+
* signature — so dot access stays verbatim under the package's strict flags.
|
|
4
|
+
*/
|
|
5
|
+
declare const classes: {
|
|
6
|
+
readonly root: string
|
|
7
|
+
readonly titleRow: string
|
|
8
|
+
readonly title: string
|
|
9
|
+
readonly subtitle: string
|
|
10
|
+
}
|
|
11
|
+
export default classes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PageTitle` — the shell-less page-title primitive (C5 consolidation). Law C8
|
|
3
|
+
* (`docs/CONTROLS-SPEC.md` §2.2) names two sanctioned spots for a page's ONE name: the breadcrumb
|
|
4
|
+
* inside `BasaltShell`, or `PageBar.title` in a shell-less app — but neither fits a surface that
|
|
5
|
+
* has no `PageBar` at all, which is exactly what `BasaltErrorBoundary`'s default fallback and an
|
|
6
|
+
* auth gate ARE. Both were the two named `in-body-page-title` waivers this primitive closes
|
|
7
|
+
* (argo's `lib/error-boundary.tsx:36`, `lib/auth-gate.tsx:30` — each a bare `<Title order={2}>` +
|
|
8
|
+
* a muted subtitle line, hand-copied).
|
|
9
|
+
*
|
|
10
|
+
* A plain `<h1>` + CSS module, NOT Mantine's `<Title>` component — same reason `page-bar.tsx`'s
|
|
11
|
+
* own `title` row does (`page-bar.module.css`'s `.title`): `basalt/in-body-page-title` and its
|
|
12
|
+
* text-lane twin both match the JSX tag `<Title`, so a THIRD sanctioned spot built out of that
|
|
13
|
+
* same tag would need a waiver on itself. A raw, styled `<h1>` needs none — it IS the remedy, not
|
|
14
|
+
* a case of it.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* import { PageTitle } from 'basalt-ui'
|
|
18
|
+
*
|
|
19
|
+
* <PageTitle title="Something went wrong" subtitle="The page hit an unexpected error." />
|
|
20
|
+
*/
|
|
21
|
+
import { cx } from '../common/props'
|
|
22
|
+
import { assertRequiredProps } from '../common/validate'
|
|
23
|
+
import type { BasaltProps, SlotStylesProps } from '../common/props'
|
|
24
|
+
import type { ReactNode } from 'react'
|
|
25
|
+
import classes from './page-title.module.css'
|
|
26
|
+
|
|
27
|
+
export type PageTitleSlot = 'root' | 'title' | 'subtitle'
|
|
28
|
+
|
|
29
|
+
export type PageTitleProps = BasaltProps &
|
|
30
|
+
SlotStylesProps<PageTitleSlot> & {
|
|
31
|
+
/** The page's one name. */
|
|
32
|
+
title: ReactNode
|
|
33
|
+
/** Optional muted line below the title. */
|
|
34
|
+
subtitle?: ReactNode
|
|
35
|
+
/** Optional leading icon, rendered before the title. Decorative — hidden from assistive tech,
|
|
36
|
+
* same convention as `WidgetHeader.icon`. */
|
|
37
|
+
icon?: ReactNode
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function PageTitle(props: PageTitleProps): ReactNode {
|
|
41
|
+
assertRequiredProps('PageTitle', props, ['title'])
|
|
42
|
+
const { title, subtitle, icon, className, classNames, style } = props
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
className={cx(classes.root, classNames?.root, className)}
|
|
46
|
+
{...(style !== undefined && { style })}
|
|
47
|
+
>
|
|
48
|
+
<div className={cx(classes.titleRow, classNames?.title)}>
|
|
49
|
+
{icon !== undefined && <span aria-hidden="true">{icon}</span>}
|
|
50
|
+
<h1 className={classes.title}>{title}</h1>
|
|
51
|
+
</div>
|
|
52
|
+
{subtitle !== undefined && (
|
|
53
|
+
<p className={cx(classes.subtitle, classNames?.subtitle)}>{subtitle}</p>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* A default re-derived per call site is how the count and the renderer drift apart (see
|
|
10
10
|
* `accountRowCount`'s doc for the same failure with account rows).
|
|
11
11
|
*/
|
|
12
|
-
import type { SidebarBlock } from '
|
|
12
|
+
import type { SidebarBlock } from './nav-types'
|
|
13
13
|
|
|
14
14
|
/** Envelope version for a persisted fold flag. Bump only if the value stops being a boolean. */
|
|
15
15
|
export const FOLD_VERSION = 1
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useBreakpoint` — one media-query hook over the theme's own breakpoints, SSR/hydration-safe.
|
|
3
|
+
*
|
|
4
|
+
* Built on `useSyncExternalStore`, NOT `@mantine/hooks`' `useMediaQuery` — that hook reads
|
|
5
|
+
* `matchMedia` inside a `useState` initializer, so on the server it returns its `fallback` and on
|
|
6
|
+
* the client's very first render it returns the REAL match; when that render is a hydration pass
|
|
7
|
+
* the two disagree silently (a server-rendered desktop layout hydrating on a phone against a
|
|
8
|
+
* client tree that already moved). `useSyncExternalStore`'s `getServerSnapshot` makes React render
|
|
9
|
+
* the server's answer during hydration and correct itself immediately after — the documented,
|
|
10
|
+
* warning-free version of the same fix. `page-aside.tsx`'s own former private
|
|
11
|
+
* `useMediaQueryMatches` carried this exact rationale; it now lives here and page-aside composes
|
|
12
|
+
* this hook instead (C5 consolidation — one media-query hook, not two).
|
|
13
|
+
*
|
|
14
|
+
* `edge` defaults to `'min'` (`"at least this wide"`); pass `'max'` for `"narrower than"`. The
|
|
15
|
+
* `fallback` (what the server, and a shim with no `matchMedia`, sees) defaults to the answer that
|
|
16
|
+
* assumes the NARROW side of the query — `false` for `min`, `true` for `max` — since a component
|
|
17
|
+
* library cannot know a consumer's real device mix; a call site with its own rationale (e.g.
|
|
18
|
+
* `page-aside.tsx`'s desktop-first portal fallback) overrides it via `options.fallback`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { useBreakpoint } from 'basalt-ui'
|
|
22
|
+
*
|
|
23
|
+
* const isDesktop = useBreakpoint('sm') // >= theme.breakpoints.sm
|
|
24
|
+
* const isNarrow = !useBreakpoint('sm') // < theme.breakpoints.sm
|
|
25
|
+
* const isWide = useBreakpoint('lg') // >= theme.breakpoints.lg
|
|
26
|
+
*/
|
|
27
|
+
import { useCallback, useMemo, useSyncExternalStore } from 'react'
|
|
28
|
+
import { useMantineTheme } from '@mantine/core'
|
|
29
|
+
|
|
30
|
+
export type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
31
|
+
|
|
32
|
+
/** `window.matchMedia`, or `null` where there is no window and on a shim that does not ship it. */
|
|
33
|
+
function mediaQueryList(query: string): MediaQueryList | null {
|
|
34
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return null
|
|
35
|
+
return window.matchMedia(query)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Internal — the raw media-query read. Exported (not `default`) so `page-aside.tsx` can compose
|
|
40
|
+
* it directly for its `(min-width: theme.breakpoints.sm)` desktop check without re-deriving the
|
|
41
|
+
* query string `useBreakpoint` builds for the fixed `BreakpointName` union.
|
|
42
|
+
*/
|
|
43
|
+
export function useMediaQueryMatches(query: string, fallback: boolean): boolean {
|
|
44
|
+
// ONE `MediaQueryList` per query, held across renders — `matchMedia()` allocates a fresh live
|
|
45
|
+
// object on every call, and `getSnapshot` runs on every render AND after every store
|
|
46
|
+
// notification, so calling it there would churn an object per read while `subscribe` listened
|
|
47
|
+
// on a different instance than `getSnapshot` measured.
|
|
48
|
+
const list = useMemo(() => mediaQueryList(query), [query])
|
|
49
|
+
const subscribe = useCallback(
|
|
50
|
+
(onStoreChange: () => void) => {
|
|
51
|
+
if (list === null) return () => {}
|
|
52
|
+
list.addEventListener('change', onStoreChange)
|
|
53
|
+
return () => {
|
|
54
|
+
list.removeEventListener('change', onStoreChange)
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
[list],
|
|
58
|
+
)
|
|
59
|
+
const getSnapshot = useCallback(() => list?.matches ?? fallback, [list, fallback])
|
|
60
|
+
const getServerSnapshot = useCallback(() => fallback, [fallback])
|
|
61
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function useBreakpoint(
|
|
65
|
+
name: BreakpointName,
|
|
66
|
+
edge: 'min' | 'max' = 'min',
|
|
67
|
+
options?: { fallback?: boolean },
|
|
68
|
+
): boolean {
|
|
69
|
+
const theme = useMantineTheme()
|
|
70
|
+
const bp = theme.breakpoints[name]
|
|
71
|
+
const fallback = options?.fallback ?? edge === 'max'
|
|
72
|
+
return useMediaQueryMatches(`(${edge}-width: ${bp})`, fallback)
|
|
73
|
+
}
|
package/src/state/fields.ts
CHANGED
|
@@ -561,13 +561,7 @@ export type FieldCodec = {
|
|
|
561
561
|
readonly int: boolean
|
|
562
562
|
readonly step: number | undefined
|
|
563
563
|
}
|
|
564
|
-
/**
|
|
565
|
-
* Validate a STORED (or caller-supplied) value. `null` = unusable.
|
|
566
|
-
*
|
|
567
|
-
* Deliberately a different function from `fromSearch` for one kind: the deprecated multi store
|
|
568
|
-
* reads an empty URL array as "absent", and reusing that rule on the storage side would turn a
|
|
569
|
-
* deliberately CLEARED filter into a miss and resurrect the fallback on upgrade.
|
|
570
|
-
*/
|
|
564
|
+
/** Validate a STORED (or caller-supplied) value. `null` = unusable. */
|
|
571
565
|
decode(raw: unknown): unknown
|
|
572
566
|
/** Pull the field out of a router search object. `null` = absent or invalid. */
|
|
573
567
|
fromSearch(search: Record<string, unknown>): unknown
|
|
@@ -614,17 +608,11 @@ function sameList(a: unknown, b: unknown): boolean {
|
|
|
614
608
|
}
|
|
615
609
|
|
|
616
610
|
/**
|
|
617
|
-
* Build the codec for one field.
|
|
618
|
-
* an EMPTY url array means "absent, fall through to storage" — the deprecated wrapper's contract,
|
|
619
|
-
* never the new store's, where an empty selection is a value like any other.
|
|
611
|
+
* Build the codec for one field.
|
|
620
612
|
*
|
|
621
613
|
* @internal — see `FieldCodec`.
|
|
622
614
|
*/
|
|
623
|
-
export function resolveFieldCodec(
|
|
624
|
-
name: string,
|
|
625
|
-
f: AnyField,
|
|
626
|
-
opts?: { legacyMultiEmpty?: boolean },
|
|
627
|
-
): FieldCodec {
|
|
615
|
+
export function resolveFieldCodec(name: string, f: AnyField): FieldCodec {
|
|
628
616
|
switch (f.kind) {
|
|
629
617
|
case 'enum': {
|
|
630
618
|
const allowed = new Set<string>(f.values)
|
|
@@ -642,19 +630,9 @@ export function resolveFieldCodec(
|
|
|
642
630
|
}
|
|
643
631
|
return f.values.filter((value) => picked.has(value))
|
|
644
632
|
}
|
|
645
|
-
// The legacy empty-array rule is a URL rule ONLY. A stored `[]` is what "I cleared every
|
|
646
|
-
// filter" looks like, and the deprecated store has always read it back as `[]`.
|
|
647
|
-
const decodeUrl =
|
|
648
|
-
opts?.legacyMultiEmpty === true
|
|
649
|
-
? (raw: unknown): unknown => {
|
|
650
|
-
const value = decode(raw)
|
|
651
|
-
return Array.isArray(value) && value.length === 0 ? null : value
|
|
652
|
-
}
|
|
653
|
-
: decode
|
|
654
633
|
return single(name, f, decode, {
|
|
655
634
|
optionValues: f.values,
|
|
656
635
|
equals: sameList,
|
|
657
|
-
decodeUrl,
|
|
658
636
|
})
|
|
659
637
|
}
|
|
660
638
|
case 'range': {
|
|
@@ -759,13 +737,10 @@ function single(
|
|
|
759
737
|
extra?: {
|
|
760
738
|
optionValues?: readonly string[]
|
|
761
739
|
equals?: (a: unknown, b: unknown) => boolean
|
|
762
|
-
/** URL-side decoder when it differs from the storage-side one (multi, legacy layout only). */
|
|
763
|
-
decodeUrl?: (raw: unknown) => unknown
|
|
764
740
|
/** Number only: the declared bounds and grain, republished onto the handle. */
|
|
765
741
|
bounds?: FieldCodec['bounds']
|
|
766
742
|
},
|
|
767
743
|
): FieldCodec {
|
|
768
|
-
const decodeUrl = extra?.decodeUrl ?? decode
|
|
769
744
|
return {
|
|
770
745
|
kind: f.kind,
|
|
771
746
|
lane: f.lane,
|
|
@@ -776,7 +751,7 @@ function single(
|
|
|
776
751
|
},
|
|
777
752
|
...(extra?.bounds !== undefined && { bounds: extra.bounds }),
|
|
778
753
|
decode,
|
|
779
|
-
fromSearch: (search) =>
|
|
754
|
+
fromSearch: (search) => decode(search[name]),
|
|
780
755
|
toSearch: (value) => ({ [name]: value }),
|
|
781
756
|
equals: extra?.equals ?? ((a, b) => a === b),
|
|
782
757
|
optionValues: extra?.optionValues ?? [],
|
|
@@ -805,12 +780,6 @@ export type StoreCoreOptions = {
|
|
|
805
780
|
readonly key: string
|
|
806
781
|
readonly fields: Record<string, AnyField>
|
|
807
782
|
readonly version?: number | undefined
|
|
808
|
-
/**
|
|
809
|
-
* Legacy single-value storage layout: the whole envelope value IS this field's value, the shape
|
|
810
|
-
* `createSearchParamStore` has written since 1.0.0. Set ONLY by the deprecated wrappers — a
|
|
811
|
-
* consumer's already-persisted selection has to keep resolving byte-for-byte.
|
|
812
|
-
*/
|
|
813
|
-
readonly legacyValueField?: string
|
|
814
783
|
}
|
|
815
784
|
|
|
816
785
|
/**
|
|
@@ -967,10 +936,9 @@ function assertNoParamCollision(key: string, entries: readonly StoreEntry[]): vo
|
|
|
967
936
|
/** @internal — the seam behind `createSearchStore` and `createLocalStore`. */
|
|
968
937
|
export function createStoreCore(o: StoreCoreOptions): StoreCore {
|
|
969
938
|
const version = o.version ?? 1
|
|
970
|
-
const legacy = o.legacyValueField
|
|
971
939
|
const entries: StoreEntry[] = Object.entries(o.fields).map(([name, f]) => ({
|
|
972
940
|
name,
|
|
973
|
-
codec: resolveFieldCodec(name, f
|
|
941
|
+
codec: resolveFieldCodec(name, f),
|
|
974
942
|
}))
|
|
975
943
|
assertNoParamCollision(o.key, entries)
|
|
976
944
|
const urlEntries = entries.filter((entry) => entry.codec.lane.url)
|
|
@@ -983,12 +951,8 @@ export function createStoreCore(o: StoreCoreOptions): StoreCore {
|
|
|
983
951
|
const usePersistedRaw = createPersistedState<unknown>({ key: o.key, version, initial: null })
|
|
984
952
|
const memory = createMemoryLane()
|
|
985
953
|
|
|
986
|
-
const toRecord = (raw: unknown): StoreRecord => {
|
|
987
|
-
|
|
988
|
-
return isRecord(raw) ? raw : {}
|
|
989
|
-
}
|
|
990
|
-
const fromRecord = (record: StoreRecord): unknown =>
|
|
991
|
-
legacy !== undefined ? (record[legacy] ?? null) : record
|
|
954
|
+
const toRecord = (raw: unknown): StoreRecord => (isRecord(raw) ? raw : {})
|
|
955
|
+
const fromRecord = (record: StoreRecord): unknown => record
|
|
992
956
|
|
|
993
957
|
const readRecord = (): StoreRecord => toRecord(readPersistedValue(o.key, version))
|
|
994
958
|
|
package/src/styles.css
CHANGED
|
@@ -40,6 +40,42 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
@layer basalt {
|
|
43
|
+
/* Native/PWA chrome (C5 consolidation) — three of the five rules argo's own `styles/native.css`
|
|
44
|
+
carried, moved here so every consumer gets them for free instead of hand-copying a stylesheet.
|
|
45
|
+
The other two (safe-area padding on the sidebar/main) are CSS-module rules on those specific
|
|
46
|
+
regions, not global — see `shell/app-sidebar.module.css` and `shell/app-main.module.css`.
|
|
47
|
+
Overscroll containment and the footer's safe-area-bottom inset already ship
|
|
48
|
+
(`shell/app-main.module.css`, `shell/app-mobile-nav.module.css`) and are not repeated here. */
|
|
49
|
+
|
|
50
|
+
/* Stops iOS from inflating font sizes on a rotation into landscape — `text-size-adjust` governs
|
|
51
|
+
text INFLATION, a different failure mode than the input-zoom floor just below. */
|
|
52
|
+
html {
|
|
53
|
+
-webkit-text-size-adjust: 100%;
|
|
54
|
+
text-size-adjust: 100%;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Removes the grey tap-highlight flash on iOS/Android for anything actually tappable. Scoped to
|
|
58
|
+
the interactive roles/tags rather than `*` (argo's own rule): a highlight on non-interactive
|
|
59
|
+
content is not the flash this exists to remove, and `*` would also numb a consumer's own
|
|
60
|
+
custom interactive elements that don't match one of these. */
|
|
61
|
+
button,
|
|
62
|
+
a,
|
|
63
|
+
[role='button'],
|
|
64
|
+
[role='tab'] {
|
|
65
|
+
-webkit-tap-highlight-color: transparent;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* A long-press on a control must not select its label or pop the iOS callout menu — inputs and
|
|
69
|
+
prose content stay selectable. Same Mantine class-hook targets argo's own rule used
|
|
70
|
+
(`.mantine-Button-root`/`.mantine-NavLink-root`/`.mantine-Burger-root`): the theme layer is
|
|
71
|
+
the one place allowed to reach for a Mantine class hook directly, per `basalt-mantine.md`. */
|
|
72
|
+
.mantine-Button-root,
|
|
73
|
+
.mantine-NavLink-root,
|
|
74
|
+
.mantine-Burger-root {
|
|
75
|
+
-webkit-touch-callout: none;
|
|
76
|
+
user-select: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
43
79
|
/* iOS input-zoom FLOOR. Safari zooms the viewport whenever a focused input computes below 16px,
|
|
44
80
|
and never zooms back out on blur — the single worst mobile-form defect. 16px is the only clean
|
|
45
81
|
fix: `maximum-scale=1` / `user-scalable=no` block pinch-zoom on Android (WCAG 1.4.4),
|