basalt-ui 1.32.0 → 1.32.1
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 +11 -0
- package/MIGRATING.md +124 -16
- package/agent/rules/basalt-mantine.md +2 -2
- package/configs/oxlint-plugin.js +92 -28
- package/configs/oxlint-plugin.test.ts +147 -1
- package/dist/data/data-table.d.ts +9 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/forms/form-layout.d.ts +6 -1
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.module.css +3 -0
- package/dist/guard/index.d.ts +2 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +4 -9
- package/dist/shell/page-bar.d.ts +24 -0
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +4 -0
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/data/data-table.tsx +9 -2
- package/src/forms/form-layout.module.css +3 -0
- package/src/forms/form-layout.tsx +6 -1
- package/src/guard/index.ts +15 -16
- package/src/shell/page-bar.tsx +24 -0
- package/src/tokens/index.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## [1.32.1](https://github.com/jkrumm/basalt-ui/compare/v1.32.0...v1.32.1) (2026-09-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* align guard lanes on FormRow/FormGroup, drop basalt-main ([17e673d](https://github.com/jkrumm/basalt-ui/commit/17e673d421c3e2f93be01c2b8d24e10e56d5b71c))
|
|
7
|
+
* bottom-align FormGroup's row direction so a trailing button isn't stretched ([e5ca2ab](https://github.com/jkrumm/basalt-ui/commit/e5ca2ab5e433e4e3ef15f505913c672b4912f0e9))
|
|
8
|
+
* export the size- and container-class tables from basalt-ui/tokens ([14fb784](https://github.com/jkrumm/basalt-ui/commit/14fb784586c3348a3647280e74ea905b2df0091b))
|
|
9
|
+
* point every § Unreleased reference at the section the release names ([bc17710](https://github.com/jkrumm/basalt-ui/commit/bc1771001048f2883607d6917e6f94cc397865be))
|
|
10
|
+
* resolve consts before unwrapping raw-breakpoint conditionals ([4f9cc79](https://github.com/jkrumm/basalt-ui/commit/4f9cc7912ae0805eb5e574f8ed8a8f9c068e0ddf))
|
|
11
|
+
|
|
1
12
|
# [1.32.0](https://github.com/jkrumm/basalt-ui/compare/v1.31.0...v1.32.0) (2026-09-30)
|
|
2
13
|
|
|
3
14
|
|
package/MIGRATING.md
CHANGED
|
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
|
|
|
66
66
|
shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
|
|
67
67
|
consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
|
|
68
68
|
|
|
69
|
-
| Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0
|
|
70
|
-
| ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ |
|
|
71
|
-
| **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§
|
|
72
|
-
| **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§
|
|
73
|
-
| **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§
|
|
74
|
-
| **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | —
|
|
75
|
-
| **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | —
|
|
76
|
-
| **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | —
|
|
77
|
-
| **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | —
|
|
78
|
-
| **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | —
|
|
79
|
-
| **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | —
|
|
80
|
-
| **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | —
|
|
69
|
+
| Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | Unreleased |
|
|
70
|
+
| ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
71
|
+
| **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | — |
|
|
72
|
+
| **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — |
|
|
73
|
+
| **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — |
|
|
74
|
+
| **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | — |
|
|
75
|
+
| **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — |
|
|
76
|
+
| **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — |
|
|
77
|
+
| **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — |
|
|
78
|
+
| **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — |
|
|
79
|
+
| **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — |
|
|
80
|
+
| **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | — |
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
@@ -86,6 +86,114 @@ consumer read this index, found nothing, and diffed a KPI row that had silently
|
|
|
86
86
|
_Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
|
|
87
87
|
release renames it and opens a new one here._
|
|
88
88
|
|
|
89
|
+
## 1.32.1 — the 1.32.0 recipe resolves, and the gaps consumers hit upgrading to it
|
|
90
|
+
|
|
91
|
+
A patch: no new API beyond restoring what 1.31.0/1.32.0 already documented. Eight consumers
|
|
92
|
+
upgraded to 1.32.0; this section is what they had to find out by reading source.
|
|
93
|
+
|
|
94
|
+
### Fixes you can now delete a workaround for
|
|
95
|
+
|
|
96
|
+
| What | Delete |
|
|
97
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| **`SIZE_CLASSES`, `CONTAINER_CLASSES`, `resolveContainerClass`, `ContainerClass` are exported from `basalt-ui/tokens`.** The 1.31.0/1.32.0 sections, the `raw-breakpoint` message and the `BasaltDataTable` JSDoc all named that import; it did not resolve. A test now fails the build when a fenced import in this file names a symbol its subpath does not export. | the local `container-classes.ts` mirror or the literal `'480px'`/`800` — import them |
|
|
99
|
+
| **`FormRow` / `FormGroup` are control homes in `check-theme` too.** The oxlint plugin already treated them as homes; the text lane's `raw-selection-control` did not, so a correctly migrated form row warned (and would have errored at 1.33.0). A parity test now pins both lanes' home lists to one set. | every `theme-allow raw-selection-control — … FormRow/FormGroup …` waiver (grep `WORKAROUND: homed in a FormRow`) |
|
|
100
|
+
| **`basalt-main` is no longer an accepted `@container` name.** Nothing ever declared it, so a query against it never matched. Declare your own container (`container: <name> / inline-size` on the element) or use `basalt-grid` on the parent. | `@container basalt-main …` — rewrite against a container you declare |
|
|
101
|
+
| **The `raw-media-query` message no longer over-claims.** Only `basalt-card` and unnamed `@container` queries are judged against the 0/240/480/800 table; any other declared name is accepted at any width. Same behaviour, honest text. | — |
|
|
102
|
+
| **`raw-breakpoint` exempts a `tests?/` directory** (e.g. a `tests/setup/dom.ts` `matchMedia` polyfill), not only `*.test.*`/`__tests__`. The message now says `cols={{ base: 3 }}` is just `cols={3}`, and a `(hover: none)` read gets a pointer-tier hint. | the polyfill's `theme-allow raw-breakpoint` |
|
|
103
|
+
| **`FormGroup direction="row"` no longer stretches a trailing button** (`align-items: flex-end`). | a `style={{ alignSelf: … }}` on the button |
|
|
104
|
+
|
|
105
|
+
**This patch also TIGHTENS two checks — expect NEW warnings after upgrading, still `warn` until
|
|
106
|
+
1.33.0:** `raw-breakpoint` now sees a responsive object inside a conditional, logical or
|
|
107
|
+
const-resolved prop value (`span={wide ? { base: 12, md: 4 } : 12}` used to slip through), and
|
|
108
|
+
`raw-media-query` rejects `@container basalt-main`. (The `FormRow`/`FormGroup` change only removes
|
|
109
|
+
warnings.)
|
|
110
|
+
|
|
111
|
+
**After any guard narrows, audit your waivers:** `basalt-ui check-theme --audit-allows` lists every
|
|
112
|
+
`theme-allow` and whether it still suppresses anything. Dead ones (rb carried five
|
|
113
|
+
`in-body-page-title`, linewatch one `bound-control-outside-home`) go.
|
|
114
|
+
|
|
115
|
+
**Scope — the breakpoint law is Mantine/CSS-scoped.** `raw-breakpoint` is an oxlint rule over JSX
|
|
116
|
+
and `raw-media-query` a `check-theme` kind over `.css`. A tokens-only (Tailwind) consumer has only
|
|
117
|
+
the `check-theme` lane; nothing here asks it to adopt `type="container"` grids.
|
|
118
|
+
|
|
119
|
+
### Container recipes 1.32.0 left implicit
|
|
120
|
+
|
|
121
|
+
**Swap two children at a container width in ONE query** — narrow default, flip both sides inside
|
|
122
|
+
one `min-width` rule. A `max-width: 799.9px` / `min-width: 800px` pair leaves a sub-pixel band where
|
|
123
|
+
both or neither render.
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
.root {
|
|
127
|
+
container: basalt-grid / inline-size;
|
|
128
|
+
}
|
|
129
|
+
.wide {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
@container basalt-grid (min-width: 800px) {
|
|
133
|
+
.narrow {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
.wide {
|
|
137
|
+
display: block;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**A plain Mantine `Table` (not `BasaltDataTable`)** has no column fold or card projection; the
|
|
143
|
+
sanctioned remedy is the same CSS module — declare the container on the wrapper and hide columns or
|
|
144
|
+
swap to a list inside a `min-width` query. `BasaltDataTable` needs the optional
|
|
145
|
+
`@tanstack/react-table` peer; don't add it only to get the fold.
|
|
146
|
+
|
|
147
|
+
**Layouts that need a step above `wide` (800px).** There is no fifth class; one ships when two named
|
|
148
|
+
consumers need the same width. Until then, verified against the 1.32.0 plugin:
|
|
149
|
+
|
|
150
|
+
| Where | Off-class width (e.g. `1200px`) |
|
|
151
|
+
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| `SimpleGrid type="container"` px key — `cols={{ base: 1, '1200px': 5 }}` | passes: `raw-breakpoint` does not judge px keys, only theme names (`sm`) |
|
|
153
|
+
| `Grid type="container"` `breakpoints` map value — `xl: '1200px'` | passes: the map's values are not judged |
|
|
154
|
+
| `@container basalt-card` or unnamed, in CSS | flagged — `theme-allow raw-media-query — <why this layout needs 1200px>` |
|
|
155
|
+
| `@container <your-own-name>` or `basalt-grid`, in CSS | passes today; a later minor may judge `basalt-grid` — prefer a reasoned `theme-allow` over relying on the unjudged name |
|
|
156
|
+
|
|
157
|
+
Either way, write the reason next to the width: it is the evidence the fifth class is waiting on.
|
|
158
|
+
|
|
159
|
+
**`ResponsiveChartHeight` `sm` (768px) → which key.** `wide` (800) is 32px later and keeps the old
|
|
160
|
+
step's meaning ("tablet or wider"); `regular` (480) opens 288px earlier. Pick by which side of the old
|
|
161
|
+
width the height was for.
|
|
162
|
+
|
|
163
|
+
### `BasaltDataTable` `renderCard` (1.32.0, undocumented here until now)
|
|
164
|
+
|
|
165
|
+
The law lives in the prop's JSDoc (`src/data/data-table.tsx`, shipped in `dist/data/table.d.ts`).
|
|
166
|
+
The points consumers tripped on:
|
|
167
|
+
|
|
168
|
+
- Swaps at the table's OWN width `< CONTAINER_CLASSES.regular` (480px), not the viewport.
|
|
169
|
+
- Card order is the table's processed order — sort, filter, page. For a domain order, pass
|
|
170
|
+
`initialSorting` or sort `data`; there is no second sort.
|
|
171
|
+
- `minWidth` disables the COLUMN fold (it declares a horizontal floor) and is ignored by the card
|
|
172
|
+
list. `stickyHeader` alone keeps the fold; pair it with `maxHeight` to stick at every width.
|
|
173
|
+
- Activation is click or **Enter** — not Space (the browser's page scroll on a focused non-button).
|
|
174
|
+
|
|
175
|
+
### Shell-less apps and the 1.31.0 breakpoint move
|
|
176
|
+
|
|
177
|
+
1.31.0 moved `sm` from 48em to **52.5em** (840px). What its checklist missed:
|
|
178
|
+
|
|
179
|
+
- **Re-key your own literals:** grep `.css` for `47.99375em` / `48em` → `52.49375em` / `52.5em`.
|
|
180
|
+
- **A shell-less app's gutter / sticky-bar CSS belongs under your own `shell/` directory** at
|
|
181
|
+
52.5em: any `shell/`-pathed CSS may write a width `@media` at a `SIZE_CLASSES` boundary; anywhere
|
|
182
|
+
else it is `raw-media-query`.
|
|
183
|
+
- **A consumer-owned Mantine `AppShell`** resolves `navbar.breakpoint` / `aside.breakpoint` through
|
|
184
|
+
`theme.breakpoints`, so `breakpoint: 'sm'` collapses at 840px now, not 768px.
|
|
185
|
+
- **In the `medium` class the sidebar defaults to the rail**, and the rail hides a `kind: 'custom'`
|
|
186
|
+
block entirely (a list block keeps a dot only with both `icon` and `count`). Anything a
|
|
187
|
+
`medium`-width user needs must not live only in a custom block.
|
|
188
|
+
- **`PageBar` without a shell:** see the shell-less example in its JSDoc — `title` leads the one
|
|
189
|
+
sticky bar, and `classNames.root` is where the gutter bleed goes.
|
|
190
|
+
|
|
191
|
+
### `useSizeClass()` outside the shell — two sanctioned uses
|
|
192
|
+
|
|
193
|
+
Layout stays a container decision; an overlay's form (`Drawer` side, sheet vs dialog) and content
|
|
194
|
+
paired with a `ViewTabs` option `only: 'sm-down'` may read it. Law and wording:
|
|
195
|
+
`agent/rules/basalt-mantine.md` (a shipped rule, placed by `basalt-ui sync`).
|
|
196
|
+
|
|
89
197
|
## 1.32.0 — `basalt/raw-breakpoint` gets its answer
|
|
90
198
|
|
|
91
199
|
### The C1 pair (`control-outside-home` / `raw-selection-control`) — `error` at 1.33.0, the last extension
|
|
@@ -105,7 +213,7 @@ minor with a documented answer). A `theme-allow raw-breakpoint — <reason>` wai
|
|
|
105
213
|
|
|
106
214
|
**Its CSS twin, the `raw-media-query` guard kind** (`basalt-ui check-theme`), enforces the same law
|
|
107
215
|
in `.css`: a width `@media` outside `shell/**`, an `@container` name other than `basalt-card` /
|
|
108
|
-
`basalt-form` / `basalt-
|
|
216
|
+
`basalt-form` / `basalt-grid` (`basalt-main` was listed here too; dropped in 1.32.1, nothing declared it) or at a width off a `CONTAINER_CLASSES` boundary, and
|
|
109
217
|
a pointer query other than `coarse`/`fine`. Same `warn`, and it moved with the rule — **`error` at
|
|
110
218
|
1.33.0** (was 1.32.0). `theme-allow raw-media-query — <reason>` waives one line.
|
|
111
219
|
|
|
@@ -406,7 +514,7 @@ exactly the case it was restored for. Measured: a 7-entry legend in a 92–150px
|
|
|
406
514
|
entries at `maxRows` 3, 6 **and** 99, with five series drawn in colours nothing named.
|
|
407
515
|
|
|
408
516
|
The fit now resolves an ABSENT cap only. When a caller states a number on a `fill` band, the legend
|
|
409
|
-
takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see §
|
|
517
|
+
takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § 1.30.1) and
|
|
410
518
|
**`VX.minPlotHeight` stops being a floor** — the plot yields the height instead. Both things that used to trim the number are framework DEFAULTS (the tier's cap,
|
|
411
519
|
and a floor about the plot); the stated number is the only intent in the room.
|
|
412
520
|
|
|
@@ -571,7 +679,7 @@ children is invisible to every rule. Grep for `SimpleGrid` before concluding the
|
|
|
571
679
|
### Charts — a responsive `height`, and the legend escape 1.30.0 promised
|
|
572
680
|
|
|
573
681
|
Both of these landed in 1.30.0 with no row here, and both were only partly reachable. **Read this
|
|
574
|
-
section against 1.30.1, not 1.30.0** — the §
|
|
682
|
+
section against 1.30.1, not 1.30.0** — the § 1.30.1 entries above are what makes them usable.
|
|
575
683
|
|
|
576
684
|
- **`height` accepts `{ base, sm?, md?, lg? }`** as well as a plain number, resolved against the
|
|
577
685
|
chart's MEASURED container width (the same width the tier reads), not the viewport. In 1.30.0 the
|
|
@@ -583,7 +691,7 @@ section against 1.30.1, not 1.30.0** — the § Unreleased entries above are wha
|
|
|
583
691
|
measured box, so a 380px inspector panel on a 1440px desktop IS "a phone" and its two-row default
|
|
584
692
|
rolled a six-series legend up to `+4 more` with no way to say no. `margin` and `xLabelRotate`
|
|
585
693
|
always had that escape; the legend did not. Under `fill` a measured cap still applies on top — see
|
|
586
|
-
§
|
|
694
|
+
§ 1.30.1 for what that actually costs, because "can still roll up FURTHER" understated it.
|
|
587
695
|
|
|
588
696
|
### Toolchain — the shipped lefthook preset's `check-theme`, and what it does NOT fix
|
|
589
697
|
|
|
@@ -97,8 +97,8 @@ Depth-tier law (which token for which surface, why static, why the ring is inset
|
|
|
97
97
|
clears the iOS zoom threshold via an `!important` floor (`sub-16-input-font` flags a smaller one).
|
|
98
98
|
- **Charts measure themselves** — pass `height`/`aspectRatio`/`fill` only; never `useElementSize`
|
|
99
99
|
in a chart file (Mantine is banned there), never raw `@visx/responsive` outside `charts/**`.
|
|
100
|
-
- **A page's
|
|
101
|
-
|
|
100
|
+
- **A page's layout keys on its container, not the viewport** (`type="container"` grids, `CONTAINER_CLASSES`).
|
|
101
|
+
`useSizeClass()`: shell chrome, overlay form, `ViewTabs only: 'sm-down'` partners. Recipes: `MIGRATING.md`.
|
|
102
102
|
|
|
103
103
|
## Scroll regions
|
|
104
104
|
|
package/configs/oxlint-plugin.js
CHANGED
|
@@ -390,8 +390,12 @@ function hasFileDeclaration(context, ruleId) {
|
|
|
390
390
|
)
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/**
|
|
394
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Test/spec files — design guidance does not apply to a fixture. Mirrors `src/cli`'s own SKIP, plus
|
|
395
|
+
* a `test/`/`tests/` directory (1.32.1): a test-setup polyfill (`tests/setup/dom.ts` stubbing
|
|
396
|
+
* `window.matchMedia`) is neither a `.test` file nor under `__tests__`, and needed a waiver.
|
|
397
|
+
*/
|
|
398
|
+
const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])(?:__tests__|tests?)[\\/]/
|
|
395
399
|
|
|
396
400
|
function isTestFile(context) {
|
|
397
401
|
return TEST_FILE.test(getFilename(context))
|
|
@@ -3972,26 +3976,57 @@ function isResponsiveObjectValue(node, namedOnly = false) {
|
|
|
3972
3976
|
return !namedOnly || names.some((name) => name !== 'base')
|
|
3973
3977
|
}
|
|
3974
3978
|
|
|
3979
|
+
/** The `const` initializers {@link reachableObjects} can resolve an Identifier to. */
|
|
3980
|
+
const RESOLVABLE_INIT_TYPES = new Set([
|
|
3981
|
+
'ObjectExpression',
|
|
3982
|
+
'ConditionalExpression',
|
|
3983
|
+
'LogicalExpression',
|
|
3984
|
+
'Identifier',
|
|
3985
|
+
])
|
|
3986
|
+
|
|
3975
3987
|
/**
|
|
3976
|
-
*
|
|
3977
|
-
*
|
|
3978
|
-
*
|
|
3979
|
-
*
|
|
3980
|
-
*
|
|
3981
|
-
* `ObjectExpression` left the Identifier-in-a-property shape trivially bypassable.
|
|
3988
|
+
* Every object literal a value can evaluate to: an Identifier resolves through `consts` (the
|
|
3989
|
+
* same-file name→init map) FIRST, then a conditional contributes both branches and a logical both
|
|
3990
|
+
* sides — in any nesting, so `const span = wide ? { base: 12, md: 4 } : 12` then `span={span}` is
|
|
3991
|
+
* the same shape as writing the conditional inline (rb's hero Grid was a false negative until
|
|
3992
|
+
* 1.32.1). `seen` guards an Identifier cycle (`const a = b`, `const b = a`) and a shared object.
|
|
3982
3993
|
*/
|
|
3983
|
-
function
|
|
3984
|
-
const resolved =
|
|
3985
|
-
|
|
3986
|
-
if (resolved === null || resolved === undefined || resolved.type !== 'ObjectExpression')
|
|
3987
|
-
return false
|
|
3988
|
-
if (seen.has(resolved)) return false
|
|
3994
|
+
function reachableObjects(node, consts, seen = new Set()) {
|
|
3995
|
+
const resolved = node?.type === 'Identifier' ? consts.get(node.name) : node
|
|
3996
|
+
if (resolved === null || resolved === undefined || seen.has(resolved)) return []
|
|
3989
3997
|
seen.add(resolved)
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3998
|
+
switch (resolved.type) {
|
|
3999
|
+
case 'ObjectExpression':
|
|
4000
|
+
return [resolved]
|
|
4001
|
+
case 'Identifier':
|
|
4002
|
+
return reachableObjects(resolved, consts, seen)
|
|
4003
|
+
case 'ConditionalExpression':
|
|
4004
|
+
return [resolved.consequent, resolved.alternate].flatMap((branch) =>
|
|
4005
|
+
reachableObjects(branch, consts, seen),
|
|
4006
|
+
)
|
|
4007
|
+
case 'LogicalExpression':
|
|
4008
|
+
return [resolved.left, resolved.right].flatMap((side) => reachableObjects(side, consts, seen))
|
|
4009
|
+
default:
|
|
4010
|
+
return []
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
/**
|
|
4015
|
+
* Does this prop value CONTAIN a responsive object — any object it can evaluate to
|
|
4016
|
+
* ({@link reachableObjects}), or one nested one level down in a property of one
|
|
4017
|
+
* (`footer={{ height: { base: 60, sm: 0 } }}`, Mantine's own AppShell idiom), the property value
|
|
4018
|
+
* resolving the same way. Matching only a nested `ObjectExpression` left the Identifier-in-a-
|
|
4019
|
+
* property shape trivially bypassable.
|
|
4020
|
+
*/
|
|
4021
|
+
function containsResponsiveObjectValue(node, consts, namedOnly = false, seen = new Set()) {
|
|
4022
|
+
return reachableObjects(node, consts, seen).some(
|
|
4023
|
+
(object) =>
|
|
4024
|
+
isResponsiveObjectValue(object, namedOnly) ||
|
|
4025
|
+
object.properties.some(
|
|
4026
|
+
(prop) =>
|
|
4027
|
+
prop.type === 'Property' &&
|
|
4028
|
+
containsResponsiveObjectValue(prop.value, consts, namedOnly, seen),
|
|
4029
|
+
),
|
|
3995
4030
|
)
|
|
3996
4031
|
}
|
|
3997
4032
|
|
|
@@ -4108,6 +4143,33 @@ const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
|
|
|
4108
4143
|
'Responsive-object prop keyed on theme breakpoints — those are viewport widths. ' +
|
|
4109
4144
|
`${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4110
4145
|
|
|
4146
|
+
/** rb r2 #5: a `(hover: none)` read was flagged with advice that only covered widths. */
|
|
4147
|
+
const RAW_BREAKPOINT_POINTER_HINT =
|
|
4148
|
+
'A hover/pointer capability read belongs in CSS: @media (pointer: coarse) in a module (the ' +
|
|
4149
|
+
'query --vx-hit keys on) — (hover: none) is not a sanctioned query.'
|
|
4150
|
+
|
|
4151
|
+
const RAW_BREAKPOINT_BASE_ONLY_MESSAGE =
|
|
4152
|
+
'Responsive-object prop with only a base key — there is no breakpoint in it at all, so drop the ' +
|
|
4153
|
+
'object and write the value: cols={{ base: 3 }} → cols={3}. (basalt/raw-breakpoint)'
|
|
4154
|
+
|
|
4155
|
+
/**
|
|
4156
|
+
* Is every responsive object this value can evaluate to ({@link reachableObjects} — the same
|
|
4157
|
+
* unwrap detection uses) one carrying ONLY `base`? That is no breakpoint decision, and the viewport
|
|
4158
|
+
* recipe is the wrong advice for it — the fix is the bare value. Message-only: the shape still
|
|
4159
|
+
* reports, as it always has.
|
|
4160
|
+
*/
|
|
4161
|
+
function isBaseOnlyResponsiveObject(node, consts) {
|
|
4162
|
+
const responsive = reachableObjects(node, consts).filter((object) =>
|
|
4163
|
+
isResponsiveObjectValue(object),
|
|
4164
|
+
)
|
|
4165
|
+
return (
|
|
4166
|
+
responsive.length > 0 &&
|
|
4167
|
+
responsive.every((object) =>
|
|
4168
|
+
object.properties.every((prop) => propertyKeyName(prop) === 'base'),
|
|
4169
|
+
)
|
|
4170
|
+
)
|
|
4171
|
+
}
|
|
4172
|
+
|
|
4111
4173
|
const RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE =
|
|
4112
4174
|
'Theme breakpoint name under type="container" — Mantine does not resolve it there: ' +
|
|
4113
4175
|
"SimpleGrid uses each key verbatim as a (min-width: …) length, so 'sm' is a dead query, " +
|
|
@@ -4121,11 +4183,12 @@ const RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE =
|
|
|
4121
4183
|
const RAW_BREAKPOINT_HOOK_MESSAGE =
|
|
4122
4184
|
"useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's " +
|
|
4123
4185
|
'one seam. Shell chrome reads useSizeClass(); a page swapping its own layout (a table for a card ' +
|
|
4124
|
-
`list, a column count) is a container decision. ${
|
|
4186
|
+
`list, a column count) is a container decision. ${RAW_BREAKPOINT_POINTER_HINT} ` +
|
|
4187
|
+
`${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4125
4188
|
|
|
4126
4189
|
const RAW_BREAKPOINT_GLOBAL_MESSAGE =
|
|
4127
4190
|
'window.matchMedia/window.innerWidth read directly — the viewport read useSizeClass() owns ' +
|
|
4128
|
-
`for shell chrome. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4191
|
+
`for shell chrome. ${RAW_BREAKPOINT_POINTER_HINT} ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4129
4192
|
|
|
4130
4193
|
/** The three viewport hooks the law names, whatever module they are imported from. */
|
|
4131
4194
|
const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useViewportSize'])
|
|
@@ -4168,8 +4231,9 @@ const rawBreakpoint = {
|
|
|
4168
4231
|
let definesShellHome = false
|
|
4169
4232
|
const visibleHiddenCandidates = []
|
|
4170
4233
|
const responsiveObjectCandidates = []
|
|
4171
|
-
// Same-file `const NAME = { base, sm }` bindings
|
|
4172
|
-
//
|
|
4234
|
+
// Same-file `const NAME = { base, sm }` bindings (or a conditional/logical/alias init, see
|
|
4235
|
+
// RESOLVABLE_INIT_TYPES), resolved when a JSX attribute or a nested property passes the
|
|
4236
|
+
// Identifier — no scope manager here, so this is a flat name→init map.
|
|
4173
4237
|
// A name declared more than once in the file is ambiguous (its two bindings cannot be told
|
|
4174
4238
|
// apart), so it is dropped from the map entirely rather than guessed at last-wins.
|
|
4175
4239
|
const responsiveObjectConsts = new Map()
|
|
@@ -4217,7 +4281,7 @@ const rawBreakpoint = {
|
|
|
4217
4281
|
return
|
|
4218
4282
|
}
|
|
4219
4283
|
seenDeclaratorNames.add(name)
|
|
4220
|
-
if (node.init?.type
|
|
4284
|
+
if (RESOLVABLE_INIT_TYPES.has(node.init?.type)) responsiveObjectConsts.set(name, node.init)
|
|
4221
4285
|
},
|
|
4222
4286
|
JSXAttribute(node) {
|
|
4223
4287
|
const name = node.name?.name
|
|
@@ -4264,10 +4328,10 @@ const rawBreakpoint = {
|
|
|
4264
4328
|
if (!containsResponsiveObjectValue(value, responsiveObjectConsts, mode !== undefined))
|
|
4265
4329
|
continue
|
|
4266
4330
|
if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4331
|
+
let message = RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE
|
|
4332
|
+
if (mode !== undefined) message = RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE
|
|
4333
|
+
else if (isBaseOnlyResponsiveObject(value, responsiveObjectConsts))
|
|
4334
|
+
message = RAW_BREAKPOINT_BASE_ONLY_MESSAGE
|
|
4271
4335
|
context.report({ node, message })
|
|
4272
4336
|
}
|
|
4273
4337
|
for (const node of visibleHiddenCandidates) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* enabling every rule this file tests. `run` shells the workspace-root `node_modules/.bin/oxlint`
|
|
8
8
|
* binary and returns the parsed set of `basalt/<rule>` diagnostics it printed.
|
|
9
9
|
*/
|
|
10
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
10
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test'
|
|
11
11
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
12
12
|
import { tmpdir } from 'node:os'
|
|
13
13
|
import { dirname, resolve } from 'node:path'
|
|
@@ -100,12 +100,37 @@ function run(
|
|
|
100
100
|
writeFileSync(filePath, source)
|
|
101
101
|
const result = Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', filename], { cwd: dir })
|
|
102
102
|
const output = `${result.stdout}${result.stderr}`
|
|
103
|
+
// A plugin that fails to load must fail the run, not read as "no findings" — otherwise every
|
|
104
|
+
// `not.toContain` below is false-green.
|
|
105
|
+
if (/Failed to (?:load JS plugin|parse oxlint configuration)/.test(output))
|
|
106
|
+
throw new Error(`oxlint could not load the basalt plugin:\n${output}`)
|
|
103
107
|
const rules = new Set(
|
|
104
108
|
[...output.matchAll(/\(basalt\/([\w-]+)\)/g)].map((match) => match[1] as string),
|
|
105
109
|
)
|
|
106
110
|
return { code: result.exitCode ?? 0, rules, output }
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
// The sentinel: one fixture every working plugin reports. If it does not, the plugin is not loaded
|
|
114
|
+
// (or not registered under `basalt/`) and nothing else in this file means anything.
|
|
115
|
+
beforeAll(() => {
|
|
116
|
+
const sentinelDir = mkdtempSync(resolve(tmpdir(), 'basalt-oxlint-sentinel-'))
|
|
117
|
+
try {
|
|
118
|
+
writeFileSync(
|
|
119
|
+
resolve(sentinelDir, '.oxlintrc.json'),
|
|
120
|
+
JSON.stringify({ jsPlugins: [PLUGIN_PATH], rules: { 'basalt/no-raw-font-size': 'error' } }),
|
|
121
|
+
)
|
|
122
|
+
writeFileSync(resolve(sentinelDir, 'sentinel.tsx'), 'export const C = () => <Text fz={10} />\n')
|
|
123
|
+
const result = Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', 'sentinel.tsx'], {
|
|
124
|
+
cwd: sentinelDir,
|
|
125
|
+
})
|
|
126
|
+
const output = `${result.stdout}${result.stderr}`
|
|
127
|
+
if (!output.includes('(basalt/no-raw-font-size)'))
|
|
128
|
+
throw new Error(`basalt oxlint plugin did not load — sentinel rule never fired:\n${output}`)
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(sentinelDir, { recursive: true, force: true })
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
109
134
|
// ── no-raw-font-size ─────────────────────────────────────────────────────────
|
|
110
135
|
|
|
111
136
|
describe('basalt/no-raw-font-size', () => {
|
|
@@ -3723,6 +3748,127 @@ describe('basalt/raw-breakpoint', () => {
|
|
|
3723
3748
|
)
|
|
3724
3749
|
expect(rules).not.toContain('raw-breakpoint')
|
|
3725
3750
|
})
|
|
3751
|
+
|
|
3752
|
+
// rb r2 #11: the object sat inside a conditional, so the rule never saw it.
|
|
3753
|
+
it('flags a responsive object inside a conditional prop value (Grid.Col, no container)', () => {
|
|
3754
|
+
const { rules } = run(
|
|
3755
|
+
`import { Grid } from '@mantine/core'\n` +
|
|
3756
|
+
`export const C = ({ wide }: { wide: boolean }) => (\n` +
|
|
3757
|
+
` <Grid><Grid.Col span={wide ? { base: 12, md: 4 } : 12}>x</Grid.Col></Grid>\n)\n`,
|
|
3758
|
+
)
|
|
3759
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3760
|
+
})
|
|
3761
|
+
|
|
3762
|
+
// Review r1: the unwrap ran before const resolution, so hoisting the conditional bypassed it.
|
|
3763
|
+
it('flags a conditional responsive object behind a same-file const', () => {
|
|
3764
|
+
const { rules } = run(
|
|
3765
|
+
`import { Grid } from '@mantine/core'\n` +
|
|
3766
|
+
`export const C = ({ wide }: { wide: boolean }) => {\n` +
|
|
3767
|
+
` const span = wide ? { base: 12, md: 4 } : 12\n` +
|
|
3768
|
+
` return <Grid><Grid.Col span={span}>x</Grid.Col></Grid>\n}\n`,
|
|
3769
|
+
)
|
|
3770
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3771
|
+
})
|
|
3772
|
+
|
|
3773
|
+
it('flags through a const alias chain and survives an alias cycle', () => {
|
|
3774
|
+
const chained = run(
|
|
3775
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3776
|
+
`const base = { base: 1, sm: 2 }\nconst cols = base\n` +
|
|
3777
|
+
`export const C = () => <SimpleGrid cols={cols} />\n`,
|
|
3778
|
+
)
|
|
3779
|
+
expect(chained.rules).toContain('raw-breakpoint')
|
|
3780
|
+
const cyclic = run(
|
|
3781
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3782
|
+
`const a = b\nconst b = a\n` +
|
|
3783
|
+
`export const C = () => <SimpleGrid cols={a} />\n`,
|
|
3784
|
+
)
|
|
3785
|
+
expect(cyclic.rules).not.toContain('raw-breakpoint')
|
|
3786
|
+
})
|
|
3787
|
+
|
|
3788
|
+
it('flags a responsive object on either side of a logical prop value', () => {
|
|
3789
|
+
const { rules } = run(
|
|
3790
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3791
|
+
`export const C = ({ c }: { c?: number }) => <SimpleGrid cols={c ?? { base: 1, sm: 2 }} />\n`,
|
|
3792
|
+
)
|
|
3793
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3794
|
+
})
|
|
3795
|
+
|
|
3796
|
+
// rb r2 #13: a base-only object is no breakpoint at all — the message says to write the value.
|
|
3797
|
+
it('tells a base-only responsive object to write the bare value', () => {
|
|
3798
|
+
const { rules, output } = run(
|
|
3799
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3800
|
+
`export const C = () => <SimpleGrid cols={{ base: 3 }} />\n`,
|
|
3801
|
+
)
|
|
3802
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3803
|
+
expect(output).toContain('cols={3}')
|
|
3804
|
+
expect(output).not.toContain('keyed on theme breakpoints')
|
|
3805
|
+
})
|
|
3806
|
+
|
|
3807
|
+
it('gives the base-only message through a const-bound conditional too', () => {
|
|
3808
|
+
const { output } = run(
|
|
3809
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3810
|
+
`export const C = ({ d }: { d: boolean }) => {\n` +
|
|
3811
|
+
` const cols = d ? { base: 3 } : 2\n return <SimpleGrid cols={cols} />\n}\n`,
|
|
3812
|
+
)
|
|
3813
|
+
expect(output).toContain('cols={3}')
|
|
3814
|
+
expect(output).not.toContain('keyed on theme breakpoints')
|
|
3815
|
+
})
|
|
3816
|
+
|
|
3817
|
+
it('keeps the viewport message when any branch carries a real breakpoint', () => {
|
|
3818
|
+
const { output } = run(
|
|
3819
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3820
|
+
`export const C = ({ d }: { d: boolean }) => <SimpleGrid cols={d ? { base: 3 } : { base: 1, sm: 2 }} />\n`,
|
|
3821
|
+
)
|
|
3822
|
+
expect(output).toContain('keyed on theme breakpoints')
|
|
3823
|
+
})
|
|
3824
|
+
|
|
3825
|
+
// rb r2 #5.
|
|
3826
|
+
it('points a useMediaQuery read at the (pointer: coarse) CSS home', () => {
|
|
3827
|
+
const { output } = run(
|
|
3828
|
+
`import { useMediaQuery } from '@mantine/hooks'\n` +
|
|
3829
|
+
`export const useTouch = () => useMediaQuery('(hover: none)')\n`,
|
|
3830
|
+
)
|
|
3831
|
+
expect(output).toContain('@media (pointer: coarse)')
|
|
3832
|
+
})
|
|
3833
|
+
|
|
3834
|
+
// obsidian r2 #7: a test-setup polyfill lives in `tests/`, not in a `.test` file.
|
|
3835
|
+
it.each(['tests/setup/dom.ts', 'test/setup.ts', 'src/__tests__/setup.ts'])(
|
|
3836
|
+
'does NOT flag a matchMedia stub under %s',
|
|
3837
|
+
(filename) => {
|
|
3838
|
+
const { rules } = run(`window.matchMedia = () => ({ matches: false })\n`, filename)
|
|
3839
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3840
|
+
},
|
|
3841
|
+
)
|
|
3842
|
+
|
|
3843
|
+
it('still flags a file whose name merely contains "test" outside a test dir', () => {
|
|
3844
|
+
const { rules } = run(`const mq = window.matchMedia('(max-width: 600px)')\n`, 'src/latest/a.ts')
|
|
3845
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3846
|
+
})
|
|
3847
|
+
})
|
|
3848
|
+
|
|
3849
|
+
// ── control homes: text lane ≡ plugin ────────────────────────────────────────
|
|
3850
|
+
|
|
3851
|
+
// 1.32.1 (B2): the plugin homed FormRow/FormGroup while check-theme's `raw-selection-control` did
|
|
3852
|
+
// not, so a correctly migrated form reported in one lane only. Both lists are module-private (the
|
|
3853
|
+
// guard is dependency-free, the plugin cannot import TS), so this reads them out of the source.
|
|
3854
|
+
describe('control homes — lane parity', () => {
|
|
3855
|
+
const pluginSource = readFileSync(PLUGIN_PATH, 'utf8')
|
|
3856
|
+
const guardSource = readFileSync(resolve(import.meta.dirname, '../src/guard/index.ts'), 'utf8')
|
|
3857
|
+
|
|
3858
|
+
const pluginSet = (name: string): string[] => {
|
|
3859
|
+
const body = pluginSource.match(new RegExp(`const ${name} = new Set\\(\\[([^\\]]*)\\]`))?.[1]
|
|
3860
|
+
if (body === undefined) throw new Error(`${name} not found in oxlint-plugin.js`)
|
|
3861
|
+
return [...body.matchAll(/'([^']+)'/g)].map((m) => m[1] as string)
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
it('check-theme CONTROL_HOST_TAG names exactly CONTROL_HOST_TAGS ∪ BASALT_HOST_TAGS', () => {
|
|
3865
|
+
const alternation = guardSource.match(/const CONTROL_HOST_TAG =\s*\/<\(\?:([^)]*)\)/)?.[1]
|
|
3866
|
+
if (alternation === undefined) throw new Error('CONTROL_HOST_TAG not found in guard/index.ts')
|
|
3867
|
+
const textLane = alternation.split('|').map((tag) => tag.replaceAll('\\.', '.'))
|
|
3868
|
+
const pluginLane = [...pluginSet('CONTROL_HOST_TAGS'), ...pluginSet('BASALT_HOST_TAGS')]
|
|
3869
|
+
expect(pluginLane).toContain('FormRow')
|
|
3870
|
+
expect(textLane.toSorted()).toEqual(pluginLane.toSorted())
|
|
3871
|
+
})
|
|
3726
3872
|
})
|
|
3727
3873
|
|
|
3728
3874
|
// ── the deprecation ledger (B4) ──────────────────────────────────────────────
|
|
@@ -287,7 +287,8 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
|
|
|
287
287
|
* list can never disagree with its table; a fixed card order is `initialSorting`, not a second
|
|
288
288
|
* sort. Everything around the body stays: the header (title · count · toolbar), the bulk bar,
|
|
289
289
|
* the pager, and `query` / `isLoading` / `emptyState`. `onRowActivate` makes each card
|
|
290
|
-
* activatable exactly as it does a row (click, or Enter on the focused card
|
|
290
|
+
* activatable exactly as it does a row (click, or Enter on the focused card — never Space, the
|
|
291
|
+
* browser's own page scroll on a focused non-button). The selection
|
|
291
292
|
* checkbox is not projected — a card list with a selection keeps its bulk bar but no ticks.
|
|
292
293
|
*
|
|
293
294
|
* An activatable card takes `tabIndex=0` + `data-activatable`, never `role="button"`: a card is
|
|
@@ -295,7 +296,9 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
|
|
|
295
296
|
* descendant presentational, so those nested controls would vanish from the accessibility tree.
|
|
296
297
|
* The affordance is the cursor and the focus ring, as on the row. `maxHeight` caps the card list
|
|
297
298
|
* in a `ScrollArea`; `minWidth` does not apply — it is a horizontal floor for columns, and a
|
|
298
|
-
* card list has none.
|
|
299
|
+
* card list has none. `stickyHeader` has nothing to stick while the cards render. A card order
|
|
300
|
+
* with no user sort is `data`'s own order — sort it there (or `initialSorting`) for a domain
|
|
301
|
+
* order.
|
|
299
302
|
*
|
|
300
303
|
* Reach for it when the row carries more than the columns a phone can hold (a status + label +
|
|
301
304
|
* age header, a clamped title, a prose line). A table that only needs to shed a column or two
|
|
@@ -431,6 +434,10 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
|
|
|
431
434
|
* its own card instead of widening the page. Reach for this only to hold a MINIMUM table width
|
|
432
435
|
* — e.g. so eight columns keep their readable widths and scroll rather than compress.
|
|
433
436
|
*
|
|
437
|
+
* Setting it also switches the column fold OFF (a declared floor is the caller saying "this
|
|
438
|
+
* table is at least this wide", which folding columns out would contradict), so a `minWidth`
|
|
439
|
+
* table scrolls where the same table without it folds. `maxHeight` alone keeps the fold.
|
|
440
|
+
*
|
|
434
441
|
* The one table whose wrapper is MEASURED rather than declared is `stickyHeader` with neither
|
|
435
442
|
* this nor `maxHeight`; see that prop and `useMeasuredContainment` in the implementation for
|
|
436
443
|
* why, and pair it with `maxHeight` if the table must both scroll and stick.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/data/data-table.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/data/data-table.tsx"],"names":[],"mappings":"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"}
|
|
@@ -90,7 +90,12 @@ export type FormGroupProps = BasaltProps & {
|
|
|
90
90
|
error?: ReactNode;
|
|
91
91
|
/** Marks the group required — same two-channel treatment as `FormRow`. */
|
|
92
92
|
required?: boolean;
|
|
93
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* `column` stacks the options; `row` lays them out inline, wraps, and bottom-aligns them, so a
|
|
95
|
+
* trailing button sits level with the inputs instead of stretching to the tallest child. Give a
|
|
96
|
+
* `FormRow` inside a `row` group (or any flex row) a definite width — it is an inline-size
|
|
97
|
+
* container, so its intrinsic width is zero and it collapses otherwise. @default 'column'
|
|
98
|
+
*/
|
|
94
99
|
direction?: 'row' | 'column';
|
|
95
100
|
/** The cluster — checkboxes, radios, chips. */
|
|
96
101
|
children: ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/forms/form-layout.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAyC1D,gEAAgE;AAChE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GACxC,eAAe,CAAC,eAAe,CAAC,GAChC,sBAAsB,GAAG;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAoBlD;AAID,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4FAA4F;IAC5F,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BA0B1C;AAID,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,qGAAqG;IACrG,KAAK,EAAE,MAAM,CAAA;IACb,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB
|
|
1
|
+
{"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/forms/form-layout.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAyC1D,gEAAgE;AAChE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GACxC,eAAe,CAAC,eAAe,CAAC,GAChC,sBAAsB,GAAG;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAoBlD;AAID,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4FAA4F;IAC5F,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BA0B1C;AAID,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,qGAAqG;IACrG,KAAK,EAAE,MAAM,CAAA;IACb,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAsB9C;AAID,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C;;;;;;;;OAQG;IACH,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAgBlD"}
|
|
@@ -138,9 +138,12 @@
|
|
|
138
138
|
gap: var(--vx-space-stack-xs);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/* Bottom-aligned, not stretched: a trailing button beside a labelled FormRow keeps its own height
|
|
142
|
+
and lines up with the input, not with the label above it. */
|
|
141
143
|
.groupControls[data-direction='row'] {
|
|
142
144
|
flex-direction: row;
|
|
143
145
|
flex-wrap: wrap;
|
|
146
|
+
align-items: flex-end;
|
|
144
147
|
gap: var(--vx-space-control-gap);
|
|
145
148
|
}
|
|
146
149
|
|
package/dist/guard/index.d.ts
CHANGED
|
@@ -302,7 +302,7 @@ export declare const GUARD_RULES: {
|
|
|
302
302
|
readonly kind: "raw-selection-control";
|
|
303
303
|
readonly pattern: RegExp;
|
|
304
304
|
readonly appliesTo: (relPath: string) => boolean;
|
|
305
|
-
readonly message: "Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.";
|
|
305
|
+
readonly message: "Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.";
|
|
306
306
|
};
|
|
307
307
|
readonly 'inline-font-size': {
|
|
308
308
|
readonly kind: "inline-font-size";
|
|
@@ -313,7 +313,7 @@ export declare const GUARD_RULES: {
|
|
|
313
313
|
readonly kind: "raw-media-query";
|
|
314
314
|
readonly pattern: RegExp;
|
|
315
315
|
readonly appliesTo: (relPath: string) => boolean;
|
|
316
|
-
readonly message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${string} at a boundary in ${string}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`;
|
|
316
|
+
readonly message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${string}, and a basalt-card or unnamed @container query must sit at a boundary in ${string} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`;
|
|
317
317
|
};
|
|
318
318
|
};
|
|
319
319
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE7E,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
|
package/dist/guard/index.js
CHANGED
|
@@ -22,12 +22,7 @@ const CSS_WIDTH_MEDIA_QUERY = new RegExp(
|
|
|
22
22
|
const CSS_CONTAINER_QUERY = /@container(?:\s+([\w-]+))?\s*((?:\([^)]*\)\s*(?:and\s*)?)+)/g;
|
|
23
23
|
const CSS_CONTAINER_LITERAL = /(?:min-width|max-width|width)\s*(?:[<>]=?|:)\s*([\d.]+)(px|em)?/g;
|
|
24
24
|
const CSS_NON_COARSE_POINTER_QUERY = /\(\s*(?:any-)?pointer\s*:\s*(?!(?:coarse|fine)\b)[a-z-]+\s*\)/g;
|
|
25
|
-
const DECLARED_CONTAINER_NAMES = /* @__PURE__ */ new Set([
|
|
26
|
-
"basalt-card",
|
|
27
|
-
"basalt-form",
|
|
28
|
-
"basalt-main",
|
|
29
|
-
"basalt-grid"
|
|
30
|
-
]);
|
|
25
|
+
const DECLARED_CONTAINER_NAMES = /* @__PURE__ */ new Set(["basalt-card", "basalt-form", "basalt-grid"]);
|
|
31
26
|
const CONTAINER_CLASS_LITERALS_PX = /* @__PURE__ */ new Set([0, 240, 239.9, 480, 479.9, 800, 799.9]);
|
|
32
27
|
const SIZE_CLASS_LITERALS = /* @__PURE__ */ new Set(["52.5em", "75em", "52.49375em", "74.99375em"]);
|
|
33
28
|
const SHELL_CSS_PATH = /(?:^|\/)shell\//;
|
|
@@ -193,7 +188,7 @@ function titleBodyNamesThePage(body) {
|
|
|
193
188
|
}
|
|
194
189
|
const RAW_SELECTION_CONTROL_TAG = /<(SegmentedControl|Select|MultiSelect|NativeSelect|DatePickerInput|DateInput|TagsInput|Chip\.Group)(?![\w])(?:=>|[^>])*?>/g;
|
|
195
190
|
const PROSE_CONTEXT_TAG = /<(?:Prose|ArticleLayout|Modal|Drawer)(?![\w])/;
|
|
196
|
-
const CONTROL_HOST_TAG = /<(?:SettingsRow|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/;
|
|
191
|
+
const CONTROL_HOST_TAG = /<(?:SettingsRow|FormRow|FormGroup|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/;
|
|
197
192
|
const CONTROL_HOST_WINDOW_LINES = 12;
|
|
198
193
|
const OVERLAY_CONVENTION_KEBAB = /^[^/]*-(?:modal|drawer|popover|panel|form)\.[jt]sx$/;
|
|
199
194
|
const OVERLAY_CONVENTION_PASCAL = /^[A-Z]\w*(?:Modal|Drawer|Popover|Panel|Form)\.[jt]sx$/;
|
|
@@ -923,7 +918,7 @@ const GUARD_RULES = {
|
|
|
923
918
|
// handled inline (full-text tag-scoped scan + host window)
|
|
924
919
|
// JSX-tag-shaped (`<Select …>`) — never appears in CSS text.
|
|
925
920
|
appliesTo: (relPath) => !relPath.endsWith(".css"),
|
|
926
|
-
message: "Raw selection control with no home \u2014 a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1\u2013C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title \u2014 so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST \u2014 where the two disagree, the plugin is right."
|
|
921
|
+
message: "Raw selection control with no home \u2014 a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1\u2013C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title \u2014 so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST \u2014 where the two disagree, the plugin is right."
|
|
927
922
|
},
|
|
928
923
|
"inline-font-size": {
|
|
929
924
|
kind: "inline-font-size",
|
|
@@ -937,7 +932,7 @@ const GUARD_RULES = {
|
|
|
937
932
|
// CSS-only: a width @media, an @container name/literal or a non-coarse pointer query has no JSX
|
|
938
933
|
// spelling — the AST-side twin of this law is the oxlint plugin's basalt/raw-breakpoint.
|
|
939
934
|
appliesTo: (relPath) => relPath.endsWith(".css"),
|
|
940
|
-
message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(" / ")} at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(" / ")}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`
|
|
935
|
+
message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(" / ")}, and a basalt-card or unnamed @container query must sit at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(" / ")} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`
|
|
941
936
|
}
|
|
942
937
|
};
|
|
943
938
|
function guardKindRemedy(kind) {
|
package/dist/shell/page-bar.d.ts
CHANGED
|
@@ -100,5 +100,29 @@ export type PageBarProps = BasaltProps & SlotStylesProps<PageBarSlot> & {
|
|
|
100
100
|
/** One `ViewTabs`. */
|
|
101
101
|
tabs?: ReactNode;
|
|
102
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* The page's control bar — row 1 (title/icon, `actions`, `sync`) and row 2 (`tabs`, `filters`,
|
|
105
|
+
* `filtersEnd`). Inside `BasaltShell` both rows portal into shell chrome; with no shell it renders
|
|
106
|
+
* ONE sticky bar in flow, `title` leading, and needs no `PageBarProvider`.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* // Shell-less (linewatch-shaped): the store is local, the bar is the page's first child.
|
|
110
|
+
* const filters = createLocalStore({ key: 'outages', fields: { q: field.string({ max: 60 }) } })
|
|
111
|
+
*
|
|
112
|
+
* function OutagesPage() {
|
|
113
|
+
* const [q] = filters.field.q.use()
|
|
114
|
+
* return (
|
|
115
|
+
* <>
|
|
116
|
+
* <PageBar
|
|
117
|
+
* title="Outages"
|
|
118
|
+
* actions={{ primary: { key: 'export', label: 'Export', onClick: exportCsv } }}
|
|
119
|
+
* filters={<FilterSet><SearchFilter field={filters.field.q} placeholder="Find a line" /></FilterSet>}
|
|
120
|
+
* classNames={{ root: classes.bleed }} // your gutter bleed — see `PageBarSlot`
|
|
121
|
+
* />
|
|
122
|
+
* <OutageTable query={q} />
|
|
123
|
+
* </>
|
|
124
|
+
* )
|
|
125
|
+
* }
|
|
126
|
+
*/
|
|
103
127
|
export declare function PageBar({ title, icon, actions, sync, filters, filtersEnd, tabs, className, style, classNames, }: PageBarProps): ReactNode;
|
|
104
128
|
//# sourceMappingURL=page-bar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAWnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAK9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAKxD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CACzB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CAsDZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGtE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IAC7C,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAC3B,CAaA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAmEH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,YAAY,GAAG,SAAS,CAkM1B"}
|
|
1
|
+
{"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAWnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAK9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAKxD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CACzB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CAsDZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGtE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IAC7C,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAC3B,CAaA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAmEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,YAAY,GAAG,SAAS,CAkM1B"}
|
package/dist/tokens/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { deriveRadius } from './palette.js';
|
|
|
16
16
|
export type { RadiusValues } from './palette.js';
|
|
17
17
|
export { deriveSpacing } from './palette.js';
|
|
18
18
|
export type { SpaceValues } from './palette.js';
|
|
19
|
+
export { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES } from './size-classes.js';
|
|
20
|
+
export type { ContainerClass } from './size-classes.js';
|
|
19
21
|
/** A per-theme color pair: a hue keeps its identity but shifts shade across schemes. */
|
|
20
22
|
export type ColorPair = {
|
|
21
23
|
light: string;
|
|
@@ -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;AAQvE,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;
|
|
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
|
@@ -14,6 +14,7 @@ import { BP, p } from "./palette.js";
|
|
|
14
14
|
import { DEFAULT_DERIVE_CONFIG, deriveTokens, resolveDeriveConfig } from "./derive.js";
|
|
15
15
|
import { deriveRadius } from "./palette.js";
|
|
16
16
|
import { deriveSpacing } from "./palette.js";
|
|
17
|
+
import { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES as SIZE_CLASSES2 } from "./size-classes.js";
|
|
17
18
|
const alpha = (token, a) => `color-mix(in srgb, ${token} ${Math.round(a * 100)}%, transparent)`;
|
|
18
19
|
const TEXT = {
|
|
19
20
|
nano: 10,
|
|
@@ -671,7 +672,9 @@ ${mobileLadderBlock(space)}`;
|
|
|
671
672
|
export {
|
|
672
673
|
BP,
|
|
673
674
|
BREAKPOINTS,
|
|
675
|
+
CONTAINER_CLASSES,
|
|
674
676
|
DEFAULT_DERIVE_CONFIG,
|
|
677
|
+
SIZE_CLASSES2 as SIZE_CLASSES,
|
|
675
678
|
VX,
|
|
676
679
|
alpha,
|
|
677
680
|
buildDensityCss,
|
|
@@ -686,6 +689,7 @@ export {
|
|
|
686
689
|
groupTokens,
|
|
687
690
|
p,
|
|
688
691
|
pxRem,
|
|
692
|
+
resolveContainerClass,
|
|
689
693
|
resolveDeriveConfig,
|
|
690
694
|
seriesTokens
|
|
691
695
|
};
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.32.
|
|
1
|
+
# basalt-ui v1.32.1
|
|
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.32.
|
|
22
|
+
Version: 1.32.1
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
package/src/data/data-table.tsx
CHANGED
|
@@ -608,7 +608,8 @@ export type BasaltDataTableProps<T> = BasaltProps &
|
|
|
608
608
|
* list can never disagree with its table; a fixed card order is `initialSorting`, not a second
|
|
609
609
|
* sort. Everything around the body stays: the header (title · count · toolbar), the bulk bar,
|
|
610
610
|
* the pager, and `query` / `isLoading` / `emptyState`. `onRowActivate` makes each card
|
|
611
|
-
* activatable exactly as it does a row (click, or Enter on the focused card
|
|
611
|
+
* activatable exactly as it does a row (click, or Enter on the focused card — never Space, the
|
|
612
|
+
* browser's own page scroll on a focused non-button). The selection
|
|
612
613
|
* checkbox is not projected — a card list with a selection keeps its bulk bar but no ticks.
|
|
613
614
|
*
|
|
614
615
|
* An activatable card takes `tabIndex=0` + `data-activatable`, never `role="button"`: a card is
|
|
@@ -616,7 +617,9 @@ export type BasaltDataTableProps<T> = BasaltProps &
|
|
|
616
617
|
* descendant presentational, so those nested controls would vanish from the accessibility tree.
|
|
617
618
|
* The affordance is the cursor and the focus ring, as on the row. `maxHeight` caps the card list
|
|
618
619
|
* in a `ScrollArea`; `minWidth` does not apply — it is a horizontal floor for columns, and a
|
|
619
|
-
* card list has none.
|
|
620
|
+
* card list has none. `stickyHeader` has nothing to stick while the cards render. A card order
|
|
621
|
+
* with no user sort is `data`'s own order — sort it there (or `initialSorting`) for a domain
|
|
622
|
+
* order.
|
|
620
623
|
*
|
|
621
624
|
* Reach for it when the row carries more than the columns a phone can hold (a status + label +
|
|
622
625
|
* age header, a clamped title, a prose line). A table that only needs to shed a column or two
|
|
@@ -761,6 +764,10 @@ export type BasaltDataTableProps<T> = BasaltProps &
|
|
|
761
764
|
* its own card instead of widening the page. Reach for this only to hold a MINIMUM table width
|
|
762
765
|
* — e.g. so eight columns keep their readable widths and scroll rather than compress.
|
|
763
766
|
*
|
|
767
|
+
* Setting it also switches the column fold OFF (a declared floor is the caller saying "this
|
|
768
|
+
* table is at least this wide", which folding columns out would contradict), so a `minWidth`
|
|
769
|
+
* table scrolls where the same table without it folds. `maxHeight` alone keeps the fold.
|
|
770
|
+
*
|
|
764
771
|
* The one table whose wrapper is MEASURED rather than declared is `stickyHeader` with neither
|
|
765
772
|
* this nor `maxHeight`; see that prop and `useMeasuredContainment` in the implementation for
|
|
766
773
|
* why, and pair it with `maxHeight` if the table must both scroll and stick.
|
|
@@ -138,9 +138,12 @@
|
|
|
138
138
|
gap: var(--vx-space-stack-xs);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/* Bottom-aligned, not stretched: a trailing button beside a labelled FormRow keeps its own height
|
|
142
|
+
and lines up with the input, not with the label above it. */
|
|
141
143
|
.groupControls[data-direction='row'] {
|
|
142
144
|
flex-direction: row;
|
|
143
145
|
flex-wrap: wrap;
|
|
146
|
+
align-items: flex-end;
|
|
144
147
|
gap: var(--vx-space-control-gap);
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -213,7 +213,12 @@ export type FormGroupProps = BasaltProps & {
|
|
|
213
213
|
error?: ReactNode
|
|
214
214
|
/** Marks the group required — same two-channel treatment as `FormRow`. */
|
|
215
215
|
required?: boolean
|
|
216
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* `column` stacks the options; `row` lays them out inline, wraps, and bottom-aligns them, so a
|
|
218
|
+
* trailing button sits level with the inputs instead of stretching to the tallest child. Give a
|
|
219
|
+
* `FormRow` inside a `row` group (or any flex row) a definite width — it is an inline-size
|
|
220
|
+
* container, so its intrinsic width is zero and it collapses otherwise. @default 'column'
|
|
221
|
+
*/
|
|
217
222
|
direction?: 'row' | 'column'
|
|
218
223
|
/** The cluster — checkboxes, radios, chips. */
|
|
219
224
|
children: ReactNode
|
package/src/guard/index.ts
CHANGED
|
@@ -177,27 +177,23 @@ const CSS_NON_COARSE_POINTER_QUERY =
|
|
|
177
177
|
* The `@container` names the container-class axis declares — a consumer container carrying any
|
|
178
178
|
* other name has invented its own container-query breakpoint instead of using the framework's.
|
|
179
179
|
* `basalt-card` is the one shipped today (`src/dashboard/section.module.css`);
|
|
180
|
-
* `basalt-form`/`basalt-
|
|
181
|
-
*
|
|
180
|
+
* `basalt-form`/`basalt-grid` are the declared remaining two, reserved here so a consumer adopting
|
|
181
|
+
* one ahead of the framework does not immediately trip this guard. `basalt-main` was a third until
|
|
182
|
+
* 1.32.1 dropped it: nothing ever declared it, so the guard accepted a query that never matched.
|
|
182
183
|
*
|
|
183
184
|
* `basalt-grid` is also what `StatGroup`/`WidgetGrid` declare (`src/dashboard/*.module.css`) —
|
|
184
185
|
* they shipped as `basalt-stat-group`/`basalt-widget-grid` until 1.32.0 moved them onto this table.
|
|
185
186
|
*/
|
|
186
|
-
const DECLARED_CONTAINER_NAMES = new Set([
|
|
187
|
-
'basalt-card',
|
|
188
|
-
'basalt-form',
|
|
189
|
-
'basalt-main',
|
|
190
|
-
'basalt-grid',
|
|
191
|
-
])
|
|
187
|
+
const DECLARED_CONTAINER_NAMES = new Set(['basalt-card', 'basalt-form', 'basalt-grid'])
|
|
192
188
|
|
|
193
189
|
/**
|
|
194
190
|
* `CONTAINER_CLASSES` (`src/tokens/size-classes.ts`) boundaries, duplicated per the `CTL_THEME_TAGS`
|
|
195
191
|
* convention in the oxlint plugin — this module is dependency-free by contract (file header) and
|
|
196
192
|
* must not import `../tokens`. Pinned against the real table by `check-source.test.ts`. Judged only
|
|
197
|
-
* against `basalt-card
|
|
198
|
-
* the container's own floor, not a class boundary, but `(width >= 0px)` is a legitimate
|
|
199
|
-
* "scope this rule to the container, whatever its width" (`widget-header.module.css`'s
|
|
200
|
-
* — allowed for that reason, not because it names a real `CONTAINER_CLASSES` step.
|
|
193
|
+
* against `basalt-card` and unnamed queries: the other declared names have no shipped numeric table
|
|
194
|
+
* yet. `0` is the container's own floor, not a class boundary, but `(width >= 0px)` is a legitimate
|
|
195
|
+
* idiom for "scope this rule to the container, whatever its width" (`widget-header.module.css`'s
|
|
196
|
+
* `cqi` clamp) — allowed for that reason, not because it names a real `CONTAINER_CLASSES` step.
|
|
201
197
|
*/
|
|
202
198
|
const CONTAINER_CLASS_LITERALS_PX = new Set([0, 240, 239.9, 480, 479.9, 800, 799.9])
|
|
203
199
|
|
|
@@ -691,9 +687,12 @@ const PROSE_CONTEXT_TAG = /<(?:Prose|ArticleLayout|Modal|Drawer)(?![\w])/
|
|
|
691
687
|
* read differently in the two lanes. The plugin gates them on the tag's basalt IMPORT; a 12-line
|
|
692
688
|
* regex window has no import graph, so this lane matches by name and accepts the missed warn on a
|
|
693
689
|
* consumer's own `PanelRow` — the same direction every approximation in this scan already leans.
|
|
690
|
+
*
|
|
691
|
+
* The name set is the plugin's `CONTROL_HOST_TAGS` ∪ `BASALT_HOST_TAGS`; `oxlint-plugin.test.ts` ›
|
|
692
|
+
* lane parity asserts the two agree (1.32.1 — `FormRow`/`FormGroup` had drifted out of this lane).
|
|
694
693
|
*/
|
|
695
694
|
const CONTROL_HOST_TAG =
|
|
696
|
-
/<(?:SettingsRow|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/
|
|
695
|
+
/<(?:SettingsRow|FormRow|FormGroup|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/
|
|
697
696
|
|
|
698
697
|
/**
|
|
699
698
|
* How far ABOVE a control the host-tag window reaches, in lines.
|
|
@@ -2200,7 +2199,7 @@ export const GUARD_RULES = {
|
|
|
2200
2199
|
// JSX-tag-shaped (`<Select …>`) — never appears in CSS text.
|
|
2201
2200
|
appliesTo: (relPath) => !relPath.endsWith('.css'),
|
|
2202
2201
|
message:
|
|
2203
|
-
'Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.',
|
|
2202
|
+
'Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.',
|
|
2204
2203
|
},
|
|
2205
2204
|
'inline-font-size': {
|
|
2206
2205
|
kind: 'inline-font-size',
|
|
@@ -2214,7 +2213,7 @@ export const GUARD_RULES = {
|
|
|
2214
2213
|
// CSS-only: a width @media, an @container name/literal or a non-coarse pointer query has no JSX
|
|
2215
2214
|
// spelling — the AST-side twin of this law is the oxlint plugin's basalt/raw-breakpoint.
|
|
2216
2215
|
appliesTo: (relPath) => relPath.endsWith('.css'),
|
|
2217
|
-
message: `Raw viewport/container breakpoint in CSS — a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(' / ')} at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(' / ')}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`,
|
|
2216
|
+
message: `Raw viewport/container breakpoint in CSS — a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(' / ')}, and a basalt-card or unnamed @container query must sit at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(' / ')} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`,
|
|
2218
2217
|
},
|
|
2219
2218
|
} as const satisfies Record<GuardKind, GuardRule>
|
|
2220
2219
|
|
|
@@ -2481,7 +2480,7 @@ export function checkSource(text: string, relPath: string, cfg: GuardConfig): Fi
|
|
|
2481
2480
|
}
|
|
2482
2481
|
// Literal boundaries are judged against `basalt-card`'s own table (see
|
|
2483
2482
|
// `CONTAINER_CLASS_LITERALS_PX`'s doc) — an unnamed query has no OTHER table to judge
|
|
2484
|
-
// against, so it is judged against this one too; the other
|
|
2483
|
+
// against, so it is judged against this one too; the other declared names stay
|
|
2485
2484
|
// unjudged until they ship their own.
|
|
2486
2485
|
if (name !== '' && name !== 'basalt-card') continue
|
|
2487
2486
|
for (const lit of body.matchAll(CSS_CONTAINER_LITERAL)) {
|
package/src/shell/page-bar.tsx
CHANGED
|
@@ -346,6 +346,30 @@ function useMeasuredHeightVar(active: boolean): (el: HTMLDivElement | null) => v
|
|
|
346
346
|
return attach
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* The page's control bar — row 1 (title/icon, `actions`, `sync`) and row 2 (`tabs`, `filters`,
|
|
351
|
+
* `filtersEnd`). Inside `BasaltShell` both rows portal into shell chrome; with no shell it renders
|
|
352
|
+
* ONE sticky bar in flow, `title` leading, and needs no `PageBarProvider`.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* // Shell-less (linewatch-shaped): the store is local, the bar is the page's first child.
|
|
356
|
+
* const filters = createLocalStore({ key: 'outages', fields: { q: field.string({ max: 60 }) } })
|
|
357
|
+
*
|
|
358
|
+
* function OutagesPage() {
|
|
359
|
+
* const [q] = filters.field.q.use()
|
|
360
|
+
* return (
|
|
361
|
+
* <>
|
|
362
|
+
* <PageBar
|
|
363
|
+
* title="Outages"
|
|
364
|
+
* actions={{ primary: { key: 'export', label: 'Export', onClick: exportCsv } }}
|
|
365
|
+
* filters={<FilterSet><SearchFilter field={filters.field.q} placeholder="Find a line" /></FilterSet>}
|
|
366
|
+
* classNames={{ root: classes.bleed }} // your gutter bleed — see `PageBarSlot`
|
|
367
|
+
* />
|
|
368
|
+
* <OutageTable query={q} />
|
|
369
|
+
* </>
|
|
370
|
+
* )
|
|
371
|
+
* }
|
|
372
|
+
*/
|
|
349
373
|
export function PageBar({
|
|
350
374
|
title,
|
|
351
375
|
icon,
|
package/src/tokens/index.ts
CHANGED
|
@@ -52,6 +52,11 @@ export type { RadiusValues } from './palette'
|
|
|
52
52
|
export { deriveSpacing } from './palette'
|
|
53
53
|
export type { SpaceValues } from './palette'
|
|
54
54
|
|
|
55
|
+
// The two responsive tables — MIGRATING recipes and the raw-breakpoint guard messages send
|
|
56
|
+
// consumers here for them; `migrating-imports.test.ts` pins that every documented import resolves.
|
|
57
|
+
export { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES } from './size-classes'
|
|
58
|
+
export type { ContainerClass } from './size-classes'
|
|
59
|
+
|
|
55
60
|
/** A per-theme color pair: a hue keeps its identity but shifts shade across schemes. */
|
|
56
61
|
export type ColorPair = { light: string; dark: string }
|
|
57
62
|
|