basalt-ui 1.17.0 → 1.19.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 +1 -1
- package/CHANGELOG.md +14 -0
- package/agent/rules/basalt-charts.md +12 -0
- package/agent/rules/basalt-mantine.md +71 -6
- package/agent/rules/basalt-router.md +129 -30
- package/agent/rules/basalt-tokens.md +2 -1
- package/agent/skills/basalt-design/SKILL.md +2 -2
- package/dist/charts/kinds/DualPanel.d.ts +7 -0
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +10 -3
- package/dist/charts/primitives/CartesianChart.d.ts +17 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +5 -2
- package/dist/charts/primitives/ChartTooltip.d.ts +6 -1
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +2 -1
- package/dist/charts/series.d.ts +5 -0
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/nav/types.d.ts +110 -0
- package/dist/nav/types.d.ts.map +1 -0
- package/dist/nav/types.js +0 -0
- package/dist/router-tanstack/index.d.ts +9 -2
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +7 -0
- package/dist/router-tanstack/nav.d.ts +163 -0
- package/dist/router-tanstack/nav.d.ts.map +1 -0
- package/dist/router-tanstack/nav.js +38 -0
- package/dist/router-tanstack/nav.test.d.ts +2 -0
- package/dist/router-tanstack/nav.test.d.ts.map +1 -0
- package/dist/router-tanstack/search-param-store.d.ts +19 -9
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.d.ts +30 -0
- package/dist/router-tanstack/use-nav.d.ts.map +1 -0
- package/dist/router-tanstack/use-nav.js +71 -0
- package/dist/shell/app-breadcrumbs.d.ts +7 -9
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +8 -4
- package/dist/shell/app-mobile-nav.d.ts +23 -24
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +290 -68
- package/dist/shell/app-mobile-nav.module.css +166 -11
- package/dist/shell/app-mobile-nav.module.css.d.ts +11 -2
- package/dist/shell/app-mobile-nav.test.d.ts +2 -0
- package/dist/shell/app-mobile-nav.test.d.ts.map +1 -0
- package/dist/shell/app-sidebar.d.ts +3 -28
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +40 -174
- package/dist/shell/app-sidebar.module.css +3 -2
- package/dist/shell/app-sidebar.module.css.d.ts +0 -2
- package/dist/shell/index.d.ts +22 -65
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +38 -76
- package/dist/shell/mobile-nav-model.d.ts +41 -0
- package/dist/shell/mobile-nav-model.d.ts.map +1 -0
- package/dist/shell/mobile-nav-model.js +244 -0
- package/dist/shell/mobile-nav-model.test.d.ts +2 -0
- package/dist/shell/mobile-nav-model.test.d.ts.map +1 -0
- package/dist/shell/nav-count-badge.d.ts +11 -0
- package/dist/shell/nav-count-badge.d.ts.map +1 -0
- package/dist/shell/nav-count-badge.js +29 -0
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +8 -7
- package/dist/theme/nav-link.module.css +4 -2
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/palette.d.ts +15 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +13 -0
- package/dist/tokens.css +2 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/kinds/DualPanel.test.tsx +81 -1
- package/src/charts/kinds/DualPanel.tsx +26 -4
- package/src/charts/primitives/CartesianChart.test.tsx +173 -1
- package/src/charts/primitives/CartesianChart.tsx +29 -5
- package/src/charts/primitives/ChartTooltip.test.tsx +22 -1
- package/src/charts/primitives/ChartTooltip.tsx +7 -1
- package/src/charts/series.ts +5 -0
- package/src/index.ts +15 -5
- package/src/nav/types.ts +115 -0
- package/src/router-tanstack/index.ts +21 -2
- package/src/router-tanstack/nav.test.ts +111 -0
- package/src/router-tanstack/nav.ts +205 -0
- package/src/router-tanstack/search-param-store.ts +19 -9
- package/src/router-tanstack/use-nav.ts +146 -0
- package/src/shell/app-breadcrumbs.tsx +21 -19
- package/src/shell/app-mobile-nav.module.css +166 -11
- package/src/shell/app-mobile-nav.module.css.d.ts +11 -2
- package/src/shell/app-mobile-nav.test.tsx +369 -0
- package/src/shell/app-mobile-nav.tsx +436 -102
- package/src/shell/app-sidebar.module.css +3 -2
- package/src/shell/app-sidebar.module.css.d.ts +0 -2
- package/src/shell/app-sidebar.test.tsx +6 -10
- package/src/shell/app-sidebar.tsx +49 -206
- package/src/shell/index.test.tsx +62 -7
- package/src/shell/index.tsx +105 -164
- package/src/shell/mobile-nav-model.test.ts +296 -0
- package/src/shell/mobile-nav-model.ts +389 -0
- package/src/shell/nav-count-badge.tsx +39 -0
- package/src/surfaces.ts +1 -1
- package/src/theme/density-relations.test.ts +19 -1
- package/src/theme/index.ts +8 -7
- package/src/theme/nav-link.module.css +4 -2
- package/src/theme/spacing.test.ts +11 -0
- package/src/tokens/density.test.ts +2 -0
- package/src/tokens/index.ts +8 -5
- package/src/tokens/palette.ts +16 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "@mantine/core";
|
|
3
|
+
import { VX } from "../tokens/index.js";
|
|
4
|
+
function NavCountBadge({ count }) {
|
|
5
|
+
if (!count) return null;
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
Badge,
|
|
8
|
+
{
|
|
9
|
+
size: "sm",
|
|
10
|
+
styles: {
|
|
11
|
+
root: {
|
|
12
|
+
backgroundColor: "color-mix(in srgb, var(--vx-ink) 8%, transparent)",
|
|
13
|
+
color: "var(--vx-muted)",
|
|
14
|
+
fontFamily: "var(--basalt-font-mono)",
|
|
15
|
+
fontSize: VX.text.micro,
|
|
16
|
+
fontWeight: 500,
|
|
17
|
+
height: 16,
|
|
18
|
+
padding: "0 5px",
|
|
19
|
+
marginLeft: "auto",
|
|
20
|
+
borderRadius: "var(--vx-radius-tight)"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
children: count
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
NavCountBadge
|
|
29
|
+
};
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -209,7 +209,7 @@ export declare const SURFACES: {
|
|
|
209
209
|
readonly rule: "router";
|
|
210
210
|
readonly skill: readonly ["basalt-app"];
|
|
211
211
|
readonly guardKinds: readonly [];
|
|
212
|
-
readonly description: "TanStack Router bridge: useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)";
|
|
212
|
+
readonly description: "TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)";
|
|
213
213
|
readonly optionalPeers: readonly ["@tanstack/react-router"];
|
|
214
214
|
readonly globs: {
|
|
215
215
|
readonly shipped: readonly [];
|
package/dist/surfaces.js
CHANGED
|
@@ -120,7 +120,7 @@ const SURFACES = {
|
|
|
120
120
|
rule: "router",
|
|
121
121
|
skill: ["basalt-app"],
|
|
122
122
|
guardKinds: [],
|
|
123
|
-
description: "TanStack Router bridge: useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)",
|
|
123
|
+
description: "TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)",
|
|
124
124
|
optionalPeers: ["@tanstack/react-router"],
|
|
125
125
|
globs: {
|
|
126
126
|
shipped: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EACV,oBAAoB,EAGpB,oBAAoB,EAErB,MAAM,eAAe,CAAA;AAStB,OAAO,KAAK,EAAe,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAOlD;;;;;GAKG;AACH,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAIlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AASvD,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,iBAAiB;QACzB,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,WAAW,CAAC,EAAE,iBAAiB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,aAAa,CAAC,EAAE,WAAW,CAAA;KAC5B;CACF;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EACV,oBAAoB,EAGpB,oBAAoB,EAErB,MAAM,eAAe,CAAA;AAStB,OAAO,KAAK,EAAe,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAOlD;;;;;GAKG;AACH,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAIlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AASvD,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,iBAAiB;QACzB,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,WAAW,CAAC,EAAE,iBAAiB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,aAAa,CAAC,EAAE,WAAW,CAAA;KAC5B;CACF;AAm5BD;+EAC+E;AAC/E,eAAO,MAAM,SAAS,EAAE,oBAAuD,CAAA;AAmJ/E;;8FAE8F;AAC9F,eAAO,MAAM,oBAAoB,EAAE,oBACc,CAAA;AAEjD,kEAAkE;AAClE,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAA;IAEzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAYD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,CAAC,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CA+BtB"}
|
package/dist/theme/index.js
CHANGED
|
@@ -304,10 +304,10 @@ function buildTheme(data, options = {}) {
|
|
|
304
304
|
// panel-filled row read as a button sitting in the nav rather than as the current location.
|
|
305
305
|
// Selection is carried by the tint + ink text + weight 600 + the accent icon instead. Forced
|
|
306
306
|
// here at the THEME level via NavLink's `--nl-*` vars so it holds for every render path —
|
|
307
|
-
// including a consumer's router `<Link>`
|
|
308
|
-
// module; the active ICON is accent-colored via `nav-link.module.css`
|
|
309
|
-
// `[data-position='left']` leftSection, which the flat `vars`/`styles` API
|
|
310
|
-
// conditionally on `[data-active]`).
|
|
307
|
+
// including a consumer's router `<Link>` hosted through `SidebarItem.Anchor`, which never
|
|
308
|
+
// sees the shell CSS module; the active ICON is accent-colored via `nav-link.module.css`
|
|
309
|
+
// (targets the `[data-position='left']` leftSection, which the flat `vars`/`styles` API
|
|
310
|
+
// can't express conditionally on `[data-active]`).
|
|
311
311
|
//
|
|
312
312
|
// `--nl-hover` (ink-13%) only ever reaches an ACTIVE row in practice — the inactive hover is
|
|
313
313
|
// set at ink-6% by higher-specificity `:not([data-active])` rules in `nav-link.module.css` /
|
|
@@ -323,9 +323,10 @@ function buildTheme(data, options = {}) {
|
|
|
323
323
|
children: {}
|
|
324
324
|
}),
|
|
325
325
|
// Dense, rounded nav rows — applied at the THEME level so they survive EVERY render path,
|
|
326
|
-
// including a consumer's router `<Link>`
|
|
327
|
-
// module (the module's `.link` only reaches the shell's own fallback
|
|
328
|
-
// reasoning as the `--nl-*` fill above: layout is single-sourced here,
|
|
326
|
+
// including a consumer's router `<Link>` hosted through `SidebarItem.Anchor`, which never
|
|
327
|
+
// sees the shell CSS module (the module's `.link` only reaches the shell's own fallback
|
|
328
|
+
// `<NavLink>`). Same reasoning as the `--nl-*` fill above: layout is single-sourced here,
|
|
329
|
+
// not in two places.
|
|
329
330
|
// The active-weight/shadow/icon-accent state selectors can't live in `styles` (flat inline
|
|
330
331
|
// props only) — they're in nav-link.module.css, wired via `classNames` so they reach the
|
|
331
332
|
// same every-render-path scope.
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* tint rather than as a button, so the row never competes with the real filled controls around it.
|
|
6
6
|
*
|
|
7
7
|
* Applied at the THEME level via `classNames` so it reaches EVERY NavLink render path — including a
|
|
8
|
-
* consumer's router `<Link>` mounted through `
|
|
8
|
+
* consumer's router `<Link>` mounted through `SidebarItem.Anchor`, which a shell-scoped CSS
|
|
9
|
+
* module never
|
|
9
10
|
* sees. Mantine's `styles` prop only takes flat inline properties (a nested `&[data-active]` there
|
|
10
11
|
* throws a React warning), so the state selectors live here. React Router's NavLink marks the
|
|
11
12
|
* active item via `aria-current` rather than the `active` prop's `data-active`, so both states are
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
|
|
43
44
|
/*
|
|
44
45
|
* Inactive hover — the ink-6% ghost tint + ink text/icon (docs/DESIGN-SPEC.md §5). Lives here (not
|
|
45
|
-
* only in the shell module) so a consumer's `
|
|
46
|
+
* only in the shell module) so a consumer's `SidebarItem.Anchor` path gets the same hover;
|
|
47
|
+
* Mantine's own
|
|
46
48
|
* hover fill uses `:where(scheme) .m_*:hover` (gray-0/dark-6), which this outranks on specificity.
|
|
47
49
|
* Active rows keep their panel fill + `--nl-hover` untouched.
|
|
48
50
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAMvE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,WAAW,CAAA;AAOjC,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAM5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAO7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,wFAAwF;AACxF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,8FAA8F;AAC9F,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,GAAG,MAAM,KAAG,MACoB,CAAA;AAsCrE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyIL,CAAA;AAEV,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE;IACjC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,WAAW,CAUd;AAOD;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAExC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens/index.js
CHANGED
|
@@ -357,6 +357,8 @@ function spaceDecls(space) {
|
|
|
357
357
|
decl("space-sidebar-child-row-indent", `${space.step.sidebarChildRowIndent}px`),
|
|
358
358
|
decl("space-app-header-mobile-actions-height", `${space.step.appHeaderMobileActionsHeight}px`),
|
|
359
359
|
decl("space-mobile-nav-tab-gap", `${space.step.mobileNavTabGap}px`),
|
|
360
|
+
decl("space-mobile-nav-icon-size", `${space.step.mobileNavIconSize}px`),
|
|
361
|
+
decl("space-mobile-nav-row-height", `${space.step.mobileNavRowHeight}px`),
|
|
360
362
|
decl("space-agent-rail-inset-x", `${space.step.agentRailInsetX}px`),
|
|
361
363
|
decl("space-agent-part-gap-top", `${space.step.agentPartGapTop}px`),
|
|
362
364
|
decl("space-agent-code-inset", `${space.step.agentCodeInset}px`),
|
package/dist/tokens/palette.d.ts
CHANGED
|
@@ -495,6 +495,17 @@ declare const SPACE_STEP_BASE: {
|
|
|
495
495
|
readonly appShellNavbarRailWidth: 48;
|
|
496
496
|
/** Tab's icon-to-label gap. */
|
|
497
497
|
readonly mobileNavTabGap: 3;
|
|
498
|
+
/** Bar height, EXCLUDING the safe-area inset — Mantine's own `AppShell.footer` rule adds
|
|
499
|
+
* `env(safe-area-inset-bottom)` to it. JS-consumed (AppShell `footer.height`), no `--vx-*` var,
|
|
500
|
+
* same shape as `appShellHeaderHeight`. Floored at 48px in `deriveSpacing`. */
|
|
501
|
+
readonly mobileNavBarHeight: 56;
|
|
502
|
+
/** Slot icon box. */
|
|
503
|
+
readonly mobileNavIconSize: 24;
|
|
504
|
+
/** Menu/sheet row height. Floored at 44px in `deriveSpacing` (Apple HIG 44pt / WCAG 2.5.5 AAA). */
|
|
505
|
+
readonly mobileNavRowHeight: 44;
|
|
506
|
+
/** More/group `<Menu width={…}>`. JS-consumed, no var — same rationale as
|
|
507
|
+
* `sidebarSettingsMenuWidth` directly above. */
|
|
508
|
+
readonly mobileNavMenuWidth: 232;
|
|
498
509
|
/** `ChartLegend`'s column/row gap (`VX.legendGap`). */
|
|
499
510
|
readonly chartLegendGap: 22;
|
|
500
511
|
/** Chart plot-area margin, top (`VX.margin.top`). */
|
|
@@ -870,6 +881,10 @@ export declare const SPACE_STEP: {
|
|
|
870
881
|
readonly appShellNavbarWidth: number;
|
|
871
882
|
readonly appShellNavbarRailWidth: number;
|
|
872
883
|
readonly mobileNavTabGap: number;
|
|
884
|
+
readonly mobileNavBarHeight: number;
|
|
885
|
+
readonly mobileNavIconSize: number;
|
|
886
|
+
readonly mobileNavRowHeight: number;
|
|
887
|
+
readonly mobileNavMenuWidth: number;
|
|
873
888
|
readonly chartLegendGap: number;
|
|
874
889
|
readonly chartMarginTop: number;
|
|
875
890
|
readonly chartMarginRight: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"palette.d.ts","sourceRoot":"","sources":["../../src/tokens/palette.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"palette.d.ts","sourceRoot":"","sources":["../../src/tokens/palette.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens/palette.js
CHANGED
|
@@ -410,6 +410,17 @@ const SPACE_STEP_BASE = {
|
|
|
410
410
|
// ── shell/app-mobile-nav.module.css ───────────────────────────────────────────────────────────
|
|
411
411
|
/** Tab's icon-to-label gap. */
|
|
412
412
|
mobileNavTabGap: 3,
|
|
413
|
+
/** Bar height, EXCLUDING the safe-area inset — Mantine's own `AppShell.footer` rule adds
|
|
414
|
+
* `env(safe-area-inset-bottom)` to it. JS-consumed (AppShell `footer.height`), no `--vx-*` var,
|
|
415
|
+
* same shape as `appShellHeaderHeight`. Floored at 48px in `deriveSpacing`. */
|
|
416
|
+
mobileNavBarHeight: 56,
|
|
417
|
+
/** Slot icon box. */
|
|
418
|
+
mobileNavIconSize: 24,
|
|
419
|
+
/** Menu/sheet row height. Floored at 44px in `deriveSpacing` (Apple HIG 44pt / WCAG 2.5.5 AAA). */
|
|
420
|
+
mobileNavRowHeight: 44,
|
|
421
|
+
/** More/group `<Menu width={…}>`. JS-consumed, no var — same rationale as
|
|
422
|
+
* `sidebarSettingsMenuWidth` directly above. */
|
|
423
|
+
mobileNavMenuWidth: 232,
|
|
413
424
|
// ── charts (tokens/index.ts's `VX` object — structural, tracks density) ──────────────────────
|
|
414
425
|
// `VX.lineWidth`/`VX.line2Width` are DELIBERATELY absent here — they are stroke WEIGHTS (line
|
|
415
426
|
// thickness), not spacing/gaps/sizes, so they stay fixed regardless of density (see `VX` in
|
|
@@ -515,6 +526,8 @@ function deriveSpacing(level) {
|
|
|
515
526
|
anchors.stackXl = stackUnit * 6;
|
|
516
527
|
const mappedStep = mapSpaceGroup(SPACE_STEP_BASE, level, multiplier);
|
|
517
528
|
mappedStep.sidebarSearchTriggerHeight = Math.max(24, mappedStep.sidebarSearchTriggerHeight);
|
|
529
|
+
mappedStep.mobileNavBarHeight = Math.max(48, mappedStep.mobileNavBarHeight);
|
|
530
|
+
mappedStep.mobileNavRowHeight = Math.max(44, mappedStep.mobileNavRowHeight);
|
|
518
531
|
const step = {
|
|
519
532
|
...mappedStep,
|
|
520
533
|
// RESPONSIVE, one per AppShell breakpoint — each DERIVED from its own header, plus
|
package/dist/tokens.css
CHANGED
|
@@ -95,6 +95,8 @@
|
|
|
95
95
|
--vx-space-sidebar-child-row-indent: 12px;
|
|
96
96
|
--vx-space-app-header-mobile-actions-height: 52px;
|
|
97
97
|
--vx-space-mobile-nav-tab-gap: 3px;
|
|
98
|
+
--vx-space-mobile-nav-icon-size: 24px;
|
|
99
|
+
--vx-space-mobile-nav-row-height: 44px;
|
|
98
100
|
--vx-space-agent-rail-inset-x: 10px;
|
|
99
101
|
--vx-space-agent-part-gap-top: 6px;
|
|
100
102
|
--vx-space-agent-code-inset: 8px;
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.
|
|
1
|
+
# basalt-ui v1.19.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.
|
|
22
|
+
Version: 1.19.0
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
|
@@ -65,7 +65,7 @@ OptionalPeers: @tanstack/react-query-devtools@^5.101.0
|
|
|
65
65
|
|
|
66
66
|
## basalt-ui/router-tanstack
|
|
67
67
|
Import: import { ... } from 'basalt-ui/router-tanstack'
|
|
68
|
-
Description: TanStack Router bridge: useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)
|
|
68
|
+
Description: TanStack Router bridge: defineNav/navGroup/navTarget (one typed nav definition) + useNav (sections + mobileNav, spread onto BasaltShell) + useBasaltNav (active route) + useRouterBreadcrumbs + createSearchParamStore (single-select URL-state store) + createMultiSearchParamStore (multi-select URL-state store)
|
|
69
69
|
Layer: headless
|
|
70
70
|
Rule: basalt-router
|
|
71
71
|
OptionalPeers: @tanstack/react-router@^1.170.0
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `Bars.test.tsx`) — DualPanel has two `HoverOverlay`s sharing one cursor, but only the TOP one is
|
|
7
7
|
* keyboard-focusable, so `getByRole('slider')` is unambiguous.
|
|
8
8
|
*/
|
|
9
|
-
import { fireEvent, render, screen } from '@testing-library/react'
|
|
9
|
+
import { fireEvent, render, screen, within } from '@testing-library/react'
|
|
10
10
|
import { describe, expect, test } from 'bun:test'
|
|
11
11
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
12
12
|
import { ChartCursorScope } from '../cursor/scope'
|
|
@@ -439,3 +439,83 @@ describe('DualPanel — cursorResolution threads through to sibling resolution',
|
|
|
439
439
|
expect(foldedSlider.getAttribute('aria-valuetext')).toBe('08.08')
|
|
440
440
|
})
|
|
441
441
|
})
|
|
442
|
+
|
|
443
|
+
describe('DualPanel — onFollow (follower behaviour parity with CartesianChart)', () => {
|
|
444
|
+
type SourceRow = { date: string; shown: number }
|
|
445
|
+
const sourceRows: SourceRow[] = [{ date: '2026-08-01', shown: 10 }]
|
|
446
|
+
const sourceSeries: ChartSeries<SourceRow>[] = [
|
|
447
|
+
{ key: 'shown', label: 'Shown', color: '#111', mark: 'line', getValue: (d) => d.shown },
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
function renderPair(onFollow?: boolean, followerRows: Row[] = rows) {
|
|
451
|
+
render(
|
|
452
|
+
<ChartCursorScope>
|
|
453
|
+
<CartesianChart<SourceRow>
|
|
454
|
+
data={sourceRows}
|
|
455
|
+
chartId="dp-of-source"
|
|
456
|
+
getX={(d) => d.date}
|
|
457
|
+
series={sourceSeries}
|
|
458
|
+
ariaLabel="Source"
|
|
459
|
+
legend={false}
|
|
460
|
+
>
|
|
461
|
+
{() => null}
|
|
462
|
+
</CartesianChart>
|
|
463
|
+
<DualPanel<Row>
|
|
464
|
+
data={followerRows}
|
|
465
|
+
chartId="dp-of-follower"
|
|
466
|
+
getX={(d) => d.date}
|
|
467
|
+
series={series}
|
|
468
|
+
getBar={(d) => d.bar}
|
|
469
|
+
barLabel="Bar"
|
|
470
|
+
barColorPositive="#0a0"
|
|
471
|
+
barColorNegative="#a00"
|
|
472
|
+
formatTop={(v) => String(v)}
|
|
473
|
+
formatBottom={(v) => v.toFixed(2)}
|
|
474
|
+
ariaLabel="Follower"
|
|
475
|
+
legend={false}
|
|
476
|
+
{...(onFollow !== undefined && { onFollow })}
|
|
477
|
+
/>
|
|
478
|
+
</ChartCursorScope>,
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
test('onFollow: true renders the DualPanel tooltip while a sibling owns the cursor', async () => {
|
|
483
|
+
renderPair(true)
|
|
484
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
485
|
+
|
|
486
|
+
expect(await screen.findByText('V')).toBeTruthy()
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
test('without onFollow (default false), DualPanel renders no tooltip as a follower', async () => {
|
|
490
|
+
renderPair()
|
|
491
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
492
|
+
|
|
493
|
+
// The SOURCE still renders its own tooltip — only the DualPanel follower stays silent.
|
|
494
|
+
expect(await screen.findByText('Shown')).toBeTruthy()
|
|
495
|
+
expect(screen.queryByText('V')).toBeNull()
|
|
496
|
+
expect(screen.queryAllByRole('tooltip')).toHaveLength(1)
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
test('the follower tooltip is not aria-live; the source is', async () => {
|
|
500
|
+
renderPair(true)
|
|
501
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
502
|
+
await screen.findByText('V')
|
|
503
|
+
|
|
504
|
+
const tooltips = screen.getAllByRole('tooltip')
|
|
505
|
+
expect(tooltips).toHaveLength(2)
|
|
506
|
+
const sourceTooltip = tooltips.find((t) => within(t).queryByText('Shown') !== null)
|
|
507
|
+
const followerTooltip = tooltips.find((t) => within(t).queryByText('V') !== null)
|
|
508
|
+
expect(sourceTooltip?.getAttribute('aria-live')).toBe('polite')
|
|
509
|
+
expect(followerTooltip?.hasAttribute('aria-live')).toBe(false)
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
test('a follower whose domain never resolves the broadcast key renders no tooltip shell', async () => {
|
|
513
|
+
const foreignRows: Row[] = [{ date: '2027-01-15', v: 99, bar: 0.5 }]
|
|
514
|
+
renderPair(true, foreignRows)
|
|
515
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
516
|
+
|
|
517
|
+
expect(await screen.findByText('Shown')).toBeTruthy()
|
|
518
|
+
expect(screen.queryByText('V')).toBeNull()
|
|
519
|
+
expect(screen.queryAllByRole('tooltip')).toHaveLength(1)
|
|
520
|
+
})
|
|
521
|
+
})
|
|
@@ -4,7 +4,7 @@ import { Group } from '@visx/group'
|
|
|
4
4
|
import { scaleLinear, scalePoint } from '@visx/scale'
|
|
5
5
|
import { Bar, LinePath } from '@visx/shape'
|
|
6
6
|
import { Threshold } from '@visx/threshold'
|
|
7
|
-
import { memo, useMemo } from 'react'
|
|
7
|
+
import { memo, useMemo, useRef } from 'react'
|
|
8
8
|
import type { ReactNode } from 'react'
|
|
9
9
|
import { AxisBottomDate, AxisLeftNumeric } from '../primitives/Axes'
|
|
10
10
|
import {
|
|
@@ -84,6 +84,13 @@ export type DualPanelProps<T> = {
|
|
|
84
84
|
* alongside the hovered datum.
|
|
85
85
|
*/
|
|
86
86
|
formatHeader?: (key: string, d: T) => string
|
|
87
|
+
/**
|
|
88
|
+
* Render this chart's tooltip when it is a cursor FOLLOWER, not only when it is the SOURCE —
|
|
89
|
+
* same seam and same anchored-positioning behaviour as `CartesianTooltipConfig.onFollow`. Default
|
|
90
|
+
* false (today's behaviour). Only the SOURCE's tooltip is `aria-live`; a follower's is visual
|
|
91
|
+
* only.
|
|
92
|
+
*/
|
|
93
|
+
onFollow?: boolean
|
|
87
94
|
/** Legend config forwarded to `ChartFrame`; `false` disables the legend (sparkline escape).
|
|
88
95
|
* Default `{ placement: 'bottom' }`. */
|
|
89
96
|
legend?: ChartLegendConfig | false
|
|
@@ -175,6 +182,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
175
182
|
bottomMaxAbsFloor,
|
|
176
183
|
tooltipLabel,
|
|
177
184
|
formatHeader,
|
|
185
|
+
onFollow,
|
|
178
186
|
plot,
|
|
179
187
|
} = props
|
|
180
188
|
|
|
@@ -424,13 +432,27 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
424
432
|
[visibleSeries, lineValid, xScale, topYScale, getX],
|
|
425
433
|
)
|
|
426
434
|
|
|
435
|
+
const svgRef = useRef<SVGSVGElement>(null)
|
|
427
436
|
const point = cursor.point
|
|
428
437
|
const sx = point !== null ? (xScale(getX(point)) ?? 0) : 0
|
|
429
438
|
const syncedBar = point !== null ? getBar(point) : null
|
|
430
439
|
|
|
440
|
+
// Same seam as `CartesianChart`: this chart renders its tooltip as the cursor SOURCE (always) or,
|
|
441
|
+
// opted in via `onFollow`, as a FOLLOWER. A follower has no pointer under it, so it is always
|
|
442
|
+
// positioned via the anchored (crosshair) path — `cursor.anchor` stays null on a chart nobody is
|
|
443
|
+
// hovering, so following it here would render nothing.
|
|
444
|
+
const isFollowerRender = !cursor.isSource && onFollow === true
|
|
445
|
+
const showTooltip = point !== null && (cursor.isSource || isFollowerRender)
|
|
446
|
+
const anchorX = isFollowerRender && point !== null ? sx : null
|
|
447
|
+
const svgRect = anchorX === null ? undefined : svgRef.current?.getBoundingClientRect()
|
|
448
|
+
const tooltipAnchor =
|
|
449
|
+
svgRect === undefined || anchorX === null
|
|
450
|
+
? cursor.anchor
|
|
451
|
+
: { x: svgRect.left + margin.left + anchorX, y: svgRect.top + margin.top }
|
|
452
|
+
|
|
431
453
|
return (
|
|
432
454
|
<>
|
|
433
|
-
<svg width={plot.width} height={plot.height}>
|
|
455
|
+
<svg ref={svgRef} width={plot.width} height={plot.height}>
|
|
434
456
|
{/* Top pane: line series + fill-between + zones + ref lines. */}
|
|
435
457
|
<Group left={margin.left} top={margin.top}>
|
|
436
458
|
<GridRows scale={topYScale} width={xMax} stroke={VX.grid} numTicks={4} />
|
|
@@ -593,8 +615,8 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
593
615
|
)}
|
|
594
616
|
</svg>
|
|
595
617
|
|
|
596
|
-
{
|
|
597
|
-
<ChartTooltipFloat anchor={cursor.
|
|
618
|
+
{showTooltip && (
|
|
619
|
+
<ChartTooltipFloat anchor={tooltipAnchor} ariaLive={cursor.isSource}>
|
|
598
620
|
<TooltipHeader
|
|
599
621
|
date={getX(point)}
|
|
600
622
|
{...(formatHeader !== undefined && {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from '@testing-library/react'
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react'
|
|
2
2
|
import { describe, expect, test } from 'bun:test'
|
|
3
3
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
4
|
import { ChartCursorScope } from '../cursor/scope'
|
|
@@ -267,3 +267,175 @@ describe('tooltip extraRows — ctx.visible/ctx.hidden track legend toggling', (
|
|
|
267
267
|
expect((await screen.findByTestId('extra-ctx')).textContent).toContain('visible:a|hidden:b')
|
|
268
268
|
})
|
|
269
269
|
})
|
|
270
|
+
|
|
271
|
+
describe('tooltip.onFollow — the follower renders when a SIBLING owns the cursor', () => {
|
|
272
|
+
function renderPair(onFollow?: boolean) {
|
|
273
|
+
render(
|
|
274
|
+
<ChartCursorScope>
|
|
275
|
+
<CartesianChart<Row>
|
|
276
|
+
data={rows}
|
|
277
|
+
chartId="of-source"
|
|
278
|
+
getX={(d) => d.date}
|
|
279
|
+
series={[seriesFor('a')]}
|
|
280
|
+
ariaLabel="Source"
|
|
281
|
+
legend={false}
|
|
282
|
+
>
|
|
283
|
+
{() => null}
|
|
284
|
+
</CartesianChart>
|
|
285
|
+
<CartesianChart<Row>
|
|
286
|
+
data={rows}
|
|
287
|
+
chartId="of-follower"
|
|
288
|
+
getX={(d) => d.date}
|
|
289
|
+
series={[seriesFor('b')]}
|
|
290
|
+
ariaLabel="Follower"
|
|
291
|
+
legend={false}
|
|
292
|
+
tooltip={{ follow: false, ...(onFollow !== undefined && { onFollow }) }}
|
|
293
|
+
>
|
|
294
|
+
{() => null}
|
|
295
|
+
</CartesianChart>
|
|
296
|
+
</ChartCursorScope>,
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
test('onFollow: true renders the follower tooltip rows while the source owns the cursor', async () => {
|
|
301
|
+
renderPair(true)
|
|
302
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
303
|
+
|
|
304
|
+
expect(await screen.findByText('b')).toBeTruthy()
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
test('without onFollow (default false), the follower renders no tooltip at all', async () => {
|
|
308
|
+
renderPair()
|
|
309
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
310
|
+
|
|
311
|
+
// The SOURCE still renders its own tooltip ('a') — only the follower ('b') stays silent.
|
|
312
|
+
expect(await screen.findByText('a')).toBeTruthy()
|
|
313
|
+
expect(screen.queryByText('b')).toBeNull()
|
|
314
|
+
expect(screen.queryAllByRole('tooltip')).toHaveLength(1)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// `follow` is deliberately left at its default (true, pointer-tracking) here — unlike every
|
|
318
|
+
// other case in this file, which hardcodes `follow: false` on the follower. That hardcoding
|
|
319
|
+
// means `useAnchoredPosition`'s FIRST clause (`tooltipCfg?.follow === false`) always already
|
|
320
|
+
// satisfies the anchored branch, so those tests can't tell `|| isFollowerRender` apart from a
|
|
321
|
+
// no-op. With `follow` left at its pointer-tracking default, anchoring can only come from
|
|
322
|
+
// `isFollowerRender` — and a follower's own `cursor.anchor` (set only by pointer/keyboard
|
|
323
|
+
// events ON THIS CHART, never by a sibling's) stays null for the whole test, since only the
|
|
324
|
+
// SOURCE is ever interacted with. So: `isFollowerRender` present → anchored position resolves
|
|
325
|
+
// from the crosshair and the tooltip mounts; `isFollowerRender` absent → the pointer-tracking
|
|
326
|
+
// branch falls through to the follower's own (never-set) `cursor.anchor`, which is null, and
|
|
327
|
+
// `ChartTooltipFloat` renders nothing at all for a null anchor. Presence/absence of the
|
|
328
|
+
// follower tooltip is what's observable here — happy-dom zeroes every `getBoundingClientRect`,
|
|
329
|
+
// so the anchor's actual pixel coordinates can't discriminate the two branches.
|
|
330
|
+
test('onFollow: true with follow left at its pointer-tracking default still anchors to the crosshair', async () => {
|
|
331
|
+
render(
|
|
332
|
+
<ChartCursorScope>
|
|
333
|
+
<CartesianChart<Row>
|
|
334
|
+
data={rows}
|
|
335
|
+
chartId="of-anchor-source"
|
|
336
|
+
getX={(d) => d.date}
|
|
337
|
+
series={[seriesFor('a')]}
|
|
338
|
+
ariaLabel="Source"
|
|
339
|
+
legend={false}
|
|
340
|
+
>
|
|
341
|
+
{() => null}
|
|
342
|
+
</CartesianChart>
|
|
343
|
+
<CartesianChart<Row>
|
|
344
|
+
data={rows}
|
|
345
|
+
chartId="of-anchor-follower"
|
|
346
|
+
getX={(d) => d.date}
|
|
347
|
+
series={[seriesFor('b')]}
|
|
348
|
+
ariaLabel="Follower"
|
|
349
|
+
legend={false}
|
|
350
|
+
tooltip={{ onFollow: true }}
|
|
351
|
+
>
|
|
352
|
+
{() => null}
|
|
353
|
+
</CartesianChart>
|
|
354
|
+
</ChartCursorScope>,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
358
|
+
|
|
359
|
+
expect(await screen.findByText('b')).toBeTruthy()
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
describe('tooltip.onFollow — aria-live stays SOURCE-only', () => {
|
|
364
|
+
test('the source tooltip is aria-live; the follower tooltip is not', async () => {
|
|
365
|
+
render(
|
|
366
|
+
<ChartCursorScope>
|
|
367
|
+
<CartesianChart<Row>
|
|
368
|
+
data={rows}
|
|
369
|
+
chartId="al-source"
|
|
370
|
+
getX={(d) => d.date}
|
|
371
|
+
series={[seriesFor('a')]}
|
|
372
|
+
ariaLabel="Source"
|
|
373
|
+
legend={false}
|
|
374
|
+
>
|
|
375
|
+
{() => null}
|
|
376
|
+
</CartesianChart>
|
|
377
|
+
<CartesianChart<Row>
|
|
378
|
+
data={rows}
|
|
379
|
+
chartId="al-follower"
|
|
380
|
+
getX={(d) => d.date}
|
|
381
|
+
series={[seriesFor('b')]}
|
|
382
|
+
ariaLabel="Follower"
|
|
383
|
+
legend={false}
|
|
384
|
+
tooltip={{ follow: false, onFollow: true }}
|
|
385
|
+
>
|
|
386
|
+
{() => null}
|
|
387
|
+
</CartesianChart>
|
|
388
|
+
</ChartCursorScope>,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
392
|
+
await screen.findByText('b')
|
|
393
|
+
|
|
394
|
+
const tooltips = screen.getAllByRole('tooltip')
|
|
395
|
+
expect(tooltips).toHaveLength(2)
|
|
396
|
+
const sourceTooltip = tooltips.find((t) => within(t).queryByText('a') !== null)
|
|
397
|
+
const followerTooltip = tooltips.find((t) => within(t).queryByText('b') !== null)
|
|
398
|
+
expect(sourceTooltip?.getAttribute('aria-live')).toBe('polite')
|
|
399
|
+
expect(followerTooltip?.hasAttribute('aria-live')).toBe(false)
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
describe('tooltip.onFollow — a follower whose domain never resolves the broadcast key renders nothing', () => {
|
|
404
|
+
test('an unrelated calendar stays silent, not an empty tooltip shell', async () => {
|
|
405
|
+
const foreignRows: Row[] = [{ date: '2027-01-15', a: 99, b: 99 }]
|
|
406
|
+
|
|
407
|
+
render(
|
|
408
|
+
<ChartCursorScope>
|
|
409
|
+
<CartesianChart<Row>
|
|
410
|
+
data={rows}
|
|
411
|
+
chartId="unres-source"
|
|
412
|
+
getX={(d) => d.date}
|
|
413
|
+
series={[seriesFor('a')]}
|
|
414
|
+
ariaLabel="Source"
|
|
415
|
+
legend={false}
|
|
416
|
+
>
|
|
417
|
+
{() => null}
|
|
418
|
+
</CartesianChart>
|
|
419
|
+
<CartesianChart<Row>
|
|
420
|
+
data={foreignRows}
|
|
421
|
+
chartId="unres-follower"
|
|
422
|
+
getX={(d) => d.date}
|
|
423
|
+
series={[seriesFor('b')]}
|
|
424
|
+
ariaLabel="Follower"
|
|
425
|
+
legend={false}
|
|
426
|
+
tooltip={{ follow: false, onFollow: true }}
|
|
427
|
+
>
|
|
428
|
+
{() => null}
|
|
429
|
+
</CartesianChart>
|
|
430
|
+
</ChartCursorScope>,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
fireEvent.keyDown(screen.getByRole('slider', { name: 'Source' }), { key: 'ArrowRight' })
|
|
434
|
+
|
|
435
|
+
// The SOURCE still renders its own tooltip — only the follower with no resolvable point stays
|
|
436
|
+
// silent, rather than mounting an empty tooltip shell.
|
|
437
|
+
expect(await screen.findByText('a')).toBeTruthy()
|
|
438
|
+
expect(screen.queryByText('b')).toBeNull()
|
|
439
|
+
expect(screen.queryAllByRole('tooltip')).toHaveLength(1)
|
|
440
|
+
})
|
|
441
|
+
})
|