basalt-ui 1.31.0 → 1.32.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/CHANGELOG.md +23 -0
- package/MIGRATING.md +101 -17
- package/agent/rules/basalt-mantine.md +4 -4
- package/configs/oxlint-plugin.js +159 -42
- package/configs/oxlint-plugin.test.ts +182 -0
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +18 -66
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +5 -26
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +10 -68
- package/dist/charts/kinds/band-chrome.d.ts +31 -0
- package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
- package/dist/charts/kinds/band-chrome.js +71 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +49 -76
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +5 -23
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +23 -40
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +5 -133
- package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +16 -1
- package/dist/charts/primitives/HoverOverlay.d.ts +24 -9
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +17 -1
- package/dist/charts/primitives/chart-card-context.d.ts +17 -13
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -1
- package/dist/charts/primitives/chart-card-context.js +10 -5
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +25 -31
- package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
- package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
- package/dist/charts/primitives/legend-disclosure.js +153 -0
- package/dist/controls/controls.module.css +5 -0
- package/dist/controls/sync-button.module.css +1 -0
- package/dist/dashboard/stat-group.module.css +19 -64
- package/dist/dashboard/widget-grid.module.css +11 -38
- package/dist/data/data-table-state.d.ts +83 -0
- package/dist/data/data-table-state.d.ts.map +1 -0
- package/dist/data/data-table-state.js +139 -0
- package/dist/data/data-table.d.ts +33 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +179 -277
- package/dist/forms/form-layout.module.css +3 -13
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +6 -5
- package/dist/notifications/center.d.ts.map +1 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +12 -10
- package/dist/shell/aside-docking.d.ts +31 -0
- package/dist/shell/aside-docking.d.ts.map +1 -0
- package/dist/shell/aside-docking.js +19 -0
- package/dist/shell/index.d.ts +0 -11
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +13 -14
- package/dist/shell/use-breakpoint.d.ts +1 -1
- package/dist/theme/floating.module.css +1 -0
- package/dist/theme-lab/derive-controls.d.ts.map +1 -1
- package/llms.txt +2 -2
- package/package.json +2 -1
- package/src/charts/kinds/BandStrip.tsx +18 -64
- package/src/charts/kinds/DualPanel.tsx +4 -24
- package/src/charts/kinds/MirroredBars.tsx +12 -66
- package/src/charts/kinds/band-chrome.tsx +91 -0
- package/src/charts/primitives/CartesianChart.tsx +79 -122
- package/src/charts/primitives/ChartCard.tsx +5 -27
- package/src/charts/primitives/ChartFrame.tsx +37 -76
- package/src/charts/primitives/ChartLegend.tsx +7 -154
- package/src/charts/primitives/ChartTooltip.tsx +16 -0
- package/src/charts/primitives/HoverOverlay.tsx +52 -21
- package/src/charts/primitives/chart-card-context.ts +25 -16
- package/src/charts/primitives/chart-frame-layout.ts +44 -56
- package/src/charts/primitives/legend-disclosure.tsx +177 -0
- package/src/controls/controls.module.css +5 -0
- package/src/controls/sync-button.module.css +1 -0
- package/src/dashboard/stat-group.module.css +19 -64
- package/src/dashboard/widget-grid.module.css +11 -38
- package/src/data/data-table-state.ts +218 -0
- package/src/data/data-table.tsx +127 -218
- package/src/forms/form-layout.module.css +3 -13
- package/src/guard/index.ts +21 -16
- package/src/notifications/center.tsx +3 -0
- package/src/provider/index.tsx +18 -17
- package/src/shell/aside-docking.ts +54 -0
- package/src/shell/index.tsx +15 -37
- package/src/shell/use-breakpoint.ts +2 -2
- package/src/theme/floating.module.css +1 -0
- package/src/theme-lab/derive-controls.tsx +2 -0
|
@@ -205,6 +205,9 @@ export function NotificationCenter({ maxHeight = 320, className, style }: Notifi
|
|
|
205
205
|
</Group>
|
|
206
206
|
|
|
207
207
|
{/* View switch */}
|
|
208
|
+
{/* theme-allow control-outside-home — the center's own inbox/all view, rendered inside
|
|
209
|
+
`NotificationBell`'s Popover (an overlay, a declared non-home the AST cannot see across
|
|
210
|
+
the component boundary). */}
|
|
208
211
|
<SegmentedControl
|
|
209
212
|
fullWidth
|
|
210
213
|
size="xs"
|
package/src/provider/index.tsx
CHANGED
|
@@ -242,8 +242,13 @@ export function composeInjectedCss(
|
|
|
242
242
|
|
|
243
243
|
// ── Host attribute ────────────────────────────────────────────────────────────────────────────────
|
|
244
244
|
|
|
245
|
-
/**
|
|
246
|
-
|
|
245
|
+
/** A module-level mount count across every `BasaltProvider`; each call returns the new count. */
|
|
246
|
+
function createMountCounter(): { mount: () => number; unmount: () => number } {
|
|
247
|
+
let count = 0
|
|
248
|
+
return { mount: () => ++count, unmount: () => --count }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const hostAttributeMounts = createMountCounter()
|
|
247
252
|
|
|
248
253
|
/**
|
|
249
254
|
* `data-basalt-host` on `<html>` — the one place a `display-mode` query is read; CSS keys off it.
|
|
@@ -261,17 +266,16 @@ export function useHostAttribute(): void {
|
|
|
261
266
|
document.documentElement.setAttribute('data-basalt-host', isPwa ? 'pwa' : 'web')
|
|
262
267
|
}, [isPwa])
|
|
263
268
|
|
|
264
|
-
// Ref-counted across every mounted `BasaltProvider
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
// effect keyed on `[isPwa]` would decrement (and possibly zero out) this counter on every
|
|
269
|
-
// `isPwa` change, not only on a genuine unmount.
|
|
269
|
+
// Ref-counted across every mounted `BasaltProvider`: a second provider (a nested route wrapping
|
|
270
|
+
// its own, two trees on one page) unmounting must not rip the attribute out from under a first
|
|
271
|
+
// one still mounted; only the LAST unmount removes it. Deliberately a SEPARATE effect from the one
|
|
272
|
+
// above: sharing one keyed on `[isPwa]` would decrement this counter on every `isPwa` change.
|
|
270
273
|
useIsomorphicLayoutEffect(() => {
|
|
271
|
-
|
|
274
|
+
hostAttributeMounts.mount()
|
|
272
275
|
return () => {
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
if (hostAttributeMounts.unmount() === 0) {
|
|
277
|
+
document.documentElement.removeAttribute('data-basalt-host')
|
|
278
|
+
}
|
|
275
279
|
}
|
|
276
280
|
}, [])
|
|
277
281
|
}
|
|
@@ -374,21 +378,18 @@ function BasaltBridge({
|
|
|
374
378
|
* misuse, not a false positive from React 18 StrictMode's double-invoke (that mounts/unmounts the
|
|
375
379
|
* SAME instance in sequence, so the count returns to 0 before a second instance could ever exist).
|
|
376
380
|
*/
|
|
377
|
-
|
|
381
|
+
const providerMounts = createMountCounter()
|
|
378
382
|
|
|
379
383
|
function useDuplicateProviderGuard(): void {
|
|
380
384
|
useEffect(() => {
|
|
381
|
-
|
|
382
|
-
if (mountedProviderCount > 1 && isDev()) {
|
|
385
|
+
if (providerMounts.mount() > 1 && isDev()) {
|
|
383
386
|
console.warn(
|
|
384
387
|
'[basalt] BasaltProvider: more than one instance is mounted at once — nested/duplicate ' +
|
|
385
388
|
'BasaltProviders double-mount MantineProvider, the palette <style> and ' +
|
|
386
389
|
'ConnectivityProvider. Mount exactly one, at the app root.',
|
|
387
390
|
)
|
|
388
391
|
}
|
|
389
|
-
return () =>
|
|
390
|
-
mountedProviderCount--
|
|
391
|
-
}
|
|
392
|
+
return () => void providerMounts.unmount()
|
|
392
393
|
}, [])
|
|
393
394
|
}
|
|
394
395
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aside docking (`docs/DESIGN-CORE.md` § Layout, elevation, shapes) — the pure decision `ShellFrame`
|
|
3
|
+
* sizes `AppShell.Aside` from. An open aside pushes main only in `expanded` while main keeps
|
|
4
|
+
* `MAIN_MIN_WIDTH` beside the navbar; otherwise the region reserves just its rail and an open panel
|
|
5
|
+
* overlays main. Unclaimed, the region is zero-wide.
|
|
6
|
+
*/
|
|
7
|
+
import { toEm } from '../tokens/size-classes'
|
|
8
|
+
import type { SizeClass } from './use-size-class'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main keeps at least this much width, or an open aside overlays it instead of docking. Not
|
|
12
|
+
* derived from a token: it is the readable floor for a page body, independent of density.
|
|
13
|
+
*/
|
|
14
|
+
const MAIN_MIN_WIDTH = 720
|
|
15
|
+
|
|
16
|
+
export type AsideDockingInput = {
|
|
17
|
+
claimed: boolean
|
|
18
|
+
folded: boolean
|
|
19
|
+
sizeClass: SizeClass
|
|
20
|
+
/** The `asideDockQuery` media query's result. */
|
|
21
|
+
roomToDock: boolean
|
|
22
|
+
asideWidth: number
|
|
23
|
+
asideRailWidth: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AsideDocking = {
|
|
27
|
+
/** An open aside pushes main rather than overlaying it. */
|
|
28
|
+
docks: boolean
|
|
29
|
+
/** An open aside renders over main (reserving only its rail). */
|
|
30
|
+
overlay: boolean
|
|
31
|
+
/** The width `AppShell` reserves for the region. */
|
|
32
|
+
width: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The `min-width` query that holds while navbar + open aside + `MAIN_MIN_WIDTH` fit side by side. */
|
|
36
|
+
export function asideDockQuery(input: { navbarWidth: number; asideWidth: number }): string {
|
|
37
|
+
return `(min-width: ${toEm(input.navbarWidth + input.asideWidth + MAIN_MIN_WIDTH)})`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The room-to-dock query's SSR/no-`matchMedia` fallback — an `'expanded'` `sizeClassHint` means the
|
|
42
|
+
* server already believes there is room, matching `useSizeClass`'s own hint-seeded guard
|
|
43
|
+
* (`use-size-class.ts`), so the first client paint doesn't fold a default-open aside for one commit.
|
|
44
|
+
*/
|
|
45
|
+
export function roomToDockServerFallback(sizeClassHint: SizeClass | undefined): boolean {
|
|
46
|
+
return sizeClassHint === 'expanded'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resolveAsideDocking(input: AsideDockingInput): AsideDocking {
|
|
50
|
+
const docks = input.sizeClass === 'expanded' && input.roomToDock
|
|
51
|
+
const open = input.claimed && !input.folded
|
|
52
|
+
const reserved = open && docks ? input.asideWidth : input.asideRailWidth
|
|
53
|
+
return { docks, overlay: open && !docks, width: input.claimed ? reserved : 0 }
|
|
54
|
+
}
|
package/src/shell/index.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import { blockRowCount, projectMobileNav } from './mobile-nav-model'
|
|
|
34
34
|
import { AppBreadcrumbs } from './app-breadcrumbs'
|
|
35
35
|
import { PageBarBandOutlet, PageBarOutlet, PageBarProvider, usePageKebabClaimed } from './page-bar'
|
|
36
36
|
import { AsideDocksContext, AsideOutlet, AsideProvider, useAsideRegion } from './page-aside'
|
|
37
|
+
import { asideDockQuery, resolveAsideDocking, roomToDockServerFallback } from './aside-docking'
|
|
37
38
|
import { OverflowMenu, globalActionAsBarAction, globalActionMobile } from '../controls/actions'
|
|
38
39
|
import type { GlobalAction } from '../controls/actions'
|
|
39
40
|
import type { AccountMenuItem, BasaltAccountProps } from './account-types'
|
|
@@ -47,9 +48,7 @@ import type {
|
|
|
47
48
|
} from './nav-types'
|
|
48
49
|
import { CtlSlot, useBasaltSpacing } from '../theme'
|
|
49
50
|
import { createPersistedStore } from '../state/persisted'
|
|
50
|
-
import { toEm } from '../tokens/size-classes'
|
|
51
51
|
import { SizeClassHintContext, useSizeClass } from './use-size-class'
|
|
52
|
-
import type { SizeClass } from './use-size-class'
|
|
53
52
|
import brandClasses from './app-brand.module.css'
|
|
54
53
|
import headerClasses from './app-header.module.css'
|
|
55
54
|
import mainClasses from './app-main.module.css'
|
|
@@ -303,25 +302,6 @@ function collapseStore(key: string): CollapseStore {
|
|
|
303
302
|
return store
|
|
304
303
|
}
|
|
305
304
|
|
|
306
|
-
/**
|
|
307
|
-
* Main keeps at least this much width, or an open aside overlays it instead of docking. Not
|
|
308
|
-
* derived from a token: it is the readable floor for a page body, independent of density.
|
|
309
|
-
*/
|
|
310
|
-
const MAIN_MIN_WIDTH = 720
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* The room-to-dock `min-width` query's SSR/no-`matchMedia` fallback — an `'expanded'`
|
|
314
|
-
* `sizeClassHint` means the server already believes there is room, matching `useSizeClass`'s own
|
|
315
|
-
* hint-seeded guard one level up (`use-size-class.ts`). Extracted as its own pure function (rather
|
|
316
|
-
* than the inline `sizeClassHint === 'expanded'` it replaces) purely so `index.test.tsx` can pin it
|
|
317
|
-
* without needing to render `BasaltShell` through a real SSR pass — `AppShell`'s + `PageAside`'s own
|
|
318
|
-
* effects/refs/portals make that path unobservable through rendered markup, and the previous
|
|
319
|
-
* hardcoded `false` fallback here shipped with no test at all for exactly that reason.
|
|
320
|
-
*/
|
|
321
|
-
export function roomToDockServerFallback(sizeClassHint: SizeClass | undefined): boolean {
|
|
322
|
-
return sizeClassHint === 'expanded'
|
|
323
|
-
}
|
|
324
|
-
|
|
325
305
|
/**
|
|
326
306
|
* The shell's two page-level regions are providers, and both wrap the frame rather than living
|
|
327
307
|
* inside it: `PageBarProvider` owns the header portal and the single-kebab claim, `AsideProvider`
|
|
@@ -395,25 +375,23 @@ function ShellFrame({
|
|
|
395
375
|
const isCollapseControlled = collapsedProp !== undefined
|
|
396
376
|
const uncontrolledCollapsed = collapseIsSet ? storedCollapsed : sizeClass === 'medium'
|
|
397
377
|
const collapsed = isCollapseControlled ? collapsedProp : uncontrolledCollapsed
|
|
398
|
-
// Aside docking (`
|
|
399
|
-
// `
|
|
400
|
-
// just its rail and an open panel overlays main. Provided through `AsideDocksContext` in the SAME
|
|
401
|
-
// render, so `PageAside` defaults its fold from the right value on its first pass.
|
|
378
|
+
// Aside docking (`aside-docking.ts`), provided through `AsideDocksContext` in the SAME render so
|
|
379
|
+
// `PageAside` defaults its fold from the right value on its first pass.
|
|
402
380
|
const navbarWidth = collapsed ? step.appShellNavbarRailWidth : step.appShellNavbarWidth
|
|
403
|
-
// The SSR/first-paint fallback mirrors `useSizeClass`'s own hint-seeded guard: an `expanded` hint
|
|
404
|
-
// from `<BasaltProvider sizeClassHint>` means the server already believes there is room to dock, so
|
|
405
|
-
// the first client paint agrees instead of defaulting to "cannot dock" and folding a default-open
|
|
406
|
-
// aside for one commit.
|
|
407
381
|
const sizeClassHint = useContext(SizeClassHintContext)
|
|
408
382
|
const roomToDock = useMediaQuery(
|
|
409
|
-
|
|
383
|
+
asideDockQuery({ navbarWidth, asideWidth: step.appShellAsideWidth }),
|
|
410
384
|
roomToDockServerFallback(sizeClassHint),
|
|
411
385
|
)
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
386
|
+
const docking = resolveAsideDocking({
|
|
387
|
+
claimed: aside.claimed,
|
|
388
|
+
folded: aside.folded,
|
|
389
|
+
sizeClass,
|
|
390
|
+
roomToDock,
|
|
391
|
+
asideWidth: step.appShellAsideWidth,
|
|
392
|
+
asideRailWidth: step.appShellAsideRailWidth,
|
|
393
|
+
})
|
|
394
|
+
const asideOverlay = docking.overlay
|
|
417
395
|
// Keyboard escape hatch for the overlay form only — the docked form never blocks content, so
|
|
418
396
|
// there is nothing to escape out of. `requestClose` is a stable callback from `AsideProvider`
|
|
419
397
|
// (destructured, not read as `aside.requestClose`, so the effect deps name it directly rather
|
|
@@ -460,7 +438,7 @@ function ShellFrame({
|
|
|
460
438
|
)
|
|
461
439
|
|
|
462
440
|
return (
|
|
463
|
-
<AsideDocksContext.Provider value={docks}>
|
|
441
|
+
<AsideDocksContext.Provider value={docking.docks}>
|
|
464
442
|
<AppShell
|
|
465
443
|
h="100dvh"
|
|
466
444
|
// Mantine's DEFAULT layout, not `alt`: the header spans the full viewport width on top and
|
|
@@ -484,7 +462,7 @@ function ShellFrame({
|
|
|
484
462
|
// prop for it on purpose; the ROUTE decides, the same way it decides its page bar.
|
|
485
463
|
aside={{
|
|
486
464
|
// An overlay reserves the rail only — `<AppShell.Aside>` below widens itself over main.
|
|
487
|
-
width:
|
|
465
|
+
width: docking.width,
|
|
488
466
|
breakpoint: 'sm',
|
|
489
467
|
// Below `sm` there is no region at all — `PageAside` renders its content in the page
|
|
490
468
|
// flow instead, one node, no responsive twin (law C9).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `useBreakpoint` — DEPRECATED (on notice in `MIGRATING.md`, removed
|
|
2
|
+
* `useBreakpoint` — DEPRECATED (on notice in `MIGRATING.md`, removed in 1.33.0): use
|
|
3
3
|
* `useSizeClass` from `./use-size-class`. One media-query hook over the theme's own breakpoints,
|
|
4
4
|
* built on the shared `useMediaQuery` (`common/use-media-query`) — SSR/hydration-safe.
|
|
5
5
|
*
|
|
@@ -21,7 +21,7 @@ import { useMediaQuery } from '../common/use-media-query'
|
|
|
21
21
|
export type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* @deprecated Use `useSizeClass` — removed
|
|
24
|
+
* @deprecated Use `useSizeClass` — removed in 1.33.0 (see `MIGRATING.md`). `theme.breakpoints` is
|
|
25
25
|
* now derived: `sm` moved 48em → 52.5em and `md`/`lg`/`xl` all sit at 75em (`xs`/`sm` collapse,
|
|
26
26
|
* `md`/`lg`/`xl` collapse).
|
|
27
27
|
*/
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* `.sheetBody` is the Modal body; it reaches the trailing button row `overlays.confirm`
|
|
43
43
|
* renders (Mantine `Group`), stretching the buttons to full width at the `--vx-hit` floor.
|
|
44
44
|
*/
|
|
45
|
+
/* theme-allow raw-media-query — the bottom sheet IS the compact size class's overlay form */
|
|
45
46
|
@media (max-width: 52.49375em) {
|
|
46
47
|
.sheetInner {
|
|
47
48
|
align-items: flex-end;
|
|
@@ -178,6 +178,8 @@ export function DeriveControls({ resetIcon }: DeriveControlsProps) {
|
|
|
178
178
|
<Text size="xs" fw={500} mb={4}>
|
|
179
179
|
Neutral family
|
|
180
180
|
</Text>
|
|
181
|
+
{/* theme-allow control-outside-home — a theme-lab setting (a settings row, a declared
|
|
182
|
+
non-home), not a page filter: it edits the derive config, never page data. */}
|
|
181
183
|
<SegmentedControl
|
|
182
184
|
size="xs"
|
|
183
185
|
fullWidth
|