@lerianstudio/sindarian-ui 2.0.0-beta.1 → 2.0.0-beta.11
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/dist/components/breadcrumb/index.d.ts.map +1 -1
- package/dist/components/breadcrumb/index.js +25 -1
- package/dist/components/card/account-card/index.js +5 -5
- package/dist/components/card/basic-card/index.d.ts.map +1 -1
- package/dist/components/card/basic-card/index.js +2 -2
- package/dist/components/card/entity-card/index.js +1 -1
- package/dist/components/dialog/confirmation-dialog/index.d.ts.map +1 -1
- package/dist/components/dialog/confirmation-dialog/index.js +4 -1
- package/dist/components/entity-box/index.d.ts +18 -2
- package/dist/components/entity-box/index.d.ts.map +1 -1
- package/dist/components/entity-box/index.js +32 -4
- package/dist/components/form/file-upload-field/index.d.ts +11 -2
- package/dist/components/form/file-upload-field/index.d.ts.map +1 -1
- package/dist/components/form/file-upload-field/index.js +8 -3
- package/dist/components/form/input-field/index.d.ts +41 -2
- package/dist/components/form/input-field/index.d.ts.map +1 -1
- package/dist/components/form/input-field/index.js +73 -15
- package/dist/components/form/password-field/index.d.ts +5 -1
- package/dist/components/form/password-field/index.d.ts.map +1 -1
- package/dist/components/form/password-field/index.js +2 -2
- package/dist/components/form/select-field/index.d.ts +9 -1
- package/dist/components/form/select-field/index.d.ts.map +1 -1
- package/dist/components/form/select-field/index.js +2 -2
- package/dist/components/page-header/index.js +1 -1
- package/dist/components/table/id-table-cell.d.ts +5 -1
- package/dist/components/table/id-table-cell.d.ts.map +1 -1
- package/dist/components/table/id-table-cell.js +48 -6
- package/dist/components/ui/autocomplete/index.d.ts +24 -0
- package/dist/components/ui/autocomplete/index.d.ts.map +1 -1
- package/dist/components/ui/autocomplete/index.js +24 -0
- package/dist/components/ui/badge/index.d.ts +11 -7
- package/dist/components/ui/badge/index.d.ts.map +1 -1
- package/dist/components/ui/badge/index.js +20 -8
- package/dist/components/ui/button/styles.css +31 -1
- package/dist/components/ui/calendar/index.d.ts.map +1 -1
- package/dist/components/ui/calendar/index.js +7 -1
- package/dist/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/components/ui/copy-field/index.js +1 -1
- package/dist/components/ui/file-upload/index.d.ts +235 -9
- package/dist/components/ui/file-upload/index.d.ts.map +1 -1
- package/dist/components/ui/file-upload/index.js +354 -17
- package/dist/components/ui/form.d.ts +4 -0
- package/dist/components/ui/form.d.ts.map +1 -1
- package/dist/components/ui/form.js +48 -6
- package/dist/components/ui/input/styles.css +75 -3
- package/dist/components/ui/sheet/index.d.ts.map +1 -1
- package/dist/components/ui/sheet/index.js +2 -2
- package/dist/components/ui/sidebar/index.d.ts +1 -0
- package/dist/components/ui/sidebar/index.d.ts.map +1 -1
- package/dist/components/ui/sidebar/index.js +1 -0
- package/dist/components/ui/sidebar/sidebar-expand-button.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-expand-button.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-expand-button.js +15 -1
- package/dist/components/ui/sidebar/sidebar-item.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-item.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-item.js +28 -4
- package/dist/components/ui/sidebar/sidebar-provider.d.ts +37 -0
- package/dist/components/ui/sidebar/sidebar-provider.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-provider.js +125 -4
- package/dist/components/ui/sidebar/sidebar-root.d.ts +38 -1
- package/dist/components/ui/sidebar/sidebar-root.d.ts.map +1 -1
- package/dist/components/ui/sidebar/sidebar-root.js +100 -5
- package/dist/components/ui/sidebar/sidebar-trigger.d.ts +18 -0
- package/dist/components/ui/sidebar/sidebar-trigger.d.ts.map +1 -0
- package/dist/components/ui/sidebar/sidebar-trigger.js +30 -0
- package/dist/components/ui/toggle-group/index.d.ts +1 -1
- package/dist/components/ui/toggle-group/index.d.ts.map +1 -1
- package/dist/components/ui/toggle-group/index.js +73 -4
- package/dist/enterprise/number-input/index.d.ts.map +1 -1
- package/dist/enterprise/number-input/index.js +1 -1
- package/dist/esm/components/breadcrumb/index.d.ts.map +1 -1
- package/dist/esm/components/breadcrumb/index.js +25 -1
- package/dist/esm/components/card/account-card/index.js +5 -5
- package/dist/esm/components/card/basic-card/index.d.ts.map +1 -1
- package/dist/esm/components/card/basic-card/index.js +2 -2
- package/dist/esm/components/card/entity-card/index.js +1 -1
- package/dist/esm/components/dialog/confirmation-dialog/index.d.ts.map +1 -1
- package/dist/esm/components/dialog/confirmation-dialog/index.js +4 -1
- package/dist/esm/components/entity-box/index.d.ts +18 -2
- package/dist/esm/components/entity-box/index.d.ts.map +1 -1
- package/dist/esm/components/entity-box/index.js +29 -4
- package/dist/esm/components/form/file-upload-field/index.d.ts +11 -2
- package/dist/esm/components/form/file-upload-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/file-upload-field/index.js +8 -3
- package/dist/esm/components/form/input-field/index.d.ts +41 -2
- package/dist/esm/components/form/input-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/input-field/index.js +73 -15
- package/dist/esm/components/form/password-field/index.d.ts +5 -1
- package/dist/esm/components/form/password-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/password-field/index.js +2 -2
- package/dist/esm/components/form/select-field/index.d.ts +9 -1
- package/dist/esm/components/form/select-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/select-field/index.js +2 -2
- package/dist/esm/components/page-header/index.js +1 -1
- package/dist/esm/components/table/id-table-cell.d.ts +5 -1
- package/dist/esm/components/table/id-table-cell.d.ts.map +1 -1
- package/dist/esm/components/table/id-table-cell.js +48 -3
- package/dist/esm/components/ui/autocomplete/index.d.ts +24 -0
- package/dist/esm/components/ui/autocomplete/index.d.ts.map +1 -1
- package/dist/esm/components/ui/autocomplete/index.js +24 -0
- package/dist/esm/components/ui/badge/index.d.ts +11 -7
- package/dist/esm/components/ui/badge/index.d.ts.map +1 -1
- package/dist/esm/components/ui/badge/index.js +20 -8
- package/dist/esm/components/ui/calendar/index.d.ts.map +1 -1
- package/dist/esm/components/ui/calendar/index.js +8 -2
- package/dist/esm/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/esm/components/ui/copy-field/index.js +1 -1
- package/dist/esm/components/ui/file-upload/index.d.ts +235 -9
- package/dist/esm/components/ui/file-upload/index.d.ts.map +1 -1
- package/dist/esm/components/ui/file-upload/index.js +353 -17
- package/dist/esm/components/ui/form.d.ts +4 -0
- package/dist/esm/components/ui/form.d.ts.map +1 -1
- package/dist/esm/components/ui/form.js +48 -6
- package/dist/esm/components/ui/sheet/index.d.ts.map +1 -1
- package/dist/esm/components/ui/sheet/index.js +2 -2
- package/dist/esm/components/ui/sidebar/index.d.ts +1 -0
- package/dist/esm/components/ui/sidebar/index.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/index.js +1 -0
- package/dist/esm/components/ui/sidebar/sidebar-expand-button.d.ts +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-expand-button.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-expand-button.js +15 -1
- package/dist/esm/components/ui/sidebar/sidebar-item.d.ts +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-item.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-item.js +28 -4
- package/dist/esm/components/ui/sidebar/sidebar-provider.d.ts +37 -0
- package/dist/esm/components/ui/sidebar/sidebar-provider.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-provider.js +122 -1
- package/dist/esm/components/ui/sidebar/sidebar-root.d.ts +38 -1
- package/dist/esm/components/ui/sidebar/sidebar-root.d.ts.map +1 -1
- package/dist/esm/components/ui/sidebar/sidebar-root.js +102 -7
- package/dist/esm/components/ui/sidebar/sidebar-trigger.d.ts +18 -0
- package/dist/esm/components/ui/sidebar/sidebar-trigger.d.ts.map +1 -0
- package/dist/esm/components/ui/sidebar/sidebar-trigger.js +26 -0
- package/dist/esm/components/ui/toggle-group/index.d.ts +1 -1
- package/dist/esm/components/ui/toggle-group/index.d.ts.map +1 -1
- package/dist/esm/components/ui/toggle-group/index.js +73 -4
- package/dist/esm/enterprise/number-input/index.d.ts.map +1 -1
- package/dist/esm/enterprise/number-input/index.js +1 -1
- package/dist/globals.css +77 -31
- package/package.json +5 -3
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
@layer components {
|
|
15
15
|
.input-wrapper {
|
|
16
|
-
@apply h-input-wrapper-h border-input-border p-input-wrapper-p relative flex w-full cursor-text items-center rounded-md border transition-[color,box-shadow];
|
|
16
|
+
@apply h-input-wrapper-h border-input-border p-input-wrapper-p relative flex w-full min-w-0 cursor-text items-center rounded-md border transition-[color,box-shadow];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.input-wrapper-focus {
|
|
@@ -27,9 +27,81 @@
|
|
|
27
27
|
/* Don't give the placeholder its own size or weight. At a smaller size it
|
|
28
28
|
still shares the input's baseline, so it rode ~0.75px below the value and
|
|
29
29
|
the text jumped the moment you typed; font-medium gave the hint the same
|
|
30
|
-
presence as the label naming the field.
|
|
30
|
+
presence as the label naming the field.
|
|
31
|
+
|
|
32
|
+
THE CONTRACT, and all three halves of it are measured. A field keeps its
|
|
33
|
+
natural width: nothing here declares one, so the control contributes its
|
|
34
|
+
own intrinsic size and a box sized BY ITS CONTENT — a search box in a
|
|
35
|
+
`flex gap-4` filter bar — comes out as a field. A field fills its box
|
|
36
|
+
instead of overrunning it: `min-w-0` on both boxes drops the automatic
|
|
37
|
+
minimum that used to hold the control at 239px inside a narrower wrapper,
|
|
38
|
+
down to the one width it cannot go under. That floor is arithmetic rather
|
|
39
|
+
than policy — `px-input-px` is 16px a side and the box sizing is
|
|
40
|
+
border-box, so the control is 32px wide carrying nothing at all — and a
|
|
41
|
+
box narrower than that gets the difference painted outside it. Measured at
|
|
42
|
+
23px on the console's template-builder header, whose `w-48` wrapper is
|
|
43
|
+
squeezed to 18px and which the console repairs with `shrink-0` on that
|
|
44
|
+
wrapper. This package's own `SheetContent side="right"` runs the same
|
|
45
|
+
arithmetic one step earlier: at a 390px viewport `w-2/5 p-12` leaves a
|
|
46
|
+
59px panel, and the field comes out 41px wide with 9px of text — unusable,
|
|
47
|
+
but inside its box, where 2.0.0-beta.9 painted 189px of it across the
|
|
48
|
+
panel edge. A row that must compress says so on its own flex item, with
|
|
49
|
+
`min-w-0`, and that one token now works.
|
|
50
|
+
|
|
51
|
+
Why no declared width, measured rather than assumed. A declared width
|
|
52
|
+
replaces the control's max-content contribution as well as its min-content
|
|
53
|
+
one, because an `<input>` does not wrap and has only the one intrinsic
|
|
54
|
+
size. `w-0` shipped in 2.0.0-beta.10 and bought a narrow row's compression
|
|
55
|
+
by giving that up: five product-console screens rendered an empty 32px
|
|
56
|
+
chip at every viewport, and a sixth — the template builder's name field —
|
|
57
|
+
did at 390px while keeping 142px of text at 1440px, all of them wrappers
|
|
58
|
+
that had been handed 0px to be as wide as.
|
|
59
|
+
`w-full` buys the same compression without the chip — a percentage
|
|
60
|
+
contributes max-content but zero min-content — and it was the runner-up
|
|
61
|
+
here. It loses on the same filter bar at 390px, where the control is not
|
|
62
|
+
merely sized by its content but squeezed, and it disappears rather than
|
|
63
|
+
letting the bar scroll. Seventeen rule sets were measured against
|
|
64
|
+
seventeen fixtures; none closes all of them.
|
|
65
|
+
|
|
66
|
+
What this one leaves open is `two-up-200`: two fields in a caller's
|
|
67
|
+
`flex-1` columns inside a 200px row, which now overflows by 138px instead
|
|
68
|
+
of compressing. The caller's one token closes it — `min-w-0` on its own
|
|
69
|
+
column, measured at 78px and fitting — and the fixture beside it,
|
|
70
|
+
`two-up-200-caller-min-w-0`, is the guard that keeps that hatch working.
|
|
71
|
+
On 2.0.0-beta.9 the hatch did NOT work: the same row overflowed by 48px
|
|
72
|
+
and the control painted 152px across its neighbour, because the control
|
|
73
|
+
floored at its intrinsic size inside the column that had just agreed to
|
|
74
|
+
compress. That is the difference this rule set makes.
|
|
75
|
+
|
|
76
|
+
`min-width` reaches exactly one box: the one it is declared on. It does
|
|
77
|
+
not lower an ancestor's automatic minimum and it does not lower a
|
|
78
|
+
descendant's — the sentence this comment carried before the fix said so
|
|
79
|
+
and was right. Nothing above contradicts it: the rule works because BOTH
|
|
80
|
+
boxes declare their own, the control's on `.input-base` and the wrapper's
|
|
81
|
+
on `.input-wrapper`, and neither half works alone. Measured: the wrapper's
|
|
82
|
+
alone fails eleven fixtures (`--candidate=wrapper-only-auto`), the
|
|
83
|
+
control's alone fails four (`--candidate=auto-min-w-0`). It is also why
|
|
84
|
+
`two-up-200` stays open above. The caller's `flex-1` column is a third
|
|
85
|
+
box, no declaration in this package reaches it, and only the caller's own
|
|
86
|
+
`min-w-0` lowers it — which is why that row still overflows by 138px.
|
|
87
|
+
|
|
88
|
+
There is deliberately NO positive min-width floor, and that was measured
|
|
89
|
+
too. A floor here cannot reach the sites that want one: a consumer
|
|
90
|
+
writing `.input-base` by hand tends to add its own `min-w-0`, which is a
|
|
91
|
+
`utilities`-layer class and outranks anything this `components`-layer rule
|
|
92
|
+
set declares. With `min-w-16` added here, the same fixture read 64px wide
|
|
93
|
+
without that hand-written `min-w-0` and 32px with it. The floor also cost
|
|
94
|
+
ground elsewhere: two inputs sharing a 120px row went from fitting to
|
|
95
|
+
overflowing by 52px, and a floor small enough to survive that row still
|
|
96
|
+
left a squeezed filter bar at 32px of content. A caller whose field must
|
|
97
|
+
hold its width says so on its own wrapper, with `shrink-0`.
|
|
98
|
+
|
|
99
|
+
`scripts/measure-input-shrink.mjs` carries the fixtures and the numbers;
|
|
100
|
+
run it when touching this rule set, and `--candidate=ID` re-measures any
|
|
101
|
+
of the seventeen, or `shipped` for this one, before re-opening the
|
|
102
|
+
trade. */
|
|
31
103
|
.input-base {
|
|
32
|
-
@apply px-input-px text-input-foreground placeholder:text-input-placeholder h-full min-h-0 flex-1 border-none bg-transparent outline-none focus:ring-0 focus:ring-offset-0;
|
|
104
|
+
@apply px-input-px text-input-foreground placeholder:text-input-placeholder h-full min-h-0 min-w-0 flex-1 border-none bg-transparent outline-none focus:ring-0 focus:ring-offset-0;
|
|
33
105
|
}
|
|
34
106
|
|
|
35
107
|
.input-disabled {
|
|
@@ -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,
|
|
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"}
|
|
@@ -87,7 +87,7 @@ function SheetContent({ side = 'right', className, children, onInteractOutside,
|
|
|
87
87
|
}
|
|
88
88
|
onInteractOutside?.(event);
|
|
89
89
|
}, [onInteractOutside]);
|
|
90
|
-
return ((0, jsx_runtime_1.jsxs)(SheetPortal, { children: [(0, jsx_runtime_1.jsx)(SheetOverlay, {}), (0, jsx_runtime_1.jsxs)(SheetPrimitive.Content, { "data-slot": "sheet-content", className: (0, utils_1.cn)('flex max-h-screen flex-col overflow-x-auto px-8 pb-0', sheetVariants({ side }), className), onInteractOutside: handleInteractOutside, ...props, children: [children, (0, jsx_runtime_1.jsxs)(SheetPrimitive.Close, { className: "data-[state=open]:bg-secondary absolute top-7 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-6 w-6" }), (0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: "Close" })] })] })] }));
|
|
90
|
+
return ((0, jsx_runtime_1.jsxs)(SheetPortal, { children: [(0, jsx_runtime_1.jsx)(SheetOverlay, {}), (0, jsx_runtime_1.jsxs)(SheetPrimitive.Content, { "data-slot": "sheet-content", className: (0, utils_1.cn)('flex max-h-screen flex-col overflow-x-auto px-8 pb-0', sheetVariants({ side }), className), onInteractOutside: handleInteractOutside, ...props, children: [children, (0, jsx_runtime_1.jsxs)(SheetPrimitive.Close, { className: "data-[state=open]:bg-secondary ring-offset-background focus-visible:ring-ring absolute top-7 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-6 w-6" }), (0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: "Close" })] })] })] }));
|
|
91
91
|
}
|
|
92
92
|
function SheetHeader({ className, ...props }) {
|
|
93
93
|
return ((0, jsx_runtime_1.jsx)("div", { "data-slot": "sheet-header", className: (0, utils_1.cn)('mb-4 flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
|
|
@@ -99,5 +99,5 @@ function SheetTitle({ className, ...props }) {
|
|
|
99
99
|
return ((0, jsx_runtime_1.jsx)(SheetPrimitive.Title, { "data-slot": "sheet-title", className: (0, utils_1.cn)('text-muted-foreground mb-2 flex text-xl font-bold', className), ...props }));
|
|
100
100
|
}
|
|
101
101
|
function SheetDescription({ className, ...props }) {
|
|
102
|
-
return ((0, jsx_runtime_1.jsx)(SheetPrimitive.Description, { "data-slot": "sheet-description", className: (0, utils_1.cn)('text-
|
|
102
|
+
return ((0, jsx_runtime_1.jsx)(SheetPrimitive.Description, { "data-slot": "sheet-description", className: (0, utils_1.cn)('text-muted-foreground text-sm font-medium', className), ...props }));
|
|
103
103
|
}
|
|
@@ -4,6 +4,7 @@ export * from './sidebar-item-icon-button';
|
|
|
4
4
|
export * from './sidebar-item-button';
|
|
5
5
|
export * from './sidebar-item';
|
|
6
6
|
export * from './sidebar-expand-button';
|
|
7
|
+
export * from './sidebar-trigger';
|
|
7
8
|
export * from './sidebar-back-button';
|
|
8
9
|
export * from './sidebar-provider';
|
|
9
10
|
export * from './sidebar-item-collapsible';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/index.tsx"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/index.tsx"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kBAAkB,CAAA"}
|
|
@@ -20,6 +20,7 @@ __exportStar(require("./sidebar-item-icon-button"), exports);
|
|
|
20
20
|
__exportStar(require("./sidebar-item-button"), exports);
|
|
21
21
|
__exportStar(require("./sidebar-item"), exports);
|
|
22
22
|
__exportStar(require("./sidebar-expand-button"), exports);
|
|
23
|
+
__exportStar(require("./sidebar-trigger"), exports);
|
|
23
24
|
__exportStar(require("./sidebar-back-button"), exports);
|
|
24
25
|
__exportStar(require("./sidebar-provider"), exports);
|
|
25
26
|
__exportStar(require("./sidebar-item-collapsible"), exports);
|
|
@@ -7,6 +7,6 @@ type SidebarExpandButtonProps = {
|
|
|
7
7
|
*/
|
|
8
8
|
tooltip?: string;
|
|
9
9
|
};
|
|
10
|
-
export declare const SidebarExpandButton: ({ tooltip }: SidebarExpandButtonProps) => React.JSX.Element;
|
|
10
|
+
export declare const SidebarExpandButton: ({ tooltip }: SidebarExpandButtonProps) => React.JSX.Element | null;
|
|
11
11
|
export {};
|
|
12
12
|
//# sourceMappingURL=sidebar-expand-button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-expand-button.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-expand-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,KAAK,wBAAwB,GAAG;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,aAAa,wBAAwB,
|
|
1
|
+
{"version":3,"file":"sidebar-expand-button.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-expand-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,KAAK,wBAAwB,GAAG;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,aAAa,wBAAwB,6BAwExE,CAAA"}
|
|
@@ -13,7 +13,21 @@ const sidebar_components_1 = require("./sidebar-components");
|
|
|
13
13
|
const tooltip_1 = require("../../ui/tooltip");
|
|
14
14
|
const icon_button_1 = require("../icon-button");
|
|
15
15
|
const SidebarExpandButton = ({ tooltip }) => {
|
|
16
|
-
const { isCollapsed, toggleSidebar } = (0, sidebar_provider_1.useSidebar)();
|
|
16
|
+
const { isCollapsed, isDrawer, toggleSidebar } = (0, sidebar_provider_1.useSidebar)();
|
|
17
|
+
/**
|
|
18
|
+
* Nothing to collapse in a drawer. The expanded branch below hangs its button
|
|
19
|
+
* at `right-[-20px]`, twenty pixels outside the rail — off the canvas of an
|
|
20
|
+
* overlay that has no page beside it — and collapsing to a 72px strip inside
|
|
21
|
+
* a 244px sheet is not a state worth reaching. The drawer's own close control
|
|
22
|
+
* and the backdrop are how it is dismissed.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ `isDrawer`, NOT `isMobile`. A consumer left on the default
|
|
25
|
+
* `mobile="inline"` still has a real rail below 768px, and its collapse
|
|
26
|
+
* control has to come with it.
|
|
27
|
+
*/
|
|
28
|
+
if (isDrawer) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
17
31
|
return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [!isCollapsed && ((0, jsx_runtime_1.jsx)("div", { "data-slot": "sidebar-expand-button", className: "border-border bg-card flex w-full", children: (0, jsx_runtime_1.jsx)("div", { className: "absolute right-[-20px] bottom-4", children: (0, jsx_runtime_1.jsx)(icon_button_1.IconButton, { variant: "secondary", rounded: true, onClick: toggleSidebar, "aria-label": tooltip || 'Collapse sidebar', "aria-expanded": true, children: (0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeftClose, { className: "text-muted-foreground" }) }) }) })), isCollapsed && ((0, jsx_runtime_1.jsx)(sidebar_components_1.SidebarFooter, { children: (0, jsx_runtime_1.jsx)(tooltip_1.TooltipProvider, { children: (0, jsx_runtime_1.jsxs)(tooltip_1.Tooltip, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.TooltipTrigger
|
|
18
32
|
// `hover:text-accent-foreground` on the TRIGGER, not only on
|
|
19
33
|
// the glyph below. The base ink here used to be base/400, which
|
|
@@ -9,5 +9,5 @@ export type SidebarItemProps = React.ComponentProps<typeof SidebarItemButton> &
|
|
|
9
9
|
disabled?: boolean;
|
|
10
10
|
children?: React.ReactNode;
|
|
11
11
|
};
|
|
12
|
-
export declare const SidebarItem: ({ active, href, children: _children, ...props }: SidebarItemProps) => React.JSX.Element;
|
|
12
|
+
export declare const SidebarItem: ({ active, href, children: _children, onClick, ...props }: SidebarItemProps) => React.JSX.Element;
|
|
13
13
|
//# sourceMappingURL=sidebar-item.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-item.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-item.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAIlE,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,iBAAiB,CAAC,GAC3E,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,GAAG;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,CAAA;AAEH,eAAO,MAAM,WAAW,GAAI,
|
|
1
|
+
{"version":3,"file":"sidebar-item.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-item.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAIlE,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,iBAAiB,CAAC,GAC3E,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,GAAG;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,CAAA;AAEH,eAAO,MAAM,WAAW,GAAI,0DAMzB,gBAAgB,sBAsDlB,CAAA"}
|
|
@@ -7,14 +7,38 @@ const sidebar_item_button_1 = require("./sidebar-item-button");
|
|
|
7
7
|
const sidebar_item_icon_button_1 = require("./sidebar-item-icon-button");
|
|
8
8
|
const sidebar_provider_1 = require("./sidebar-provider");
|
|
9
9
|
const sidebar_router_1 = require("./sidebar-router");
|
|
10
|
-
const SidebarItem = ({ active, href, children: _children, ...props }) => {
|
|
10
|
+
const SidebarItem = ({ active, href, children: _children, onClick, ...props }) => {
|
|
11
11
|
const { usePathname } = (0, sidebar_router_1.useSidebarRouter)();
|
|
12
12
|
const pathname = usePathname();
|
|
13
|
-
const { isCollapsed } = (0, sidebar_provider_1.useSidebar)();
|
|
13
|
+
const { isCollapsed, isDrawer, setOpenMobile } = (0, sidebar_provider_1.useSidebar)();
|
|
14
14
|
const isActive = (href) => pathname === href;
|
|
15
|
+
/**
|
|
16
|
+
* ⛔ ACTIVATING A DESTINATION CLOSES THE DRAWER. It is the whole point of the
|
|
17
|
+
* drawer and it was the one interaction that left it open: Escape, the
|
|
18
|
+
* backdrop and the X all dismissed it, a navigation link did not.
|
|
19
|
+
*
|
|
20
|
+
* The overlay is modal, so `document.body` carries `pointer-events: none`
|
|
21
|
+
* while it is up, and a consumer's `SidebarItem` is a client-side `Link`
|
|
22
|
+
* under a provider that survives the route change — so the reader landed on
|
|
23
|
+
* the new page rendered INERT behind a drawer they had to dismiss a second
|
|
24
|
+
* time, on a phone.
|
|
25
|
+
*
|
|
26
|
+
* Here rather than in `SidebarRoot`, because this is the component that knows
|
|
27
|
+
* an activation happened; `SidebarBackButton` and every
|
|
28
|
+
* `SidebarItemCollapsibleContent` leaf render through it, so they are covered
|
|
29
|
+
* by construction. Guarded on `isDrawer` so an inline rail — where nothing is
|
|
30
|
+
* covering the page — behaves exactly as before, and the consumer's own
|
|
31
|
+
* handler still runs either way.
|
|
32
|
+
*/
|
|
33
|
+
const handleClick = (event) => {
|
|
34
|
+
onClick?.(event);
|
|
35
|
+
if (isDrawer) {
|
|
36
|
+
setOpenMobile(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
15
39
|
if (isCollapsed) {
|
|
16
|
-
return ((0, jsx_runtime_1.jsx)(sidebar_item_icon_button_1.SidebarItemIconButton, { "data-slot": "sidebar-item", href: href, active: isActive(href) || active, ...props }));
|
|
40
|
+
return ((0, jsx_runtime_1.jsx)(sidebar_item_icon_button_1.SidebarItemIconButton, { "data-slot": "sidebar-item", href: href, active: isActive(href) || active, onClick: handleClick, ...props }));
|
|
17
41
|
}
|
|
18
|
-
return ((0, jsx_runtime_1.jsx)(sidebar_item_button_1.SidebarItemButton, { "data-slot": "sidebar-item", href: href, active: isActive(href) || active, ...props }));
|
|
42
|
+
return ((0, jsx_runtime_1.jsx)(sidebar_item_button_1.SidebarItemButton, { "data-slot": "sidebar-item", href: href, active: isActive(href) || active, onClick: handleClick, ...props }));
|
|
19
43
|
};
|
|
20
44
|
exports.SidebarItem = SidebarItem;
|
|
@@ -1,12 +1,49 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
4
|
+
*
|
|
5
|
+
* 767px rather than 768px: this is the upper bound of `max-width`, so it is the
|
|
6
|
+
* complement of Tailwind's `md` breakpoint (`min-width: 768px`) and `md:hidden`
|
|
7
|
+
* on `SidebarTrigger` flips at exactly the same pixel the query does.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SIDEBAR_MOBILE_QUERY = "(max-width: 767px)";
|
|
2
10
|
export type SidebarContextProps = {
|
|
3
11
|
isCollapsed: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True only inside a drawer that `SidebarRoot` is actually rendering, which
|
|
14
|
+
* needs `mobile="drawer"` as well as a narrow viewport. `SidebarRoot`
|
|
15
|
+
* re-provides the context for that subtree; nothing else sets it.
|
|
16
|
+
*/
|
|
17
|
+
isDrawer: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True while the viewport is narrow enough for a drawer. Reported in BOTH
|
|
20
|
+
* modes, so a consumer still on `mobile="inline"` can build its own
|
|
21
|
+
* responsive behaviour from it.
|
|
22
|
+
*/
|
|
23
|
+
isMobile: boolean;
|
|
24
|
+
/** Whether the mobile drawer is showing. Meaningless while `isMobile` is false. */
|
|
25
|
+
openMobile: boolean;
|
|
26
|
+
setOpenMobile: (open: boolean) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Puts focus back on whatever opened the drawer. `SidebarRoot` calls it when
|
|
29
|
+
* the drawer closes; nothing else should need it.
|
|
30
|
+
*/
|
|
31
|
+
restoreDrawerFocus: () => void;
|
|
32
|
+
/** The drawer's DOM id, so `SidebarTrigger` can point `aria-controls` at it. */
|
|
33
|
+
sidebarId: string;
|
|
4
34
|
items: Record<string, boolean>;
|
|
5
35
|
setItems: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
|
6
36
|
getItemCollapsed: (key: string) => boolean;
|
|
7
37
|
setItemCollapsed: (key: string, value: boolean) => void;
|
|
8
38
|
toggleSidebar: () => void;
|
|
9
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Exported so `SidebarRoot` can re-provide it for the drawer's subtree. That
|
|
42
|
+
* subtree is the only place `isCollapsed` and `isDrawer` differ from the
|
|
43
|
+
* provider's own values, and scoping the override to it is what keeps a
|
|
44
|
+
* consumer on `mobile="inline"` seeing exactly today's rail.
|
|
45
|
+
*/
|
|
46
|
+
export declare const SidebarContext: React.Context<SidebarContextProps | undefined>;
|
|
10
47
|
export declare const useSidebar: () => SidebarContextProps;
|
|
11
48
|
export declare const SidebarProvider: ({ children }: React.PropsWithChildren) => React.JSX.Element;
|
|
12
49
|
//# sourceMappingURL=sidebar-provider.d.ts.map
|
|
@@ -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,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,OAAO,CAAA;IACpB,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;
|
|
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;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAUxD,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"}
|
|
@@ -4,13 +4,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.SidebarProvider = exports.useSidebar = void 0;
|
|
7
|
+
exports.SidebarProvider = exports.useSidebar = exports.SidebarContext = exports.SIDEBAR_MOBILE_QUERY = void 0;
|
|
8
8
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
9
|
const react_1 = __importDefault(require("react"));
|
|
10
10
|
const storage_1 = require("../../../lib/storage");
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The viewport below which the rail stops being a layout and becomes a drawer.
|
|
13
|
+
*
|
|
14
|
+
* 767px rather than 768px: this is the upper bound of `max-width`, so it is the
|
|
15
|
+
* complement of Tailwind's `md` breakpoint (`min-width: 768px`) and `md:hidden`
|
|
16
|
+
* on `SidebarTrigger` flips at exactly the same pixel the query does.
|
|
17
|
+
*/
|
|
18
|
+
exports.SIDEBAR_MOBILE_QUERY = '(max-width: 767px)';
|
|
19
|
+
/**
|
|
20
|
+
* Where focus goes when the rail has to receive it. The rail's own content is
|
|
21
|
+
* links and buttons, so this is deliberately the short list rather than the
|
|
22
|
+
* full tabbable grammar: a `[tabindex]` sweep would have to exclude `-1`, and
|
|
23
|
+
* nothing in a sidebar carries one.
|
|
24
|
+
*/
|
|
25
|
+
const FOCUSABLE = 'a[href], button:not([disabled])';
|
|
26
|
+
/**
|
|
27
|
+
* Exported so `SidebarRoot` can re-provide it for the drawer's subtree. That
|
|
28
|
+
* subtree is the only place `isCollapsed` and `isDrawer` differ from the
|
|
29
|
+
* provider's own values, and scoping the override to it is what keeps a
|
|
30
|
+
* consumer on `mobile="inline"` seeing exactly today's rail.
|
|
31
|
+
*/
|
|
32
|
+
exports.SidebarContext = react_1.default.createContext(undefined);
|
|
12
33
|
const useSidebar = () => {
|
|
13
|
-
const context = react_1.default.useContext(SidebarContext);
|
|
34
|
+
const context = react_1.default.useContext(exports.SidebarContext);
|
|
14
35
|
if (!context) {
|
|
15
36
|
throw new Error('useSidebar must be used within a SidebarProvider');
|
|
16
37
|
}
|
|
@@ -21,6 +42,61 @@ const SidebarProvider = ({ children }) => {
|
|
|
21
42
|
const [hydrated, setHydrated] = react_1.default.useState(false);
|
|
22
43
|
const [collapsed, setCollapsed] = react_1.default.useState(false);
|
|
23
44
|
const [items, _setItems] = react_1.default.useState({});
|
|
45
|
+
const [isMobile, setIsMobile] = react_1.default.useState(false);
|
|
46
|
+
const [openMobile, _setOpenMobile] = react_1.default.useState(false);
|
|
47
|
+
const sidebarId = react_1.default.useId();
|
|
48
|
+
/**
|
|
49
|
+
* ⛔ RADIX CANNOT RESTORE THIS FOCUS BY ITSELF. `DialogContent` preventDefaults
|
|
50
|
+
* the focus scope's own restore and focuses `triggerRef.current` instead —
|
|
51
|
+
* the ref a `SheetTrigger` would have filled. The drawer is opened from this
|
|
52
|
+
* state rather than from a trigger inside the dialog, so that ref is null and
|
|
53
|
+
* closing dropped focus onto `<body>`: a keyboard reader who opened the
|
|
54
|
+
* navigation and pressed Escape was returned to the top of the document.
|
|
55
|
+
*
|
|
56
|
+
* Captured here rather than in `SidebarTrigger` so that a consumer opening
|
|
57
|
+
* the drawer from its own control gets the same behaviour.
|
|
58
|
+
*/
|
|
59
|
+
const opener = react_1.default.useRef(null);
|
|
60
|
+
const setOpenMobile = react_1.default.useCallback((open) => {
|
|
61
|
+
if (open && typeof document !== 'undefined') {
|
|
62
|
+
opener.current = document.activeElement;
|
|
63
|
+
}
|
|
64
|
+
_setOpenMobile(open);
|
|
65
|
+
}, []);
|
|
66
|
+
/**
|
|
67
|
+
* ⛔ THE VIEWPORT AT THE MOMENT FOCUS MOVES, NOT AT THE MOMENT IT OPENED.
|
|
68
|
+
*
|
|
69
|
+
* Radix calls `restoreDrawerFocus` from the focus scope's UNMOUNT handler,
|
|
70
|
+
* which is the last thing in the commit to touch focus — later than any
|
|
71
|
+
* effect in this provider, so the decision has to be made inside the handler
|
|
72
|
+
* rather than beside it. By then `isMobile` has already flipped.
|
|
73
|
+
*/
|
|
74
|
+
const isMobileRef = react_1.default.useRef(false);
|
|
75
|
+
isMobileRef.current = isMobile;
|
|
76
|
+
const restoreDrawerFocus = react_1.default.useCallback(() => {
|
|
77
|
+
// `isConnected`: the control that opened the drawer may itself have been
|
|
78
|
+
// unmounted by whatever the reader did inside it.
|
|
79
|
+
//
|
|
80
|
+
// ⛔ AND NOT AT ALL WHEN THE VIEWPORT IS THE ONE THAT DISMISSED IT. A
|
|
81
|
+
// tablet rotated from portrait to landscape with the navigation open
|
|
82
|
+
// crosses 768px, so the drawer is replaced by the rail and the opener is
|
|
83
|
+
// `SidebarTrigger` — which the same breakpoint just hid with `md:hidden`.
|
|
84
|
+
// `.focus()` on a `display: none` element is a silent no-op, and Radix's
|
|
85
|
+
// own restore is already `preventDefault()`ed, so focus fell to `<body>`
|
|
86
|
+
// and the reader lost their place (SC 2.4.3). Measured in Chromium at
|
|
87
|
+
// 390 → 1280: `{"rails":1,"triggerDisplay":"none","focus":"BODY:"}`.
|
|
88
|
+
if (isMobileRef.current && opener.current?.isConnected) {
|
|
89
|
+
opener.current.focus();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// The rail is what the drawer became, so that is where the reader goes.
|
|
93
|
+
// Below the breakpoint there is nothing here to find — a closed drawer
|
|
94
|
+
// leaves no navigation in the DOM — and focus stays where Radix left it.
|
|
95
|
+
document
|
|
96
|
+
.getElementById(sidebarId)
|
|
97
|
+
?.querySelector(FOCUSABLE)
|
|
98
|
+
?.focus();
|
|
99
|
+
}, [sidebarId]);
|
|
24
100
|
const toggleSidebar = () => setCollapsed((collapsed) => !collapsed);
|
|
25
101
|
const getItemCollapsed = (key) => {
|
|
26
102
|
return items[key] || false;
|
|
@@ -34,6 +110,37 @@ const SidebarProvider = ({ children }) => {
|
|
|
34
110
|
_setItems((0, storage_1.getStorageObject)('sidebar-items', {}));
|
|
35
111
|
setHydrated(true);
|
|
36
112
|
}, []);
|
|
113
|
+
/**
|
|
114
|
+
* ⚠️ FALSE UNTIL THE CLIENT SAYS OTHERWISE. There is no viewport on the
|
|
115
|
+
* server, so the first paint is always the rail and the drawer appears on
|
|
116
|
+
* hydration; starting from a guess would make the markup differ between the
|
|
117
|
+
* two renders. The `matchMedia` guard is the one `theme-provider.tsx` next
|
|
118
|
+
* door already carries: jsdom and some embedded runtimes ship without it, and
|
|
119
|
+
* a missing media API is a desktop, not a crash.
|
|
120
|
+
*/
|
|
121
|
+
react_1.default.useEffect(() => {
|
|
122
|
+
if (typeof window.matchMedia !== 'function') {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const media = window.matchMedia(exports.SIDEBAR_MOBILE_QUERY);
|
|
126
|
+
const sync = () => setIsMobile(media.matches);
|
|
127
|
+
sync();
|
|
128
|
+
media.addEventListener('change', sync);
|
|
129
|
+
return () => media.removeEventListener('change', sync);
|
|
130
|
+
}, []);
|
|
131
|
+
/**
|
|
132
|
+
* A drawer left open while the viewport grows back would keep an overlay and
|
|
133
|
+
* a focus trap over a layout that is already showing its rail, with nothing
|
|
134
|
+
* on screen to dismiss them.
|
|
135
|
+
*
|
|
136
|
+
* Where focus goes afterwards is `restoreDrawerFocus`'s decision, not this
|
|
137
|
+
* effect's: Radix's unmount handler runs later than anything here.
|
|
138
|
+
*/
|
|
139
|
+
react_1.default.useEffect(() => {
|
|
140
|
+
if (!isMobile) {
|
|
141
|
+
_setOpenMobile(false);
|
|
142
|
+
}
|
|
143
|
+
}, [isMobile]);
|
|
37
144
|
// Only persist to localStorage after hydration to avoid overwriting stored values
|
|
38
145
|
react_1.default.useEffect(() => {
|
|
39
146
|
if (!hydrated) {
|
|
@@ -47,8 +154,22 @@ const SidebarProvider = ({ children }) => {
|
|
|
47
154
|
}
|
|
48
155
|
localStorage.setItem('sidebar-items', JSON.stringify(items));
|
|
49
156
|
}, [items, hydrated]);
|
|
50
|
-
return ((0, jsx_runtime_1.jsx)(SidebarContext.Provider, { value: {
|
|
157
|
+
return ((0, jsx_runtime_1.jsx)(exports.SidebarContext.Provider, { value: {
|
|
158
|
+
/**
|
|
159
|
+
* ⚠️ A NARROW VIEWPORT DOES NOT TOUCH THIS. It used to be forced false
|
|
160
|
+
* whenever `isMobile`, which silently changed the rail for every
|
|
161
|
+
* consumer — including the ones that never asked for a drawer.
|
|
162
|
+
* `SidebarRoot` overrides it for the drawer's own subtree instead, so
|
|
163
|
+
* the stored preference reaches an inline rail unchanged at every
|
|
164
|
+
* viewport.
|
|
165
|
+
*/
|
|
51
166
|
isCollapsed: collapsed,
|
|
167
|
+
isDrawer: false,
|
|
168
|
+
isMobile,
|
|
169
|
+
openMobile,
|
|
170
|
+
setOpenMobile,
|
|
171
|
+
restoreDrawerFocus,
|
|
172
|
+
sidebarId,
|
|
52
173
|
items,
|
|
53
174
|
setItems: _setItems,
|
|
54
175
|
getItemCollapsed,
|
|
@@ -1,3 +1,40 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export
|
|
2
|
+
export type SidebarRootProps = React.ComponentProps<'nav'> & {
|
|
3
|
+
/**
|
|
4
|
+
* What this rail becomes below 768px.
|
|
5
|
+
*
|
|
6
|
+
* - `'inline'` (default): nothing changes. The rail renders at every
|
|
7
|
+
* viewport exactly as it always has, and the reader's collapsed preference
|
|
8
|
+
* is untouched.
|
|
9
|
+
* - `'drawer'`: the inline rail is not rendered below the breakpoint and the
|
|
10
|
+
* navigation moves into an overlay drawer that `SidebarTrigger` opens.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ THE DEFAULT IS NOT A TASTE, IT IS A CONSUMER-SAFETY RULE. `SidebarTrigger`
|
|
13
|
+
* is the only way back into the drawer and it must render inside
|
|
14
|
+
* `SidebarProvider`. A consumer that has not placed one yet — which is every
|
|
15
|
+
* consumer at the moment this shipped — would get a phone with NO navigation
|
|
16
|
+
* rather than a rail that takes 63% of it, and that is strictly worse. Opt in
|
|
17
|
+
* once there is somewhere to put the trigger.
|
|
18
|
+
*/
|
|
19
|
+
mobile?: 'inline' | 'drawer';
|
|
20
|
+
/**
|
|
21
|
+
* The drawer's accessible name. A dialog needs one, and it is read out when
|
|
22
|
+
* the drawer opens. Unused while `mobile` is `'inline'`.
|
|
23
|
+
*/
|
|
24
|
+
mobileTitle?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* ⛔ BELOW 768px A RAIL IS NOT A LAYOUT — once a consumer opts in.
|
|
28
|
+
*
|
|
29
|
+
* The widths above are a fixed share of a flex row, so at 390px the expanded
|
|
30
|
+
* rail took 63% of the screen and left the page 146px, and the only affordance
|
|
31
|
+
* — `SidebarExpandButton` — shrinks it to 72px, which is still 18% and still
|
|
32
|
+
* permanently on screen. There was nothing that removed it.
|
|
33
|
+
*
|
|
34
|
+
* With `mobile="drawer"` the rail is replaced below the breakpoint by an
|
|
35
|
+
* overlay drawer. Escape, the backdrop click, the focus trap and returning
|
|
36
|
+
* focus to whatever opened it are Radix Dialog's, through the kit's own
|
|
37
|
+
* `Sheet`; none of that is re-implemented here.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SidebarRoot: ({ className, id, mobile, mobileTitle, ...props }: SidebarRootProps) => React.JSX.Element;
|
|
3
40
|
//# sourceMappingURL=sidebar-root.d.ts.map
|
|
@@ -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;
|
|
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,sBA0GlB,CAAA"}
|
|
@@ -6,19 +6,114 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
6
6
|
const class_variance_authority_1 = require("class-variance-authority");
|
|
7
7
|
const sidebar_provider_1 = require("./sidebar-provider");
|
|
8
8
|
const utils_1 = require("../../../lib/utils");
|
|
9
|
+
const sheet_1 = require("../sheet");
|
|
10
|
+
/**
|
|
11
|
+
* ⛔ THE WIDTHS ARE TOKENS, NOT LITERALS.
|
|
12
|
+
*
|
|
13
|
+
* They were `w-[72px]` and `w-[244px]`, which a consumer could only override by
|
|
14
|
+
* winning a specificity argument with `cn`. `--sidebar-width` and
|
|
15
|
+
* `--sidebar-width-collapsed` are declared in `globals.css`, so the default
|
|
16
|
+
* still ships with the stylesheet, and a consumer re-points either one with
|
|
17
|
+
* `className="[--sidebar-width:280px]"` ON THE RAIL, which moves the rail and
|
|
18
|
+
* the mobile drawer together (see `liftWidthOverrides` below), or with a rule
|
|
19
|
+
* on `:root`/`html`.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ A DECLARATION ON A REACT-TREE ANCESTOR REACHES THE RAIL AND NOT THE
|
|
22
|
+
* DRAWER. Custom properties inherit down the DOM tree, and the drawer is
|
|
23
|
+
* portalled to `document.body`, so a layout wrapper is not an ancestor of it.
|
|
24
|
+
* `:root` is, which is why it is named above and a wrapper is not.
|
|
25
|
+
*/
|
|
9
26
|
const sidebarVariants = (0, class_variance_authority_1.cva)('group/sidebar shadow-sidebar relative flex flex-col transition-[width] duration-300 ease-in-out', {
|
|
10
27
|
variants: {
|
|
11
28
|
collapsed: {
|
|
12
|
-
true: 'w-[
|
|
13
|
-
false: 'w-[
|
|
29
|
+
true: 'w-[var(--sidebar-width-collapsed)]',
|
|
30
|
+
false: 'w-[var(--sidebar-width)]'
|
|
14
31
|
}
|
|
15
32
|
},
|
|
16
33
|
defaultVariants: {
|
|
17
34
|
collapsed: false
|
|
18
35
|
}
|
|
19
36
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Tailwind arbitrary-property classes that re-point the sidebar geometry, e.g.
|
|
39
|
+
* `[--sidebar-width:320px]` or `[--sidebar-width-collapsed:88px]`.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ ANCHORED TO A CLASS BOUNDARY, WHICH IS THE WHOLE POINT OF THE LOOKBEHIND.
|
|
42
|
+
* Unanchored it also matched the tail of `md:[--sidebar-width:320px]` and
|
|
43
|
+
* lifted it with the prefix stripped — an override a consumer scoped to
|
|
44
|
+
* ≥768px, applied unconditionally to the drawer that only exists below it.
|
|
45
|
+
*/
|
|
46
|
+
const WIDTH_OVERRIDE = /(?<=^|\s)\[--sidebar-width(?:-collapsed)?:[^\]]+\]/g;
|
|
47
|
+
/**
|
|
48
|
+
* ⛔ THE ONE PART OF THE CONSUMER'S className THAT HAS TO CROSS THE PORTAL.
|
|
49
|
+
*
|
|
50
|
+
* `SheetContent` mounts through `SheetPortal` into `document.body`, and custom
|
|
51
|
+
* properties inherit down the DOM tree rather than the React tree. So the
|
|
52
|
+
* documented override — a class on the rail — set `--sidebar-width` on an
|
|
53
|
+
* element the drawer is not a descendant of, and the whole className that DOES
|
|
54
|
+
* travel lands on the inner `<nav>`, which is BELOW the element that reads the
|
|
55
|
+
* variable. Measured in Chromium: rail 320px, drawer 244px. Following the
|
|
56
|
+
* instruction handed to product-console produced a 280px rail on desktop and a
|
|
57
|
+
* 244px drawer on a phone, silently.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ ONLY THE GEOMETRY TOKENS. The rest of a rail className is rail-shaped —
|
|
60
|
+
* `h-full`, and the `data-[collapsed=false]:min-w-70` all eight console
|
|
61
|
+
* sidebars pass — and putting that on the sheet would reinstate the
|
|
62
|
+
* 280px-inside-a-244px-sheet overflow from the other side.
|
|
63
|
+
*/
|
|
64
|
+
function liftWidthOverrides(className) {
|
|
65
|
+
return className?.match(WIDTH_OVERRIDE)?.join(' ');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* ⛔ BELOW 768px A RAIL IS NOT A LAYOUT — once a consumer opts in.
|
|
69
|
+
*
|
|
70
|
+
* The widths above are a fixed share of a flex row, so at 390px the expanded
|
|
71
|
+
* rail took 63% of the screen and left the page 146px, and the only affordance
|
|
72
|
+
* — `SidebarExpandButton` — shrinks it to 72px, which is still 18% and still
|
|
73
|
+
* permanently on screen. There was nothing that removed it.
|
|
74
|
+
*
|
|
75
|
+
* With `mobile="drawer"` the rail is replaced below the breakpoint by an
|
|
76
|
+
* overlay drawer. Escape, the backdrop click, the focus trap and returning
|
|
77
|
+
* focus to whatever opened it are Radix Dialog's, through the kit's own
|
|
78
|
+
* `Sheet`; none of that is re-implemented here.
|
|
79
|
+
*/
|
|
80
|
+
const SidebarRoot = ({ className, id, mobile = 'inline', mobileTitle = 'Navigation', ...props }) => {
|
|
81
|
+
const context = (0, sidebar_provider_1.useSidebar)();
|
|
82
|
+
const { isCollapsed, isMobile, openMobile, setOpenMobile, restoreDrawerFocus, sidebarId } = context;
|
|
83
|
+
if (!isMobile || mobile === 'inline') {
|
|
84
|
+
return ((0, jsx_runtime_1.jsx)("nav", { "data-slot": "sidebar-root", className: (0, utils_1.cn)(sidebarVariants({ collapsed: isCollapsed }), className), "data-collapsed": isCollapsed, ...props, id: mobile === 'drawer' ? sidebarId : id }));
|
|
85
|
+
}
|
|
86
|
+
return ((0, jsx_runtime_1.jsx)(sidebar_provider_1.SidebarContext.Provider, { value: { ...context, isCollapsed: false, isDrawer: true }, children: (0, jsx_runtime_1.jsx)(sheet_1.Sheet, { open: openMobile, onOpenChange: setOpenMobile, children: (0, jsx_runtime_1.jsxs)(sheet_1.SheetContent, { side: "left",
|
|
87
|
+
// The Sheet's own paddings are built for a form panel; a rail brings
|
|
88
|
+
// its own. `max-w-full` so a consumer widening `--sidebar-width` past
|
|
89
|
+
// the viewport still cannot push the drawer off screen. The lifted
|
|
90
|
+
// override goes LAST so it re-points the variable this element reads.
|
|
91
|
+
className: (0, utils_1.cn)('w-[var(--sidebar-width)] max-w-full gap-0 p-0', liftWidthOverrides(className)), "aria-describedby": undefined,
|
|
92
|
+
// ⛔ RADIX'S OWN RESTORE GOES NOWHERE HERE. `DialogContent`
|
|
93
|
+
// preventDefaults the focus scope's restore and focuses the ref a
|
|
94
|
+
// `SheetTrigger` would have filled; this drawer is opened from provider
|
|
95
|
+
// state, so that ref is null and closing dropped focus onto `<body>`.
|
|
96
|
+
// Running first means `composeEventHandlers` skips Radix's half.
|
|
97
|
+
onCloseAutoFocus: (event) => {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
restoreDrawerFocus();
|
|
100
|
+
}, children: [(0, jsx_runtime_1.jsx)(sheet_1.SheetTitle, { className: "sr-only", children: mobileTitle }), (0, jsx_runtime_1.jsx)("nav", { "data-slot": "sidebar-root", "data-mobile": "true",
|
|
101
|
+
/* ⛔ NO `data-collapsed` HERE. The consumer className is forwarded
|
|
102
|
+
verbatim, and all eight console sidebars pass
|
|
103
|
+
`data-[collapsed=false]:min-w-70`; stamping the attribute armed
|
|
104
|
+
it, giving the navigation a 280px min-width inside a 244px sheet.
|
|
105
|
+
`min-width` resolves after `max-width`, so the sheet could not
|
|
106
|
+
claw it back and the contents were 36px wider than the drawer.
|
|
107
|
+
`data-mobile` already names this surface and a drawer is never
|
|
108
|
+
collapsed, so the attribute had nothing left to say. */
|
|
109
|
+
className: (0, utils_1.cn)(sidebarVariants({ collapsed: false }), 'h-full w-full shadow-none', className), ...props,
|
|
110
|
+
/* ⛔ AFTER THE SPREAD, AND NOT THE CALLER'S. This is the target of
|
|
111
|
+
`SidebarTrigger`'s `aria-controls` and the handle the provider
|
|
112
|
+
uses to put focus back into the navigation when the viewport
|
|
113
|
+
grows. Spread first, a caller that set `id` replaced it and left
|
|
114
|
+
a dangling `aria-controls` — which axe reports as a CRITICAL
|
|
115
|
+
`aria-valid-attr-value` — and a focus restore that lands on
|
|
116
|
+
`<body>`. Both silent. */
|
|
117
|
+
id: sidebarId })] }) }) }));
|
|
23
118
|
};
|
|
24
119
|
exports.SidebarRoot = SidebarRoot;
|