@lerianstudio/sindarian-ui 2.0.0-beta.11 → 2.0.0-beta.12
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/README.md +51 -0
- package/dist/components/ui/icon-button/styles.css +42 -0
- package/dist/components/ui/input/styles.css +8 -5
- package/dist/components/ui/sheet/index.d.ts +42 -0
- package/dist/components/ui/sheet/index.d.ts.map +1 -1
- package/dist/components/ui/sheet/index.js +45 -3
- package/dist/components/ui/sidebar/sidebar-provider.d.ts +25 -4
- package/dist/components/ui/sidebar/sidebar-provider.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-provider.js +26 -5
- package/dist/components/ui/sidebar/sidebar-root.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-root.js +12 -1
- package/dist/components/ui/sidebar/sidebar-trigger.d.ts +30 -3
- package/dist/components/ui/sidebar/sidebar-trigger.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-trigger.js +31 -4
- package/dist/esm/components/ui/sheet/index.d.ts +42 -0
- package/dist/esm/components/ui/sheet/index.d.ts.map +1 -1
- package/dist/esm/components/ui/sheet/index.js +45 -3
- package/dist/esm/components/ui/sidebar/sidebar-provider.d.ts +25 -4
- package/dist/esm/components/ui/sidebar/sidebar-provider.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-provider.js +26 -5
- package/dist/esm/components/ui/sidebar/sidebar-root.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-root.js +12 -1
- package/dist/esm/components/ui/sidebar/sidebar-trigger.d.ts +30 -3
- package/dist/esm/components/ui/sidebar/sidebar-trigger.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-trigger.js +31 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -161,6 +161,57 @@ twice. The rules are identical and the duplication is harmless, costing roughly
|
|
|
161
161
|
8 KB pre-gzip. Keep the app's `@import 'tailwindcss'` first; do not drop it to
|
|
162
162
|
avoid the duplicate.
|
|
163
163
|
|
|
164
|
+
## 📱 What a call site controls on a phone
|
|
165
|
+
|
|
166
|
+
### `SheetContent` fills the screen below `sm`
|
|
167
|
+
|
|
168
|
+
The panel was `w-2/5` with `p-12` at every viewport, which at 390px is a 156px
|
|
169
|
+
panel with 9px of readable field inside it. Below 40rem it is now full width
|
|
170
|
+
with 16px of side padding; the desktop panel is unchanged.
|
|
171
|
+
|
|
172
|
+
A call site that declares its own width keeps it — the phone values carry the
|
|
173
|
+
`max-sm:` modifier and tailwind-merge only drops a conflicting class with
|
|
174
|
+
matching modifiers, so `className="w-[594px]"` still means 594px on a desktop
|
|
175
|
+
AND full width on a phone, with no edit. A call site that wants its own phone
|
|
176
|
+
geometry states it the same way:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<SheetContent className="w-[594px] max-sm:w-[320px] max-sm:p-0" />
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
That modifier rule cuts both ways, and the padding is where a call site feels
|
|
183
|
+
it: a bare `p-0` drops `p-12` and **cannot** reach `max-sm:px-4` either, so a
|
|
184
|
+
panel that asked for no padding gets 16px a side below 40rem. If it genuinely
|
|
185
|
+
wants none, `max-sm:p-0` is the one token that says so.
|
|
186
|
+
|
|
187
|
+
The navigation drawer is exempt and says so itself; nothing at a call site is
|
|
188
|
+
needed for it.
|
|
189
|
+
|
|
190
|
+
### `SidebarTrigger` hides in pixels, not rems
|
|
191
|
+
|
|
192
|
+
Its default is `min-[768px]:hidden`, the exact complement of the media query
|
|
193
|
+
the drawer decision subscribes to. Do not restate that boundary as `md:hidden`
|
|
194
|
+
anywhere: `md` is 48rem, and a rem inside a media query follows the browser's
|
|
195
|
+
default font size, so the two flip at different widths as soon as a reader
|
|
196
|
+
changes that setting — which left viewports with no rail and no hamburger.
|
|
197
|
+
|
|
198
|
+
### `IconButton size="small"` answers a 2.5rem target
|
|
199
|
+
|
|
200
|
+
The glyph stays 2rem; the tap target is an absolutely positioned pseudo-element
|
|
201
|
+
at `.icon-button-base`'s own size, so no layout moves and a table row keeps its
|
|
202
|
+
height. A call site needs nothing. Two of these side by side want at least
|
|
203
|
+
`gap-1` between them, which is what makes each target stop at its neighbour's
|
|
204
|
+
visible edge.
|
|
205
|
+
|
|
206
|
+
### A `w-*` on an input is a preferred size; `shrink-0` on the wrapper is the floor
|
|
207
|
+
|
|
208
|
+
`.input-base` declares no width, so the control contributes its intrinsic size
|
|
209
|
+
and compresses when an ancestor says it may. A width utility on the input is
|
|
210
|
+
therefore what it asks for, not what it keeps: in a row under pressure the
|
|
211
|
+
field still gives. A field that must not give says so on its own wrapper with
|
|
212
|
+
`shrink-0`, and a column that must compress says `min-w-0` on its own flex
|
|
213
|
+
item. See `scripts/measure-input-shrink.mjs` for the fixtures and the numbers.
|
|
214
|
+
|
|
164
215
|
## 📚 Component Categories
|
|
165
216
|
|
|
166
217
|
### UI Primitives
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
@theme inline {
|
|
2
2
|
--spacing-icon-button-p: calc(var(--spacing) * 2);
|
|
3
|
+
/* The tap target every icon control in this package answers to, which is
|
|
4
|
+
also `.icon-button-base`'s own box. Named once so the small variant's
|
|
5
|
+
target and the default variant's size cannot drift apart. */
|
|
6
|
+
--spacing-icon-button-target: calc(var(--spacing) * 10);
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
@layer components {
|
|
@@ -19,6 +23,44 @@
|
|
|
19
23
|
@apply size-8 [&>*]:size-4;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
/* ⛔ THE GLYPH IS 2rem, THE TARGET IS 2.5rem, AND THEY ARE NOT THE SAME BOX.
|
|
27
|
+
A table's row-actions kebab is the one control in its row, and at
|
|
28
|
+
`size-8` it was a 32x32 tap target where every icon control in the shell
|
|
29
|
+
is 40x40 — measured at 32x32 on the ledgers, transactions and
|
|
30
|
+
bank-transfer listings at a 390px viewport.
|
|
31
|
+
|
|
32
|
+
Growing the button instead would have grown the row: `TableCell` is
|
|
33
|
+
`py-4`, so a `size-10` trigger takes the row from 64.5px to 72.5px and a
|
|
34
|
+
phone shows fewer rows for the trouble. An absolutely positioned
|
|
35
|
+
pseudo-element takes part in hit testing and in nothing else, so the row,
|
|
36
|
+
the cell, the alignment and every other layout this class appears in are
|
|
37
|
+
byte-identical; `.button-base` is already `relative`, which is what this
|
|
38
|
+
anchors to.
|
|
39
|
+
|
|
40
|
+
⚠️ CENTRED AND SIZED, NOT INSET BY A CONSTANT. A negative `inset` is
|
|
41
|
+
measured from the PADDING box, so `-inset-1` on this button came out
|
|
42
|
+
38x38 rather than 40x40 — `.button-outline` carries a 1px transparent
|
|
43
|
+
border, and three of the eight perimeter points fell outside it. Half of
|
|
44
|
+
50% resolves against whatever the box actually is, so this states the
|
|
45
|
+
target as a size and lets the border be whatever it is.
|
|
46
|
+
|
|
47
|
+
The target is `--spacing-icon-button-target`, which is `.icon-button-base`
|
|
48
|
+
itself, so it is 2.5rem at any browser font rather than 40px at one of
|
|
49
|
+
them. That leaves a quarter of a rem on each side, which is also the
|
|
50
|
+
`gap-1` between the two buttons in `CopyField` — the only place in this
|
|
51
|
+
package where two of these sit side by side — so each target stops at its
|
|
52
|
+
neighbour's visible edge and neither can be tapped by aiming at the other.
|
|
53
|
+
They do share the empty gap, which the later one paints over and takes.
|
|
54
|
+
|
|
55
|
+
`scripts/measure-phone-shapes.mjs` walks eight points around the 2.5rem
|
|
56
|
+
perimeter and asks the document what is there; run it when touching this
|
|
57
|
+
rule. */
|
|
58
|
+
.icon-button-small::after {
|
|
59
|
+
position: absolute;
|
|
60
|
+
inset: calc(50% - var(--spacing-icon-button-target) / 2);
|
|
61
|
+
content: '';
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
.icon-button-rounded {
|
|
23
65
|
@apply rounded-full;
|
|
24
66
|
}
|
|
@@ -41,12 +41,15 @@
|
|
|
41
41
|
box narrower than that gets the difference painted outside it. Measured at
|
|
42
42
|
23px on the console's template-builder header, whose `w-48` wrapper is
|
|
43
43
|
squeezed to 18px and which the console repairs with `shrink-0` on that
|
|
44
|
-
wrapper. This package's own `SheetContent side="right"`
|
|
45
|
-
arithmetic one step earlier:
|
|
46
|
-
59px panel, and the field
|
|
44
|
+
wrapper. This package's own `SheetContent side="right"` used to run the
|
|
45
|
+
same arithmetic one step earlier: `w-2/5 p-12` at a 390px viewport left a
|
|
46
|
+
59px panel, and the field came out 41px wide with 9px of text — unusable,
|
|
47
47
|
but inside its box, where 2.0.0-beta.9 painted 189px of it across the
|
|
48
|
-
panel edge.
|
|
49
|
-
|
|
48
|
+
panel edge. That panel grew a phone step in 2.0.0-beta.12 and now fills
|
|
49
|
+
the screen, so the field reads 307px there; the arithmetic still stands
|
|
50
|
+
wherever a box is genuinely narrower than the control's own padding. A row
|
|
51
|
+
that must compress says so on its own flex item, with `min-w-0`, and that
|
|
52
|
+
one token now works.
|
|
50
53
|
|
|
51
54
|
Why no declared width, measured rather than assumed. A declared width
|
|
52
55
|
replaces the control's max-content contribution as well as its min-content
|
|
@@ -6,6 +6,48 @@ declare function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPri
|
|
|
6
6
|
declare function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>): React.JSX.Element;
|
|
7
7
|
declare function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>): React.JSX.Element;
|
|
8
8
|
declare function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>): React.JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* ⛔ A PANEL BUILT FOR A DESKTOP HAS TO STOP BEING TWO FIFTHS ON A PHONE.
|
|
11
|
+
*
|
|
12
|
+
* `w-2/5` and `p-12` were unconditional. At a 390px viewport that is a 156px
|
|
13
|
+
* panel with 59px of usable width, and a form field inside it measures 9px of
|
|
14
|
+
* text — a sheet that cannot be read, let alone filled in. At 320px the panel
|
|
15
|
+
* is 128px and the field has NOTHING: its own padding is wider than its box.
|
|
16
|
+
* Thirteen product-console routes opened a sheet that looked like that, which
|
|
17
|
+
* is why that console has carried a rule against `[data-slot='sheet-content']`
|
|
18
|
+
* since its M10 lane; this is that rule brought home, and the console can drop
|
|
19
|
+
* its copy.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ THE PHONE VALUES CARRY THE MODIFIER, THE DESKTOP ONES DO NOT, AND THAT
|
|
22
|
+
* ASYMMETRY IS THE WHOLE MECHANISM. `cn` resolves these through tailwind-merge
|
|
23
|
+
* before the cascade sees them, and tailwind-merge only drops a conflicting
|
|
24
|
+
* class carrying the SAME modifiers. So a caller's `w-[594px]` replaces
|
|
25
|
+
* `w-2/5` and leaves `max-sm:w-full` standing: the fifteen product-console
|
|
26
|
+
* sheets that declare a pixel width of their own — and the two that declare
|
|
27
|
+
* `w-full` — all still fill a phone, with no edit at the call site. Written
|
|
28
|
+
* the other way round — `w-full sm:w-2/5` — the caller's bare `w-[594px]`
|
|
29
|
+
* would have been overruled by `sm:w-2/5` at every desktop width instead,
|
|
30
|
+
* which is the same bug pointing the other way.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ IT CUTS BOTH WAYS, AND THE PADDING IS WHERE THAT IS FELT. A caller's bare
|
|
33
|
+
* `p-0` drops `p-12` and cannot reach `max-sm:px-4` either, so a panel that
|
|
34
|
+
* asked for no padding gets 16px a side below 40rem. That is deliberate — it
|
|
35
|
+
* is what makes the phone step reach the call sites that need it most — and it
|
|
36
|
+
* is what product-console's own unlayered rule already did to those five
|
|
37
|
+
* sheets, so nothing there moves. For any other consumer it is a behaviour
|
|
38
|
+
* change from 2.0.0-beta.11, and the way out is one token.
|
|
39
|
+
*
|
|
40
|
+
* A call site that genuinely wants its own phone width or padding says so with
|
|
41
|
+
* the modifier (`max-sm:w-[320px]`, `max-sm:p-0`), which is one token and
|
|
42
|
+
* strictly more than the console's unlayered rule allowed anyone.
|
|
43
|
+
*
|
|
44
|
+
* 40rem rather than 768px, unlike `SidebarTrigger` next door: nothing here is
|
|
45
|
+
* paired with a pixel media query in JavaScript. This is a reading-width
|
|
46
|
+
* decision and it is the complement of `sm:`, so it moves with the reader's
|
|
47
|
+
* font exactly as the rest of the package's responsive steps do. The one panel
|
|
48
|
+
* that IS coupled to the pixel breakpoint is the navigation drawer, and
|
|
49
|
+
* `SidebarRoot` opts it out by restating both tokens under `max-sm:`.
|
|
50
|
+
*/
|
|
9
51
|
declare const sheetVariants: (props?: ({
|
|
10
52
|
side?: "bottom" | "left" | "right" | "top" | null | undefined;
|
|
11
53
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sheet/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,cAAc,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,iBAAS,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,IAAI,CAAC,qBAE5E;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAErD;AAED,iBAAS,UAAU,CAAC,EAClB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,MAAM,CAAC,qBAEpD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAWrD;AAED,QAAA,MAAM,aAAa;;8EAiBlB,CAAA;AAED,KAAK,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,GAC1E,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;AAEpC,iBAAS,YAAY,CAAC,EACpB,IAAc,EACd,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA6CnB;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAWnD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,WAAW,CAAC,qBAQzD;AAED,OAAO,EACL,KAAK,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,EACjB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sheet/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,cAAc,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,iBAAS,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,IAAI,CAAC,qBAE5E;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAErD;AAED,iBAAS,UAAU,CAAC,EAClB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,MAAM,CAAC,qBAEpD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAWrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,QAAA,MAAM,aAAa;;8EAiBlB,CAAA;AAED,KAAK,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,GAC1E,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;AAEpC,iBAAS,YAAY,CAAC,EACpB,IAAc,EACd,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA6CnB;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAWnD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,WAAW,CAAC,qBAQzD;AAED,OAAO,EACL,KAAK,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,EACjB,CAAA"}
|
|
@@ -65,13 +65,55 @@ function SheetPortal({ ...props }) {
|
|
|
65
65
|
function SheetOverlay({ className, ...props }) {
|
|
66
66
|
return ((0, jsx_runtime_1.jsx)(SheetPrimitive.Overlay, { "data-slot": "sheet-overlay", className: (0, utils_1.cn)('motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', className), ...props }));
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* ⛔ A PANEL BUILT FOR A DESKTOP HAS TO STOP BEING TWO FIFTHS ON A PHONE.
|
|
70
|
+
*
|
|
71
|
+
* `w-2/5` and `p-12` were unconditional. At a 390px viewport that is a 156px
|
|
72
|
+
* panel with 59px of usable width, and a form field inside it measures 9px of
|
|
73
|
+
* text — a sheet that cannot be read, let alone filled in. At 320px the panel
|
|
74
|
+
* is 128px and the field has NOTHING: its own padding is wider than its box.
|
|
75
|
+
* Thirteen product-console routes opened a sheet that looked like that, which
|
|
76
|
+
* is why that console has carried a rule against `[data-slot='sheet-content']`
|
|
77
|
+
* since its M10 lane; this is that rule brought home, and the console can drop
|
|
78
|
+
* its copy.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ THE PHONE VALUES CARRY THE MODIFIER, THE DESKTOP ONES DO NOT, AND THAT
|
|
81
|
+
* ASYMMETRY IS THE WHOLE MECHANISM. `cn` resolves these through tailwind-merge
|
|
82
|
+
* before the cascade sees them, and tailwind-merge only drops a conflicting
|
|
83
|
+
* class carrying the SAME modifiers. So a caller's `w-[594px]` replaces
|
|
84
|
+
* `w-2/5` and leaves `max-sm:w-full` standing: the fifteen product-console
|
|
85
|
+
* sheets that declare a pixel width of their own — and the two that declare
|
|
86
|
+
* `w-full` — all still fill a phone, with no edit at the call site. Written
|
|
87
|
+
* the other way round — `w-full sm:w-2/5` — the caller's bare `w-[594px]`
|
|
88
|
+
* would have been overruled by `sm:w-2/5` at every desktop width instead,
|
|
89
|
+
* which is the same bug pointing the other way.
|
|
90
|
+
*
|
|
91
|
+
* ⚠️ IT CUTS BOTH WAYS, AND THE PADDING IS WHERE THAT IS FELT. A caller's bare
|
|
92
|
+
* `p-0` drops `p-12` and cannot reach `max-sm:px-4` either, so a panel that
|
|
93
|
+
* asked for no padding gets 16px a side below 40rem. That is deliberate — it
|
|
94
|
+
* is what makes the phone step reach the call sites that need it most — and it
|
|
95
|
+
* is what product-console's own unlayered rule already did to those five
|
|
96
|
+
* sheets, so nothing there moves. For any other consumer it is a behaviour
|
|
97
|
+
* change from 2.0.0-beta.11, and the way out is one token.
|
|
98
|
+
*
|
|
99
|
+
* A call site that genuinely wants its own phone width or padding says so with
|
|
100
|
+
* the modifier (`max-sm:w-[320px]`, `max-sm:p-0`), which is one token and
|
|
101
|
+
* strictly more than the console's unlayered rule allowed anyone.
|
|
102
|
+
*
|
|
103
|
+
* 40rem rather than 768px, unlike `SidebarTrigger` next door: nothing here is
|
|
104
|
+
* paired with a pixel media query in JavaScript. This is a reading-width
|
|
105
|
+
* decision and it is the complement of `sm:`, so it moves with the reader's
|
|
106
|
+
* font exactly as the rest of the package's responsive steps do. The one panel
|
|
107
|
+
* that IS coupled to the pixel breakpoint is the navigation drawer, and
|
|
108
|
+
* `SidebarRoot` opts it out by restating both tokens under `max-sm:`.
|
|
109
|
+
*/
|
|
110
|
+
const sheetVariants = (0, class_variance_authority_1.cva)('fixed z-50 gap-4 bg-background p-12 max-sm:px-4 shadow-lg transition ease-in-out motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', {
|
|
69
111
|
variants: {
|
|
70
112
|
side: {
|
|
71
113
|
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
|
72
114
|
bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
|
73
|
-
left: 'inset-y-0 left-0 h-full w-2/5 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
|
|
74
|
-
right: 'inset-y-0 right-0 h-full w-2/5
|
|
115
|
+
left: 'inset-y-0 left-0 h-full w-2/5 max-sm:w-full border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
|
|
116
|
+
right: 'inset-y-0 right-0 h-full w-2/5 max-sm:w-full border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right'
|
|
75
117
|
}
|
|
76
118
|
},
|
|
77
119
|
defaultVariants: {
|
|
@@ -2,11 +2,32 @@ import React from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* ⛔ THE NEGATION OF THE TRIGGER'S QUERY, SPELLED AS A NEGATION.
|
|
6
|
+
*
|
|
7
|
+
* `SidebarTrigger` hides itself at `min-[768px]`, which is `width >= 768`. This
|
|
8
|
+
* is its exact complement AT ANY PRECISION, which `(max-width: 767px)` was not:
|
|
9
|
+
* that pair leaves the open interval (767, 768) matched by neither rule, and a
|
|
10
|
+
* viewport there draws the rail AND a hamburger that opens nothing — with the
|
|
11
|
+
* rail inline, `SidebarRoot` mounts no `Sheet`, so the control is dead. Chromium
|
|
12
|
+
* snaps its layout viewport to a whole pixel and never lands there, measured at
|
|
13
|
+
* four zoom levels; that is a rounding behaviour to rely on, not a boundary.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ THE PAIR HAS TO BE IN THE SAME UNIT. It used to be `md:hidden` over there,
|
|
16
|
+
* and `md` is 48rem: rem in a media query follows the BROWSER'S default font
|
|
17
|
+
* size, so at Chrome's "Small" setting that class hid the hamburger from 576px
|
|
18
|
+
* while this query still asked for a drawer up to 767px — a band of viewports
|
|
19
|
+
* with no navigation at all. Anything else that hides or shows a control at
|
|
20
|
+
* this boundary states it in pixels for the same reason.
|
|
21
|
+
*
|
|
22
|
+
* Media Queries Level 4 boolean logic, which is not a new requirement here:
|
|
23
|
+
* Tailwind v4 emits MQ4 RANGE syntax for everything this package ships —
|
|
24
|
+
* `min-[768px]` on the trigger compiles to `@media (width >= 768px)` and
|
|
25
|
+
* `max-sm:` on `SheetContent` to `@media (width < 40rem)` — and range syntax
|
|
26
|
+
* and boolean `not` landed in the same browsers (Chrome 88, Firefox 64,
|
|
27
|
+
* Safari 16.4). `(width < 768px)` is the equivalent range spelling of this
|
|
28
|
+
* line, should a reader prefer the pair to match character for character.
|
|
8
29
|
*/
|
|
9
|
-
export declare const SIDEBAR_MOBILE_QUERY = "(
|
|
30
|
+
export declare const SIDEBAR_MOBILE_QUERY = "not (min-width: 768px)";
|
|
10
31
|
export type SidebarContextProps = {
|
|
11
32
|
isCollapsed: boolean;
|
|
12
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-provider.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB
|
|
1
|
+
{"version":3,"file":"sidebar-provider.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAU5D,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;OAGG;IACH,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACvE,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAC1C,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACvD,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,gDAEf,CAAA;AAEZ,eAAO,MAAM,UAAU,2BAMtB,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,cAAc,KAAK,CAAC,iBAAiB,sBAkKpE,CAAA"}
|
|
@@ -11,11 +11,32 @@ const storage_1 = require("../../../lib/storage");
|
|
|
11
11
|
/**
|
|
12
12
|
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* ⛔ THE NEGATION OF THE TRIGGER'S QUERY, SPELLED AS A NEGATION.
|
|
15
|
+
*
|
|
16
|
+
* `SidebarTrigger` hides itself at `min-[768px]`, which is `width >= 768`. This
|
|
17
|
+
* is its exact complement AT ANY PRECISION, which `(max-width: 767px)` was not:
|
|
18
|
+
* that pair leaves the open interval (767, 768) matched by neither rule, and a
|
|
19
|
+
* viewport there draws the rail AND a hamburger that opens nothing — with the
|
|
20
|
+
* rail inline, `SidebarRoot` mounts no `Sheet`, so the control is dead. Chromium
|
|
21
|
+
* snaps its layout viewport to a whole pixel and never lands there, measured at
|
|
22
|
+
* four zoom levels; that is a rounding behaviour to rely on, not a boundary.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ THE PAIR HAS TO BE IN THE SAME UNIT. It used to be `md:hidden` over there,
|
|
25
|
+
* and `md` is 48rem: rem in a media query follows the BROWSER'S default font
|
|
26
|
+
* size, so at Chrome's "Small" setting that class hid the hamburger from 576px
|
|
27
|
+
* while this query still asked for a drawer up to 767px — a band of viewports
|
|
28
|
+
* with no navigation at all. Anything else that hides or shows a control at
|
|
29
|
+
* this boundary states it in pixels for the same reason.
|
|
30
|
+
*
|
|
31
|
+
* Media Queries Level 4 boolean logic, which is not a new requirement here:
|
|
32
|
+
* Tailwind v4 emits MQ4 RANGE syntax for everything this package ships —
|
|
33
|
+
* `min-[768px]` on the trigger compiles to `@media (width >= 768px)` and
|
|
34
|
+
* `max-sm:` on `SheetContent` to `@media (width < 40rem)` — and range syntax
|
|
35
|
+
* and boolean `not` landed in the same browsers (Chrome 88, Firefox 64,
|
|
36
|
+
* Safari 16.4). `(width < 768px)` is the equivalent range spelling of this
|
|
37
|
+
* line, should a reader prefer the pair to match character for character.
|
|
17
38
|
*/
|
|
18
|
-
exports.SIDEBAR_MOBILE_QUERY = '(
|
|
39
|
+
exports.SIDEBAR_MOBILE_QUERY = 'not (min-width: 768px)';
|
|
19
40
|
/**
|
|
20
41
|
* Where focus goes when the rail has to receive it. The rail's own content is
|
|
21
42
|
* links and buttons, so this is deliberately the short list rather than the
|
|
@@ -80,7 +101,7 @@ const SidebarProvider = ({ children }) => {
|
|
|
80
101
|
// ⛔ AND NOT AT ALL WHEN THE VIEWPORT IS THE ONE THAT DISMISSED IT. A
|
|
81
102
|
// tablet rotated from portrait to landscape with the navigation open
|
|
82
103
|
// crosses 768px, so the drawer is replaced by the rail and the opener is
|
|
83
|
-
// `SidebarTrigger` — which the same breakpoint just hid
|
|
104
|
+
// `SidebarTrigger` — which the same breakpoint just hid.
|
|
84
105
|
// `.focus()` on a `display: none` element is a silent no-op, and Radix's
|
|
85
106
|
// own restore is already `preventDefault()`ed, so focus fell to `<body>`
|
|
86
107
|
// and the reader lost their place (SC 2.4.3). Measured in Chromium at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-root.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-root.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqEzB,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC3D;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,kDAMzB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"sidebar-root.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-root.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqEzB,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC3D;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,kDAMzB,gBAAgB,sBAqHlB,CAAA"}
|
|
@@ -88,7 +88,18 @@ const SidebarRoot = ({ className, id, mobile = 'inline', mobileTitle = 'Navigati
|
|
|
88
88
|
// its own. `max-w-full` so a consumer widening `--sidebar-width` past
|
|
89
89
|
// the viewport still cannot push the drawer off screen. The lifted
|
|
90
90
|
// override goes LAST so it re-points the variable this element reads.
|
|
91
|
-
|
|
91
|
+
//
|
|
92
|
+
// ⛔ THE `max-sm:` PAIR IS NOT A DUPLICATE. `SheetContent` grew a
|
|
93
|
+
// phone step — `max-sm:w-full max-sm:px-4` — because a form panel at
|
|
94
|
+
// two fifths of a 390px screen is unreadable. A NAVIGATION drawer is
|
|
95
|
+
// the one panel that must not take it: 244px over a dimmed page is
|
|
96
|
+
// the design, and full-bleed removes the tap-outside-to-close target
|
|
97
|
+
// that every other way out of this drawer is an alternative to.
|
|
98
|
+
// tailwind-merge drops a conflicting class only when the modifiers
|
|
99
|
+
// match, so a bare `w-[…]` cannot reach `max-sm:w-full` and these two
|
|
100
|
+
// tokens are what state the exemption. Measured: without them the
|
|
101
|
+
// drawer is 390px wide with 16px of inset on a 390px phone.
|
|
102
|
+
className: (0, utils_1.cn)('w-[var(--sidebar-width)] max-w-full gap-0 p-0 max-sm:w-[var(--sidebar-width)] max-sm:p-0', liftWidthOverrides(className)), "aria-describedby": undefined,
|
|
92
103
|
// ⛔ RADIX'S OWN RESTORE GOES NOWHERE HERE. `DialogContent`
|
|
93
104
|
// preventDefaults the focus scope's restore and focuses the ref a
|
|
94
105
|
// `SheetTrigger` would have filled; this drawer is opened from provider
|
|
@@ -4,9 +4,36 @@ export type SidebarTriggerProps = React.ComponentProps<typeof IconButton>;
|
|
|
4
4
|
/**
|
|
5
5
|
* The control that opens the mobile drawer.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* a second way to reach it is a second thing to explain; a consumer
|
|
9
|
-
* it everywhere passes its own display class.
|
|
7
|
+
* Hidden from 768px up by default, because above it the rail is already on
|
|
8
|
+
* screen and a second way to reach it is a second thing to explain; a consumer
|
|
9
|
+
* that wants it everywhere passes its own display class.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ THE BREAKPOINT IS IN PIXELS, AND `md:hidden` IS NOT A SPELLING OF IT.
|
|
12
|
+
*
|
|
13
|
+
* Whether the rail became a drawer is decided by `SIDEBAR_MOBILE_QUERY`, which
|
|
14
|
+
* is `not (min-width: 768px)` — pixels. `md:` is 48rem, and rem inside a MEDIA
|
|
15
|
+
* QUERY resolves against the browser's default font size rather than anything
|
|
16
|
+
* the page declares, so the two flip at different widths the moment a reader
|
|
17
|
+
* changes that setting. At Chrome's "Small" (12px) `md:` turns over at 576px
|
|
18
|
+
* while the drawer still turns over at 768px: every window between them showed
|
|
19
|
+
* NO RAIL AND NO HAMBURGER, which is a console with no navigation at all.
|
|
20
|
+
* Measured on product-console; the band is four of this package's own fixtures
|
|
21
|
+
* in `scripts/measure-phone-shapes.mjs`. At "Very large" (20px) the same
|
|
22
|
+
* mismatch runs the other way and 768–959px drew both.
|
|
23
|
+
*
|
|
24
|
+
* `min-[768px]:hidden` is the exact complement of that query, at every font
|
|
25
|
+
* size AND at any sub-pixel precision — which is why the query next door is
|
|
26
|
+
* spelled as a negation rather than as `(max-width: 767px)`: that pair leaves
|
|
27
|
+
* the open interval (767, 768) matched by neither, and a viewport there draws
|
|
28
|
+
* the rail plus a hamburger that opens nothing.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ THE PROPERTY IS ABOUT THE TWO MEDIA DECISIONS, NOT ABOUT THE FIRST PAINT.
|
|
31
|
+
* The harness enforces one of the two and never neither or both, which holds
|
|
32
|
+
* wherever `isMobile` has been read. `isMobile` starts false on purpose — there
|
|
33
|
+
* is no viewport on the server — and is corrected in an effect, so on a phone
|
|
34
|
+
* the first painted frame shows the inline rail while this control is already
|
|
35
|
+
* on screen. That one hydration is the deliberate price of hydration safety,
|
|
36
|
+
* documented on `SidebarProvider`'s own effect, and predates this class.
|
|
10
37
|
*
|
|
11
38
|
* ⚠️ `aria-controls` IS SET ONLY WHILE THE DRAWER EXISTS. Radix unmounts the
|
|
12
39
|
* dialog content when closed, and a reference to an element that is not in the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-trigger.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-trigger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,CAAA;AAEzE
|
|
1
|
+
{"version":3,"file":"sidebar-trigger.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-trigger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,CAAA;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,cAAc,GAAI,2DAK5B,mBAAmB,sBAqBrB,CAAA"}
|
|
@@ -10,9 +10,36 @@ const sidebar_provider_1 = require("./sidebar-provider");
|
|
|
10
10
|
/**
|
|
11
11
|
* The control that opens the mobile drawer.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* a second way to reach it is a second thing to explain; a consumer
|
|
15
|
-
* it everywhere passes its own display class.
|
|
13
|
+
* Hidden from 768px up by default, because above it the rail is already on
|
|
14
|
+
* screen and a second way to reach it is a second thing to explain; a consumer
|
|
15
|
+
* that wants it everywhere passes its own display class.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ THE BREAKPOINT IS IN PIXELS, AND `md:hidden` IS NOT A SPELLING OF IT.
|
|
18
|
+
*
|
|
19
|
+
* Whether the rail became a drawer is decided by `SIDEBAR_MOBILE_QUERY`, which
|
|
20
|
+
* is `not (min-width: 768px)` — pixels. `md:` is 48rem, and rem inside a MEDIA
|
|
21
|
+
* QUERY resolves against the browser's default font size rather than anything
|
|
22
|
+
* the page declares, so the two flip at different widths the moment a reader
|
|
23
|
+
* changes that setting. At Chrome's "Small" (12px) `md:` turns over at 576px
|
|
24
|
+
* while the drawer still turns over at 768px: every window between them showed
|
|
25
|
+
* NO RAIL AND NO HAMBURGER, which is a console with no navigation at all.
|
|
26
|
+
* Measured on product-console; the band is four of this package's own fixtures
|
|
27
|
+
* in `scripts/measure-phone-shapes.mjs`. At "Very large" (20px) the same
|
|
28
|
+
* mismatch runs the other way and 768–959px drew both.
|
|
29
|
+
*
|
|
30
|
+
* `min-[768px]:hidden` is the exact complement of that query, at every font
|
|
31
|
+
* size AND at any sub-pixel precision — which is why the query next door is
|
|
32
|
+
* spelled as a negation rather than as `(max-width: 767px)`: that pair leaves
|
|
33
|
+
* the open interval (767, 768) matched by neither, and a viewport there draws
|
|
34
|
+
* the rail plus a hamburger that opens nothing.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ THE PROPERTY IS ABOUT THE TWO MEDIA DECISIONS, NOT ABOUT THE FIRST PAINT.
|
|
37
|
+
* The harness enforces one of the two and never neither or both, which holds
|
|
38
|
+
* wherever `isMobile` has been read. `isMobile` starts false on purpose — there
|
|
39
|
+
* is no viewport on the server — and is corrected in an effect, so on a phone
|
|
40
|
+
* the first painted frame shows the inline rail while this control is already
|
|
41
|
+
* on screen. That one hydration is the deliberate price of hydration safety,
|
|
42
|
+
* documented on `SidebarProvider`'s own effect, and predates this class.
|
|
16
43
|
*
|
|
17
44
|
* ⚠️ `aria-controls` IS SET ONLY WHILE THE DRAWER EXISTS. Radix unmounts the
|
|
18
45
|
* dialog content when closed, and a reference to an element that is not in the
|
|
@@ -22,7 +49,7 @@ const sidebar_provider_1 = require("./sidebar-provider");
|
|
|
22
49
|
*/
|
|
23
50
|
const SidebarTrigger = ({ className, onClick, 'aria-label': ariaLabel = 'Open navigation', ...props }) => {
|
|
24
51
|
const { openMobile, setOpenMobile, sidebarId } = (0, sidebar_provider_1.useSidebar)();
|
|
25
|
-
return ((0, jsx_runtime_1.jsx)(icon_button_1.IconButton, { type: "button", variant: "outline", "data-slot": "sidebar-trigger", "aria-label": ariaLabel, "aria-expanded": openMobile, "aria-controls": openMobile ? sidebarId : undefined, className: (0, utils_1.cn)('
|
|
52
|
+
return ((0, jsx_runtime_1.jsx)(icon_button_1.IconButton, { type: "button", variant: "outline", "data-slot": "sidebar-trigger", "aria-label": ariaLabel, "aria-expanded": openMobile, "aria-controls": openMobile ? sidebarId : undefined, className: (0, utils_1.cn)('min-[768px]:hidden', className), onClick: (event) => {
|
|
26
53
|
onClick?.(event);
|
|
27
54
|
setOpenMobile(!openMobile);
|
|
28
55
|
}, ...props, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Menu, { "aria-hidden": true }) }));
|
|
@@ -6,6 +6,48 @@ declare function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPri
|
|
|
6
6
|
declare function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>): React.JSX.Element;
|
|
7
7
|
declare function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>): React.JSX.Element;
|
|
8
8
|
declare function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>): React.JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* ⛔ A PANEL BUILT FOR A DESKTOP HAS TO STOP BEING TWO FIFTHS ON A PHONE.
|
|
11
|
+
*
|
|
12
|
+
* `w-2/5` and `p-12` were unconditional. At a 390px viewport that is a 156px
|
|
13
|
+
* panel with 59px of usable width, and a form field inside it measures 9px of
|
|
14
|
+
* text — a sheet that cannot be read, let alone filled in. At 320px the panel
|
|
15
|
+
* is 128px and the field has NOTHING: its own padding is wider than its box.
|
|
16
|
+
* Thirteen product-console routes opened a sheet that looked like that, which
|
|
17
|
+
* is why that console has carried a rule against `[data-slot='sheet-content']`
|
|
18
|
+
* since its M10 lane; this is that rule brought home, and the console can drop
|
|
19
|
+
* its copy.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ THE PHONE VALUES CARRY THE MODIFIER, THE DESKTOP ONES DO NOT, AND THAT
|
|
22
|
+
* ASYMMETRY IS THE WHOLE MECHANISM. `cn` resolves these through tailwind-merge
|
|
23
|
+
* before the cascade sees them, and tailwind-merge only drops a conflicting
|
|
24
|
+
* class carrying the SAME modifiers. So a caller's `w-[594px]` replaces
|
|
25
|
+
* `w-2/5` and leaves `max-sm:w-full` standing: the fifteen product-console
|
|
26
|
+
* sheets that declare a pixel width of their own — and the two that declare
|
|
27
|
+
* `w-full` — all still fill a phone, with no edit at the call site. Written
|
|
28
|
+
* the other way round — `w-full sm:w-2/5` — the caller's bare `w-[594px]`
|
|
29
|
+
* would have been overruled by `sm:w-2/5` at every desktop width instead,
|
|
30
|
+
* which is the same bug pointing the other way.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ IT CUTS BOTH WAYS, AND THE PADDING IS WHERE THAT IS FELT. A caller's bare
|
|
33
|
+
* `p-0` drops `p-12` and cannot reach `max-sm:px-4` either, so a panel that
|
|
34
|
+
* asked for no padding gets 16px a side below 40rem. That is deliberate — it
|
|
35
|
+
* is what makes the phone step reach the call sites that need it most — and it
|
|
36
|
+
* is what product-console's own unlayered rule already did to those five
|
|
37
|
+
* sheets, so nothing there moves. For any other consumer it is a behaviour
|
|
38
|
+
* change from 2.0.0-beta.11, and the way out is one token.
|
|
39
|
+
*
|
|
40
|
+
* A call site that genuinely wants its own phone width or padding says so with
|
|
41
|
+
* the modifier (`max-sm:w-[320px]`, `max-sm:p-0`), which is one token and
|
|
42
|
+
* strictly more than the console's unlayered rule allowed anyone.
|
|
43
|
+
*
|
|
44
|
+
* 40rem rather than 768px, unlike `SidebarTrigger` next door: nothing here is
|
|
45
|
+
* paired with a pixel media query in JavaScript. This is a reading-width
|
|
46
|
+
* decision and it is the complement of `sm:`, so it moves with the reader's
|
|
47
|
+
* font exactly as the rest of the package's responsive steps do. The one panel
|
|
48
|
+
* that IS coupled to the pixel breakpoint is the navigation drawer, and
|
|
49
|
+
* `SidebarRoot` opts it out by restating both tokens under `max-sm:`.
|
|
50
|
+
*/
|
|
9
51
|
declare const sheetVariants: (props?: ({
|
|
10
52
|
side?: "bottom" | "left" | "right" | "top" | null | undefined;
|
|
11
53
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sheet/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,cAAc,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,iBAAS,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,IAAI,CAAC,qBAE5E;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAErD;AAED,iBAAS,UAAU,CAAC,EAClB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,MAAM,CAAC,qBAEpD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAWrD;AAED,QAAA,MAAM,aAAa;;8EAiBlB,CAAA;AAED,KAAK,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,GAC1E,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;AAEpC,iBAAS,YAAY,CAAC,EACpB,IAAc,EACd,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA6CnB;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAWnD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,WAAW,CAAC,qBAQzD;AAED,OAAO,EACL,KAAK,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,EACjB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sheet/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,cAAc,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,iBAAS,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,IAAI,CAAC,qBAE5E;AAED,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAErD;AAED,iBAAS,UAAU,CAAC,EAClB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,MAAM,CAAC,qBAEpD;AAED,iBAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,qBAWrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,QAAA,MAAM,aAAa;;8EAiBlB,CAAA;AAED,KAAK,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,GAC1E,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;AAEpC,iBAAS,YAAY,CAAC,EACpB,IAAc,EACd,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA6CnB;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,qBAWnD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,WAAW,CAAC,qBAQzD;AAED,OAAO,EACL,KAAK,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,EACjB,CAAA"}
|
|
@@ -20,13 +20,55 @@ function SheetPortal({ ...props }) {
|
|
|
20
20
|
function SheetOverlay({ className, ...props }) {
|
|
21
21
|
return (_jsx(SheetPrimitive.Overlay, { "data-slot": "sheet-overlay", className: cn('motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', className), ...props }));
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* ⛔ A PANEL BUILT FOR A DESKTOP HAS TO STOP BEING TWO FIFTHS ON A PHONE.
|
|
25
|
+
*
|
|
26
|
+
* `w-2/5` and `p-12` were unconditional. At a 390px viewport that is a 156px
|
|
27
|
+
* panel with 59px of usable width, and a form field inside it measures 9px of
|
|
28
|
+
* text — a sheet that cannot be read, let alone filled in. At 320px the panel
|
|
29
|
+
* is 128px and the field has NOTHING: its own padding is wider than its box.
|
|
30
|
+
* Thirteen product-console routes opened a sheet that looked like that, which
|
|
31
|
+
* is why that console has carried a rule against `[data-slot='sheet-content']`
|
|
32
|
+
* since its M10 lane; this is that rule brought home, and the console can drop
|
|
33
|
+
* its copy.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ THE PHONE VALUES CARRY THE MODIFIER, THE DESKTOP ONES DO NOT, AND THAT
|
|
36
|
+
* ASYMMETRY IS THE WHOLE MECHANISM. `cn` resolves these through tailwind-merge
|
|
37
|
+
* before the cascade sees them, and tailwind-merge only drops a conflicting
|
|
38
|
+
* class carrying the SAME modifiers. So a caller's `w-[594px]` replaces
|
|
39
|
+
* `w-2/5` and leaves `max-sm:w-full` standing: the fifteen product-console
|
|
40
|
+
* sheets that declare a pixel width of their own — and the two that declare
|
|
41
|
+
* `w-full` — all still fill a phone, with no edit at the call site. Written
|
|
42
|
+
* the other way round — `w-full sm:w-2/5` — the caller's bare `w-[594px]`
|
|
43
|
+
* would have been overruled by `sm:w-2/5` at every desktop width instead,
|
|
44
|
+
* which is the same bug pointing the other way.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ IT CUTS BOTH WAYS, AND THE PADDING IS WHERE THAT IS FELT. A caller's bare
|
|
47
|
+
* `p-0` drops `p-12` and cannot reach `max-sm:px-4` either, so a panel that
|
|
48
|
+
* asked for no padding gets 16px a side below 40rem. That is deliberate — it
|
|
49
|
+
* is what makes the phone step reach the call sites that need it most — and it
|
|
50
|
+
* is what product-console's own unlayered rule already did to those five
|
|
51
|
+
* sheets, so nothing there moves. For any other consumer it is a behaviour
|
|
52
|
+
* change from 2.0.0-beta.11, and the way out is one token.
|
|
53
|
+
*
|
|
54
|
+
* A call site that genuinely wants its own phone width or padding says so with
|
|
55
|
+
* the modifier (`max-sm:w-[320px]`, `max-sm:p-0`), which is one token and
|
|
56
|
+
* strictly more than the console's unlayered rule allowed anyone.
|
|
57
|
+
*
|
|
58
|
+
* 40rem rather than 768px, unlike `SidebarTrigger` next door: nothing here is
|
|
59
|
+
* paired with a pixel media query in JavaScript. This is a reading-width
|
|
60
|
+
* decision and it is the complement of `sm:`, so it moves with the reader's
|
|
61
|
+
* font exactly as the rest of the package's responsive steps do. The one panel
|
|
62
|
+
* that IS coupled to the pixel breakpoint is the navigation drawer, and
|
|
63
|
+
* `SidebarRoot` opts it out by restating both tokens under `max-sm:`.
|
|
64
|
+
*/
|
|
65
|
+
const sheetVariants = cva('fixed z-50 gap-4 bg-background p-12 max-sm:px-4 shadow-lg transition ease-in-out motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', {
|
|
24
66
|
variants: {
|
|
25
67
|
side: {
|
|
26
68
|
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
|
27
69
|
bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
|
28
|
-
left: 'inset-y-0 left-0 h-full w-2/5 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
|
|
29
|
-
right: 'inset-y-0 right-0 h-full w-2/5
|
|
70
|
+
left: 'inset-y-0 left-0 h-full w-2/5 max-sm:w-full border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
|
|
71
|
+
right: 'inset-y-0 right-0 h-full w-2/5 max-sm:w-full border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right'
|
|
30
72
|
}
|
|
31
73
|
},
|
|
32
74
|
defaultVariants: {
|
|
@@ -2,11 +2,32 @@ import React from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* ⛔ THE NEGATION OF THE TRIGGER'S QUERY, SPELLED AS A NEGATION.
|
|
6
|
+
*
|
|
7
|
+
* `SidebarTrigger` hides itself at `min-[768px]`, which is `width >= 768`. This
|
|
8
|
+
* is its exact complement AT ANY PRECISION, which `(max-width: 767px)` was not:
|
|
9
|
+
* that pair leaves the open interval (767, 768) matched by neither rule, and a
|
|
10
|
+
* viewport there draws the rail AND a hamburger that opens nothing — with the
|
|
11
|
+
* rail inline, `SidebarRoot` mounts no `Sheet`, so the control is dead. Chromium
|
|
12
|
+
* snaps its layout viewport to a whole pixel and never lands there, measured at
|
|
13
|
+
* four zoom levels; that is a rounding behaviour to rely on, not a boundary.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ THE PAIR HAS TO BE IN THE SAME UNIT. It used to be `md:hidden` over there,
|
|
16
|
+
* and `md` is 48rem: rem in a media query follows the BROWSER'S default font
|
|
17
|
+
* size, so at Chrome's "Small" setting that class hid the hamburger from 576px
|
|
18
|
+
* while this query still asked for a drawer up to 767px — a band of viewports
|
|
19
|
+
* with no navigation at all. Anything else that hides or shows a control at
|
|
20
|
+
* this boundary states it in pixels for the same reason.
|
|
21
|
+
*
|
|
22
|
+
* Media Queries Level 4 boolean logic, which is not a new requirement here:
|
|
23
|
+
* Tailwind v4 emits MQ4 RANGE syntax for everything this package ships —
|
|
24
|
+
* `min-[768px]` on the trigger compiles to `@media (width >= 768px)` and
|
|
25
|
+
* `max-sm:` on `SheetContent` to `@media (width < 40rem)` — and range syntax
|
|
26
|
+
* and boolean `not` landed in the same browsers (Chrome 88, Firefox 64,
|
|
27
|
+
* Safari 16.4). `(width < 768px)` is the equivalent range spelling of this
|
|
28
|
+
* line, should a reader prefer the pair to match character for character.
|
|
8
29
|
*/
|
|
9
|
-
export declare const SIDEBAR_MOBILE_QUERY = "(
|
|
30
|
+
export declare const SIDEBAR_MOBILE_QUERY = "not (min-width: 768px)";
|
|
10
31
|
export type SidebarContextProps = {
|
|
11
32
|
isCollapsed: boolean;
|
|
12
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-provider.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB
|
|
1
|
+
{"version":3,"file":"sidebar-provider.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAU5D,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAA;IACjB,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;OAGG;IACH,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACvE,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAC1C,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACvD,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,gDAEf,CAAA;AAEZ,eAAO,MAAM,UAAU,2BAMtB,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,cAAc,KAAK,CAAC,iBAAiB,sBAkKpE,CAAA"}
|
|
@@ -5,11 +5,32 @@ import { getStorage, getStorageObject } from '../../../lib/storage/index.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* ⛔ THE NEGATION OF THE TRIGGER'S QUERY, SPELLED AS A NEGATION.
|
|
9
|
+
*
|
|
10
|
+
* `SidebarTrigger` hides itself at `min-[768px]`, which is `width >= 768`. This
|
|
11
|
+
* is its exact complement AT ANY PRECISION, which `(max-width: 767px)` was not:
|
|
12
|
+
* that pair leaves the open interval (767, 768) matched by neither rule, and a
|
|
13
|
+
* viewport there draws the rail AND a hamburger that opens nothing — with the
|
|
14
|
+
* rail inline, `SidebarRoot` mounts no `Sheet`, so the control is dead. Chromium
|
|
15
|
+
* snaps its layout viewport to a whole pixel and never lands there, measured at
|
|
16
|
+
* four zoom levels; that is a rounding behaviour to rely on, not a boundary.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ THE PAIR HAS TO BE IN THE SAME UNIT. It used to be `md:hidden` over there,
|
|
19
|
+
* and `md` is 48rem: rem in a media query follows the BROWSER'S default font
|
|
20
|
+
* size, so at Chrome's "Small" setting that class hid the hamburger from 576px
|
|
21
|
+
* while this query still asked for a drawer up to 767px — a band of viewports
|
|
22
|
+
* with no navigation at all. Anything else that hides or shows a control at
|
|
23
|
+
* this boundary states it in pixels for the same reason.
|
|
24
|
+
*
|
|
25
|
+
* Media Queries Level 4 boolean logic, which is not a new requirement here:
|
|
26
|
+
* Tailwind v4 emits MQ4 RANGE syntax for everything this package ships —
|
|
27
|
+
* `min-[768px]` on the trigger compiles to `@media (width >= 768px)` and
|
|
28
|
+
* `max-sm:` on `SheetContent` to `@media (width < 40rem)` — and range syntax
|
|
29
|
+
* and boolean `not` landed in the same browsers (Chrome 88, Firefox 64,
|
|
30
|
+
* Safari 16.4). `(width < 768px)` is the equivalent range spelling of this
|
|
31
|
+
* line, should a reader prefer the pair to match character for character.
|
|
11
32
|
*/
|
|
12
|
-
export const SIDEBAR_MOBILE_QUERY = '(
|
|
33
|
+
export const SIDEBAR_MOBILE_QUERY = 'not (min-width: 768px)';
|
|
13
34
|
/**
|
|
14
35
|
* Where focus goes when the rail has to receive it. The rail's own content is
|
|
15
36
|
* links and buttons, so this is deliberately the short list rather than the
|
|
@@ -73,7 +94,7 @@ export const SidebarProvider = ({ children }) => {
|
|
|
73
94
|
// ⛔ AND NOT AT ALL WHEN THE VIEWPORT IS THE ONE THAT DISMISSED IT. A
|
|
74
95
|
// tablet rotated from portrait to landscape with the navigation open
|
|
75
96
|
// crosses 768px, so the drawer is replaced by the rail and the opener is
|
|
76
|
-
// `SidebarTrigger` — which the same breakpoint just hid
|
|
97
|
+
// `SidebarTrigger` — which the same breakpoint just hid.
|
|
77
98
|
// `.focus()` on a `display: none` element is a silent no-op, and Radix's
|
|
78
99
|
// own restore is already `preventDefault()`ed, so focus fell to `<body>`
|
|
79
100
|
// and the reader lost their place (SC 2.4.3). Measured in Chromium at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-root.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-root.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqEzB,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC3D;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,kDAMzB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"sidebar-root.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-root.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAqEzB,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC3D;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,kDAMzB,gBAAgB,sBAqHlB,CAAA"}
|
|
@@ -85,7 +85,18 @@ export const SidebarRoot = ({ className, id, mobile = 'inline', mobileTitle = 'N
|
|
|
85
85
|
// its own. `max-w-full` so a consumer widening `--sidebar-width` past
|
|
86
86
|
// the viewport still cannot push the drawer off screen. The lifted
|
|
87
87
|
// override goes LAST so it re-points the variable this element reads.
|
|
88
|
-
|
|
88
|
+
//
|
|
89
|
+
// ⛔ THE `max-sm:` PAIR IS NOT A DUPLICATE. `SheetContent` grew a
|
|
90
|
+
// phone step — `max-sm:w-full max-sm:px-4` — because a form panel at
|
|
91
|
+
// two fifths of a 390px screen is unreadable. A NAVIGATION drawer is
|
|
92
|
+
// the one panel that must not take it: 244px over a dimmed page is
|
|
93
|
+
// the design, and full-bleed removes the tap-outside-to-close target
|
|
94
|
+
// that every other way out of this drawer is an alternative to.
|
|
95
|
+
// tailwind-merge drops a conflicting class only when the modifiers
|
|
96
|
+
// match, so a bare `w-[…]` cannot reach `max-sm:w-full` and these two
|
|
97
|
+
// tokens are what state the exemption. Measured: without them the
|
|
98
|
+
// drawer is 390px wide with 16px of inset on a 390px phone.
|
|
99
|
+
className: cn('w-[var(--sidebar-width)] max-w-full gap-0 p-0 max-sm:w-[var(--sidebar-width)] max-sm:p-0', liftWidthOverrides(className)), "aria-describedby": undefined,
|
|
89
100
|
// ⛔ RADIX'S OWN RESTORE GOES NOWHERE HERE. `DialogContent`
|
|
90
101
|
// preventDefaults the focus scope's restore and focuses the ref a
|
|
91
102
|
// `SheetTrigger` would have filled; this drawer is opened from provider
|
|
@@ -4,9 +4,36 @@ export type SidebarTriggerProps = React.ComponentProps<typeof IconButton>;
|
|
|
4
4
|
/**
|
|
5
5
|
* The control that opens the mobile drawer.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* a second way to reach it is a second thing to explain; a consumer
|
|
9
|
-
* it everywhere passes its own display class.
|
|
7
|
+
* Hidden from 768px up by default, because above it the rail is already on
|
|
8
|
+
* screen and a second way to reach it is a second thing to explain; a consumer
|
|
9
|
+
* that wants it everywhere passes its own display class.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ THE BREAKPOINT IS IN PIXELS, AND `md:hidden` IS NOT A SPELLING OF IT.
|
|
12
|
+
*
|
|
13
|
+
* Whether the rail became a drawer is decided by `SIDEBAR_MOBILE_QUERY`, which
|
|
14
|
+
* is `not (min-width: 768px)` — pixels. `md:` is 48rem, and rem inside a MEDIA
|
|
15
|
+
* QUERY resolves against the browser's default font size rather than anything
|
|
16
|
+
* the page declares, so the two flip at different widths the moment a reader
|
|
17
|
+
* changes that setting. At Chrome's "Small" (12px) `md:` turns over at 576px
|
|
18
|
+
* while the drawer still turns over at 768px: every window between them showed
|
|
19
|
+
* NO RAIL AND NO HAMBURGER, which is a console with no navigation at all.
|
|
20
|
+
* Measured on product-console; the band is four of this package's own fixtures
|
|
21
|
+
* in `scripts/measure-phone-shapes.mjs`. At "Very large" (20px) the same
|
|
22
|
+
* mismatch runs the other way and 768–959px drew both.
|
|
23
|
+
*
|
|
24
|
+
* `min-[768px]:hidden` is the exact complement of that query, at every font
|
|
25
|
+
* size AND at any sub-pixel precision — which is why the query next door is
|
|
26
|
+
* spelled as a negation rather than as `(max-width: 767px)`: that pair leaves
|
|
27
|
+
* the open interval (767, 768) matched by neither, and a viewport there draws
|
|
28
|
+
* the rail plus a hamburger that opens nothing.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ THE PROPERTY IS ABOUT THE TWO MEDIA DECISIONS, NOT ABOUT THE FIRST PAINT.
|
|
31
|
+
* The harness enforces one of the two and never neither or both, which holds
|
|
32
|
+
* wherever `isMobile` has been read. `isMobile` starts false on purpose — there
|
|
33
|
+
* is no viewport on the server — and is corrected in an effect, so on a phone
|
|
34
|
+
* the first painted frame shows the inline rail while this control is already
|
|
35
|
+
* on screen. That one hydration is the deliberate price of hydration safety,
|
|
36
|
+
* documented on `SidebarProvider`'s own effect, and predates this class.
|
|
10
37
|
*
|
|
11
38
|
* ⚠️ `aria-controls` IS SET ONLY WHILE THE DRAWER EXISTS. Radix unmounts the
|
|
12
39
|
* dialog content when closed, and a reference to an element that is not in the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-trigger.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-trigger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,CAAA;AAEzE
|
|
1
|
+
{"version":3,"file":"sidebar-trigger.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/sidebar/sidebar-trigger.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,CAAA;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,cAAc,GAAI,2DAK5B,mBAAmB,sBAqBrB,CAAA"}
|
|
@@ -7,9 +7,36 @@ import { useSidebar } from './sidebar-provider.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* The control that opens the mobile drawer.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* a second way to reach it is a second thing to explain; a consumer
|
|
12
|
-
* it everywhere passes its own display class.
|
|
10
|
+
* Hidden from 768px up by default, because above it the rail is already on
|
|
11
|
+
* screen and a second way to reach it is a second thing to explain; a consumer
|
|
12
|
+
* that wants it everywhere passes its own display class.
|
|
13
|
+
*
|
|
14
|
+
* ⛔ THE BREAKPOINT IS IN PIXELS, AND `md:hidden` IS NOT A SPELLING OF IT.
|
|
15
|
+
*
|
|
16
|
+
* Whether the rail became a drawer is decided by `SIDEBAR_MOBILE_QUERY`, which
|
|
17
|
+
* is `not (min-width: 768px)` — pixels. `md:` is 48rem, and rem inside a MEDIA
|
|
18
|
+
* QUERY resolves against the browser's default font size rather than anything
|
|
19
|
+
* the page declares, so the two flip at different widths the moment a reader
|
|
20
|
+
* changes that setting. At Chrome's "Small" (12px) `md:` turns over at 576px
|
|
21
|
+
* while the drawer still turns over at 768px: every window between them showed
|
|
22
|
+
* NO RAIL AND NO HAMBURGER, which is a console with no navigation at all.
|
|
23
|
+
* Measured on product-console; the band is four of this package's own fixtures
|
|
24
|
+
* in `scripts/measure-phone-shapes.mjs`. At "Very large" (20px) the same
|
|
25
|
+
* mismatch runs the other way and 768–959px drew both.
|
|
26
|
+
*
|
|
27
|
+
* `min-[768px]:hidden` is the exact complement of that query, at every font
|
|
28
|
+
* size AND at any sub-pixel precision — which is why the query next door is
|
|
29
|
+
* spelled as a negation rather than as `(max-width: 767px)`: that pair leaves
|
|
30
|
+
* the open interval (767, 768) matched by neither, and a viewport there draws
|
|
31
|
+
* the rail plus a hamburger that opens nothing.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ THE PROPERTY IS ABOUT THE TWO MEDIA DECISIONS, NOT ABOUT THE FIRST PAINT.
|
|
34
|
+
* The harness enforces one of the two and never neither or both, which holds
|
|
35
|
+
* wherever `isMobile` has been read. `isMobile` starts false on purpose — there
|
|
36
|
+
* is no viewport on the server — and is corrected in an effect, so on a phone
|
|
37
|
+
* the first painted frame shows the inline rail while this control is already
|
|
38
|
+
* on screen. That one hydration is the deliberate price of hydration safety,
|
|
39
|
+
* documented on `SidebarProvider`'s own effect, and predates this class.
|
|
13
40
|
*
|
|
14
41
|
* ⚠️ `aria-controls` IS SET ONLY WHILE THE DRAWER EXISTS. Radix unmounts the
|
|
15
42
|
* dialog content when closed, and a reference to an element that is not in the
|
|
@@ -19,7 +46,7 @@ import { useSidebar } from './sidebar-provider.js';
|
|
|
19
46
|
*/
|
|
20
47
|
export const SidebarTrigger = ({ className, onClick, 'aria-label': ariaLabel = 'Open navigation', ...props }) => {
|
|
21
48
|
const { openMobile, setOpenMobile, sidebarId } = useSidebar();
|
|
22
|
-
return (_jsx(IconButton, { type: "button", variant: "outline", "data-slot": "sidebar-trigger", "aria-label": ariaLabel, "aria-expanded": openMobile, "aria-controls": openMobile ? sidebarId : undefined, className: cn('
|
|
49
|
+
return (_jsx(IconButton, { type: "button", variant: "outline", "data-slot": "sidebar-trigger", "aria-label": ariaLabel, "aria-expanded": openMobile, "aria-controls": openMobile ? sidebarId : undefined, className: cn('min-[768px]:hidden', className), onClick: (event) => {
|
|
23
50
|
onClick?.(event);
|
|
24
51
|
setOpenMobile(!openMobile);
|
|
25
52
|
}, ...props, children: _jsx(Menu, { "aria-hidden": true }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lerianstudio/sindarian-ui",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.12",
|
|
4
4
|
"description": "Sindarian UI - A UI library for Midaz Console",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": {
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"test": "jest",
|
|
51
51
|
"test:e2e": "exit 0",
|
|
52
52
|
"test:cov": "jest --coverage",
|
|
53
|
-
"measure:input-shrink": "node scripts/measure-input-shrink.mjs"
|
|
53
|
+
"measure:input-shrink": "node scripts/measure-input-shrink.mjs",
|
|
54
|
+
"measure:phone-shapes": "node scripts/measure-phone-shapes.mjs"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"@radix-ui/react-accordion": "^1.2.20",
|