basalt-ui 1.19.0 → 1.19.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 +7 -0
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +14 -14
- package/dist/shell/app-mobile-nav.module.css +33 -8
- package/dist/shell/app-mobile-nav.module.css.d.ts +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/shell/app-mobile-nav.module.css +33 -8
- package/src/shell/app-mobile-nav.module.css.d.ts +1 -1
- package/src/shell/app-mobile-nav.test.tsx +34 -0
- package/src/shell/app-mobile-nav.tsx +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.19.1](https://github.com/jkrumm/basalt-ui/compare/v1.19.0...v1.19.1) (2026-08-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* cap the mobile bottom-sheet height to its content ([04093ac](https://github.com/jkrumm/basalt-ui/commit/04093ac902d894249ebea3e6381755497670e305))
|
|
7
|
+
|
|
1
8
|
# [1.19.0](https://github.com/jkrumm/basalt-ui/compare/v1.18.0...v1.19.0) (2026-08-20)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACnD,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,GAClB,EAAE,cAAc,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"app-mobile-nav.d.ts","sourceRoot":"","sources":["../../src/shell/app-mobile-nav.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,YAAY,EAAa,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiB/C,MAAM,MAAM,cAAc,GAAG;IAC3B,kFAAkF;IAClF,KAAK,EAAE,cAAc,CAAA;IACrB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC;;wEAEoE;IACpE,OAAO,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACxC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACnD,CAAA;AAkFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAE/E;AAsFD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,OAAO,EACP,iBAAiB,GAClB,EAAE,cAAc,GAAG,YAAY,CAiP/B"}
|
|
@@ -281,32 +281,32 @@ function MobileNav({
|
|
|
281
281
|
);
|
|
282
282
|
const sheetSlot = (slot) => /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
283
283
|
surfaceTab(slot, () => setOpenKey(slot.key)),
|
|
284
|
-
/* @__PURE__ */
|
|
284
|
+
/* @__PURE__ */ jsx(
|
|
285
285
|
Drawer,
|
|
286
286
|
{
|
|
287
287
|
opened: openKey === slot.key,
|
|
288
288
|
onClose: close,
|
|
289
289
|
position: "bottom",
|
|
290
|
-
size: "auto",
|
|
291
290
|
padding: "md",
|
|
292
291
|
title: slot.label,
|
|
293
|
-
classNames: {
|
|
292
|
+
classNames: {
|
|
293
|
+
content: classes.sheet,
|
|
294
|
+
title: classes.sheetTitle,
|
|
295
|
+
header: classes.sheetHeader
|
|
296
|
+
},
|
|
294
297
|
transitionProps: {
|
|
295
298
|
transition: "slide-up",
|
|
296
299
|
duration: reduceMotion ? 0 : 220,
|
|
297
300
|
timingFunction: "cubic-bezier(0.32, 0.72, 0, 1)"
|
|
298
301
|
},
|
|
299
|
-
children: [
|
|
300
|
-
/* @__PURE__ */
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
slot.isMore ? config?.moreExtra : null
|
|
308
|
-
] }) })
|
|
309
|
-
]
|
|
302
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Autosize, { mah: "62dvh", type: "scroll", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
|
|
303
|
+
slot.groups.map((group) => /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
304
|
+
sheetGroupLabel(group),
|
|
305
|
+
rows(group.items, 0, sheetRow)
|
|
306
|
+
] }, group.key)),
|
|
307
|
+
slot.isMore && extraRows.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: 2, children: extraRows.map(sheetActionRow) }) : null,
|
|
308
|
+
slot.isMore ? config?.moreExtra : null
|
|
309
|
+
] }) })
|
|
310
310
|
}
|
|
311
311
|
)
|
|
312
312
|
] }, slot.key);
|
|
@@ -170,6 +170,27 @@
|
|
|
170
170
|
border-top-right-radius: var(--mantine-radius-lg);
|
|
171
171
|
/* A Drawer is NOT an AppShell section, so Mantine adds no inset of its own here. */
|
|
172
172
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* THE `--drawer-size-auto` TRAP — do not "fix" this by restoring `size="auto"` on the Drawer.
|
|
176
|
+
* `getSize('auto', 'drawer-size')` (Mantine's `get-size.mjs`) resolves ANY non-numeric size to
|
|
177
|
+
* `var(--drawer-size-auto)`, and `@mantine/core` defines that custom property NOWHERE — grepped
|
|
178
|
+
* the full shipped `styles.css`. On a bottom Drawer that dead var flows straight into
|
|
179
|
+
* `--drawer-height`, so `size="auto"` was never wired to anything; the sheet rendered at
|
|
180
|
+
* `calc(100% - offset*2)`, i.e. the full viewport, no matter how little content it held. That is
|
|
181
|
+
* why this component no longer passes a `size` prop at all — it would resolve to *some* value,
|
|
182
|
+
* but this rule below overrides Mantine's `height`/`max-height` regardless of what that value is.
|
|
183
|
+
*
|
|
184
|
+
* This works with NO `!important`: `packages/basalt-ui/styles.css` wraps every Mantine rule in
|
|
185
|
+
* `@layer mantine` (verified against the installed `styles.layer.css`), and this CSS module is
|
|
186
|
+
* unlayered — an unlayered rule always wins over a layered one, independent of specificity or
|
|
187
|
+
* source order. `height: auto` lets the sheet hug its content; `max-height` is a hard ceiling on
|
|
188
|
+
* the whole sheet (header + body), sized with headroom ABOVE the `ScrollArea.Autosize`'s
|
|
189
|
+
* `mah="62dvh"` in the JSX — that autosize cap is what actually decides when the sheet starts
|
|
190
|
+
* scrolling internally, so the two never fight over which one owns "the maximum".
|
|
191
|
+
*/
|
|
192
|
+
height: auto;
|
|
193
|
+
max-height: min(70dvh, 100%);
|
|
173
194
|
}
|
|
174
195
|
|
|
175
196
|
/* Sheet title = a micro-label (docs/DESIGN-SPEC.md §3), matching the sidebar's `.sectionLabel`. */
|
|
@@ -182,14 +203,18 @@
|
|
|
182
203
|
color: var(--vx-faint);
|
|
183
204
|
}
|
|
184
205
|
|
|
185
|
-
/*
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
/*
|
|
207
|
+
* Mantine's `ModalBase` header forces a `min-height` of `calc(3.75rem * var(--mantine-scale))`
|
|
208
|
+
* (60px) — sized for a real dialog title, not a 10px micro-label, and a big share of "unnecessarily
|
|
209
|
+
* big". `--vx-space-mobile-nav-row-height` pins it to the same 44px touch-target floor as every
|
|
210
|
+
* other row in this bar instead. The header's close button IS the dismiss affordance now — no
|
|
211
|
+
* separate grabber: there was never swipe-to-dismiss behind it (Mantine ships no drag primitive),
|
|
212
|
+
* so a plain decorative bar bought nothing an explicit, focusable close control doesn't already
|
|
213
|
+
* give for free, including to keyboard/screen-reader users the grabber never reached.
|
|
214
|
+
*/
|
|
215
|
+
.sheetHeader {
|
|
216
|
+
min-height: var(--vx-space-mobile-nav-row-height);
|
|
217
|
+
padding: 0 var(--mantine-spacing-md);
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
/* Large tap rows inside the sheet. NavLink rows reuse the sidebar's neutral active fill.
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.19.
|
|
1
|
+
# basalt-ui v1.19.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.19.
|
|
22
|
+
Version: 1.19.1
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
|
@@ -170,6 +170,27 @@
|
|
|
170
170
|
border-top-right-radius: var(--mantine-radius-lg);
|
|
171
171
|
/* A Drawer is NOT an AppShell section, so Mantine adds no inset of its own here. */
|
|
172
172
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* THE `--drawer-size-auto` TRAP — do not "fix" this by restoring `size="auto"` on the Drawer.
|
|
176
|
+
* `getSize('auto', 'drawer-size')` (Mantine's `get-size.mjs`) resolves ANY non-numeric size to
|
|
177
|
+
* `var(--drawer-size-auto)`, and `@mantine/core` defines that custom property NOWHERE — grepped
|
|
178
|
+
* the full shipped `styles.css`. On a bottom Drawer that dead var flows straight into
|
|
179
|
+
* `--drawer-height`, so `size="auto"` was never wired to anything; the sheet rendered at
|
|
180
|
+
* `calc(100% - offset*2)`, i.e. the full viewport, no matter how little content it held. That is
|
|
181
|
+
* why this component no longer passes a `size` prop at all — it would resolve to *some* value,
|
|
182
|
+
* but this rule below overrides Mantine's `height`/`max-height` regardless of what that value is.
|
|
183
|
+
*
|
|
184
|
+
* This works with NO `!important`: `packages/basalt-ui/styles.css` wraps every Mantine rule in
|
|
185
|
+
* `@layer mantine` (verified against the installed `styles.layer.css`), and this CSS module is
|
|
186
|
+
* unlayered — an unlayered rule always wins over a layered one, independent of specificity or
|
|
187
|
+
* source order. `height: auto` lets the sheet hug its content; `max-height` is a hard ceiling on
|
|
188
|
+
* the whole sheet (header + body), sized with headroom ABOVE the `ScrollArea.Autosize`'s
|
|
189
|
+
* `mah="62dvh"` in the JSX — that autosize cap is what actually decides when the sheet starts
|
|
190
|
+
* scrolling internally, so the two never fight over which one owns "the maximum".
|
|
191
|
+
*/
|
|
192
|
+
height: auto;
|
|
193
|
+
max-height: min(70dvh, 100%);
|
|
173
194
|
}
|
|
174
195
|
|
|
175
196
|
/* Sheet title = a micro-label (docs/DESIGN-SPEC.md §3), matching the sidebar's `.sectionLabel`. */
|
|
@@ -182,14 +203,18 @@
|
|
|
182
203
|
color: var(--vx-faint);
|
|
183
204
|
}
|
|
184
205
|
|
|
185
|
-
/*
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
/*
|
|
207
|
+
* Mantine's `ModalBase` header forces a `min-height` of `calc(3.75rem * var(--mantine-scale))`
|
|
208
|
+
* (60px) — sized for a real dialog title, not a 10px micro-label, and a big share of "unnecessarily
|
|
209
|
+
* big". `--vx-space-mobile-nav-row-height` pins it to the same 44px touch-target floor as every
|
|
210
|
+
* other row in this bar instead. The header's close button IS the dismiss affordance now — no
|
|
211
|
+
* separate grabber: there was never swipe-to-dismiss behind it (Mantine ships no drag primitive),
|
|
212
|
+
* so a plain decorative bar bought nothing an explicit, focusable close control doesn't already
|
|
213
|
+
* give for free, including to keyboard/screen-reader users the grabber never reached.
|
|
214
|
+
*/
|
|
215
|
+
.sheetHeader {
|
|
216
|
+
min-height: var(--vx-space-mobile-nav-row-height);
|
|
217
|
+
padding: 0 var(--mantine-spacing-md);
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
/* Large tap rows inside the sheet. NavLink rows reuse the sidebar's neutral active fill.
|
|
@@ -366,4 +366,38 @@ describe('MobileNav', () => {
|
|
|
366
366
|
expect(dropdown.querySelectorAll('.mantine-Menu-divider')).toHaveLength(0)
|
|
367
367
|
expect(dropdown.querySelectorAll('.mantine-Menu-item')).toHaveLength(1)
|
|
368
368
|
})
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Regression guard for the mobile-sheet-content-height fix. Neither half is pixel-assertable
|
|
372
|
+
* under happy-dom (no layout, and CSS-module class hashes are unavailable under `bun test` per
|
|
373
|
+
* the file header) — so this pins the two DOM-observable facts instead: the sheet is no longer
|
|
374
|
+
* wired through the dead `size="auto"` → `--drawer-size-auto` custom property (see `.sheet` in
|
|
375
|
+
* the CSS module for why that was never wired to anything), and the redundant grabber is gone
|
|
376
|
+
* now that the header's own close button is the sheet's sole dismiss affordance.
|
|
377
|
+
*/
|
|
378
|
+
test('11. the sheet drops the no-op size prop and the redundant grabber', async () => {
|
|
379
|
+
renderBar([
|
|
380
|
+
{
|
|
381
|
+
label: 'Reports',
|
|
382
|
+
mobile: { tab: true },
|
|
383
|
+
items: Array.from({ length: 9 }, (_, i) => item(`row${i + 1}`)),
|
|
384
|
+
},
|
|
385
|
+
{ label: 'Main', items: [item('home')] },
|
|
386
|
+
])
|
|
387
|
+
|
|
388
|
+
fireEvent.click(screen.getByLabelText('Reports'))
|
|
389
|
+
await waitFor(() => expect(drawer()).not.toBeNull())
|
|
390
|
+
|
|
391
|
+
// `size="auto"` resolves to `var(--drawer-size-auto)`, a custom property @mantine/core defines
|
|
392
|
+
// nowhere — its presence anywhere in the tree would be the old bug reappearing.
|
|
393
|
+
expect(document.querySelector('[style*="drawer-size-auto"]')).toBeNull()
|
|
394
|
+
|
|
395
|
+
// The header (title + Mantine's own close button) is the ONLY dismiss affordance now — the
|
|
396
|
+
// grabber div that used to precede the scroll area is gone, so the body wraps exactly the
|
|
397
|
+
// `ScrollArea.Autosize` root and nothing else.
|
|
398
|
+
const header = document.querySelector('.mantine-Drawer-header')
|
|
399
|
+
expect(header).not.toBeNull()
|
|
400
|
+
expect(header?.querySelector('.mantine-Drawer-close')).not.toBeNull()
|
|
401
|
+
expect(document.querySelector('.mantine-Drawer-body')?.children).toHaveLength(1)
|
|
402
|
+
})
|
|
369
403
|
})
|
|
@@ -428,18 +428,29 @@ export function MobileNav({
|
|
|
428
428
|
opened={openKey === slot.key}
|
|
429
429
|
onClose={close}
|
|
430
430
|
position="bottom"
|
|
431
|
-
size="auto"
|
|
431
|
+
// NO `size` prop — `size="auto"` used to be here, but it is a no-op on a bottom Drawer in
|
|
432
|
+
// Mantine 9.3.0 (see the `.sheet` rule in the CSS module for the full trap). The sheet's
|
|
433
|
+
// actual height/max-height comes entirely from that unlayered CSS rule, which overrides
|
|
434
|
+
// Mantine's own regardless of what `size` resolves to.
|
|
432
435
|
padding="md"
|
|
436
|
+
// `title` is both the sheet's accessible name (Mantine wires `aria-labelledby` to it
|
|
437
|
+
// automatically) and what makes the header render at all — dropping it would need an
|
|
438
|
+
// explicit `aria-label` on the Drawer instead. `classNames.header` slims Mantine's 60px
|
|
439
|
+
// title bar down to this bar's touch-target row height; its close button is the sheet's
|
|
440
|
+
// ONLY dismiss affordance now (see `.sheetHeader` in the CSS module for why the grabber
|
|
441
|
+
// that used to sit here is gone rather than the header).
|
|
433
442
|
title={slot.label}
|
|
434
|
-
classNames={{
|
|
443
|
+
classNames={{
|
|
444
|
+
content: classes.sheet,
|
|
445
|
+
title: classes.sheetTitle,
|
|
446
|
+
header: classes.sheetHeader,
|
|
447
|
+
}}
|
|
435
448
|
transitionProps={{
|
|
436
449
|
transition: 'slide-up',
|
|
437
450
|
duration: reduceMotion ? 0 : 220,
|
|
438
451
|
timingFunction: 'cubic-bezier(0.32, 0.72, 0, 1)',
|
|
439
452
|
}}
|
|
440
453
|
>
|
|
441
|
-
{/* The dismiss affordance. No swipe-to-dismiss — see `.grabber` in the CSS module. */}
|
|
442
|
-
<div className={classes.grabber} aria-hidden />
|
|
443
454
|
<ScrollArea.Autosize mah="62dvh" type="scroll">
|
|
444
455
|
<Stack gap="xs">
|
|
445
456
|
{slot.groups.map((group) => (
|