@cloudgrid-io/ui 0.27.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/avatar.d.ts +10 -4
- package/dist/blocks/avatar.d.ts.map +1 -1
- package/dist/blocks/avatar.js +8 -2
- package/dist/blocks/avatar.js.map +1 -1
- package/dist/blocks/icons.d.ts +1 -1
- package/dist/blocks/icons.d.ts.map +1 -1
- package/dist/blocks/icons.js +13 -1
- package/dist/blocks/icons.js.map +1 -1
- package/dist/blocks/side-nav.d.ts +49 -0
- package/dist/blocks/side-nav.d.ts.map +1 -0
- package/dist/blocks/side-nav.js +217 -0
- package/dist/blocks/side-nav.js.map +1 -0
- package/dist/blocks/stat-tile.d.ts +69 -0
- package/dist/blocks/stat-tile.d.ts.map +1 -0
- package/dist/blocks/stat-tile.js +54 -0
- package/dist/blocks/stat-tile.js.map +1 -0
- package/dist/blocks/tool-setup.d.ts +105 -4
- package/dist/blocks/tool-setup.d.ts.map +1 -1
- package/dist/blocks/tool-setup.js +238 -13
- package/dist/blocks/tool-setup.js.map +1 -1
- package/dist/fonts.css +38 -9
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/cloudgrid-cn.d.ts +1 -1
- package/dist/lib/cloudgrid-cn.d.ts.map +1 -1
- package/dist/lib/cloudgrid-cn.js +7 -0
- package/dist/lib/cloudgrid-cn.js.map +1 -1
- package/dist/theme.css +95 -0
- package/dist/tokens/base.json +389 -3
- package/dist/tokens/surface-ink.json +10 -0
- package/dist/tokens/surface-panel.json +10 -0
- package/dist/tokens.css +520 -10
- package/dist/tokens.json +43 -2
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +28 -0
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/dialog.d.ts +17 -1
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +18 -2
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/drawer.d.ts +208 -0
- package/dist/ui/drawer.d.ts.map +1 -0
- package/dist/ui/drawer.js +265 -0
- package/dist/ui/drawer.js.map +1 -0
- package/dist/ui/popover.d.ts +83 -0
- package/dist/ui/popover.d.ts.map +1 -0
- package/dist/ui/popover.js +102 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/radio-group.d.ts +21 -0
- package/dist/ui/radio-group.d.ts.map +1 -0
- package/dist/ui/radio-group.js +101 -0
- package/dist/ui/radio-group.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
7
|
+
import { Button } from "./button.js";
|
|
8
|
+
import { CloseIcon } from "../blocks/icons.js";
|
|
9
|
+
/**
|
|
10
|
+
* The sheet that slides in from an edge (#214).
|
|
11
|
+
*
|
|
12
|
+
* `Dialog` is centred. Nothing in this library came in from the side, so the
|
|
13
|
+
* console hand-composes the same panel eight times: five shipped surfaces on its
|
|
14
|
+
* local `Overlay` primitive (`UserDetailsDrawer`, `FeedbackDetailDrawer`,
|
|
15
|
+
* `VaultGrantsDrawer`, `ConnectorAuditDrawer`, `PerEntityCapDrawer`) and three
|
|
16
|
+
* more drawn in the app page facelift (the Secrets update panel, the Database
|
|
17
|
+
* record details panel, the Domains setup wizard). Each copy guesses its own
|
|
18
|
+
* width, lays out its own title row and picks its own close-button size.
|
|
19
|
+
*
|
|
20
|
+
* `Overlay.taxonomy.md` in the console has carried the line "a future Drawer
|
|
21
|
+
* slides from the right" since that primitive was written. This is it.
|
|
22
|
+
*
|
|
23
|
+
* ## Why this is a positioned Dialog and not Base UI's own Drawer
|
|
24
|
+
*
|
|
25
|
+
* Base UI 1.7.0 ships `Drawer`, and its usage guidelines answer the question
|
|
26
|
+
* directly: "Drawer extends Dialog: it adds gesture support, snap points, and
|
|
27
|
+
* indent effects. If you don't need these, use Dialog instead. A panel that
|
|
28
|
+
* slides in from the edge of the screen and doesn't need gesture support is a
|
|
29
|
+
* positioned Dialog."
|
|
30
|
+
*
|
|
31
|
+
* None of the eight consumers swipes. They are desktop side panels opened from a
|
|
32
|
+
* table row or a button, and the one phone surface (`PlugSheet`) dismisses with
|
|
33
|
+
* its close glyph and the scrim. `Drawer` would also bring `Drawer.Viewport` and
|
|
34
|
+
* a swipe layer this repo has no way to test in Playwright, in exchange for
|
|
35
|
+
* behaviour nobody asked for. If a consumer ever needs swipe-to-dismiss, that is
|
|
36
|
+
* Base UI's `Drawer` and its own issue, not a prop here.
|
|
37
|
+
*
|
|
38
|
+
* ## The size rungs, and where they come from
|
|
39
|
+
*
|
|
40
|
+
* `size` is a named rung rather than a Tailwind class handed in by the caller,
|
|
41
|
+
* which is the whole point of #214: five files currently decide independently how
|
|
42
|
+
* wide a drawer is.
|
|
43
|
+
*
|
|
44
|
+
* md 448px max-w-md
|
|
45
|
+
* lg 512px max-w-lg
|
|
46
|
+
*
|
|
47
|
+
* Both were measured off the founder's frames rather than chosen. The three app
|
|
48
|
+
* page frames export at 1024px against a 1600px design, so the scale is 1.5625:
|
|
49
|
+
*
|
|
50
|
+
* Secrets, "Update secret" 287px exported 448px -> md
|
|
51
|
+
* Database, "Record details" 287px exported 448px -> md
|
|
52
|
+
* Domains, "Set up chimp.me" 325px exported 508px -> lg
|
|
53
|
+
*
|
|
54
|
+
* They land on the two rungs the console already writes by hand: `max-w-md` on
|
|
55
|
+
* three of the five shipped drawers, `max-w-lg` on the newest. #694's own frame
|
|
56
|
+
* is 525 of 1600, which is `lg` to the nearest rung.
|
|
57
|
+
*
|
|
58
|
+
* There is deliberately no third rung. Two values cover every frame and every
|
|
59
|
+
* shipped copy, and a wider one with nothing behind it would be a worker
|
|
60
|
+
* inventing a brand decision.
|
|
61
|
+
*
|
|
62
|
+
* `size` is the INLINE width and applies to `left` and `right` only. A bottom
|
|
63
|
+
* sheet is full width, which is what `PlugSheet` already does.
|
|
64
|
+
*
|
|
65
|
+
* ## Full width at the narrow end, without opting in
|
|
66
|
+
*
|
|
67
|
+
* The popup is `w-full` under a `max-w-*` rung, so below 448px it simply fills
|
|
68
|
+
* the screen. No breakpoint prop, no phone variant, nothing for a caller to
|
|
69
|
+
* remember. #214 asks for exactly this.
|
|
70
|
+
*
|
|
71
|
+
* ## Ground: opaque on every surface
|
|
72
|
+
*
|
|
73
|
+
* The trap `Popover` fell into and #230 records. `bg-popover` resolves to
|
|
74
|
+
* `--cg-surface-card`, which is `transparent` inside `[data-cg-surface='panel']`
|
|
75
|
+
* and a 4% white wash inside `[data-cg-surface='ink']`, because a CARD on a dark
|
|
76
|
+
* brand surface is meant to let it through. An overlay is the one thing that
|
|
77
|
+
* cannot: it is drawn over arbitrary content, and a see-through drawer renders
|
|
78
|
+
* its words and the page's in the same pixels.
|
|
79
|
+
*
|
|
80
|
+
* So the ground is opaque in all three scopes, out of values this system already
|
|
81
|
+
* has: the card ground on the page, `--cg-surface-panel` on the panel,
|
|
82
|
+
* `--cg-surface-code` on ink. No new token and no new colour. Guarded by an alpha
|
|
83
|
+
* assertion on all three grounds in `registry/e2e/drawer.spec.ts`, the same check
|
|
84
|
+
* `popover.test.mjs` and that spec added for #228.
|
|
85
|
+
*
|
|
86
|
+
* ## `container`, and the surface a portal leaves behind
|
|
87
|
+
*
|
|
88
|
+
* Base UI portals to `<body>`, which is outside every `data-cg-surface` scope, and
|
|
89
|
+
* every `--cg-*` value here is inherited. A drawer opened from inside an ink
|
|
90
|
+
* header would render with the page's tokens and look entirely correct.
|
|
91
|
+
* `container` forwards to `Dialog.Portal`'s own prop, exactly as `PopoverContent`
|
|
92
|
+
* does, so a consumer can keep the portal inside the surface it belongs to.
|
|
93
|
+
*
|
|
94
|
+
* It changes the token scope, not the geometry: the popup is `fixed`, so it is
|
|
95
|
+
* anchored to the viewport edge wherever the portal lives. That is what all five
|
|
96
|
+
* console drawers do today. The frames draw the drawer inside the page box
|
|
97
|
+
* because on that page the box fills the window.
|
|
98
|
+
*
|
|
99
|
+
* It was worth checking that a mid-page portal does not cost a MODAL its focus
|
|
100
|
+
* trap, since Base UI's focus guards are siblings of the portal node rather than
|
|
101
|
+
* of `<body>` and a popover is not modal, so #228 never had to care. It does not:
|
|
102
|
+
* eighteen Tab and Shift+Tab presses inside a contained drawer never reach a
|
|
103
|
+
* control outside it, asserted in `registry/e2e/drawer.spec.ts`. An earlier
|
|
104
|
+
* throwaway probe appeared to show a leak and was wrong: it tabbed within a few
|
|
105
|
+
* milliseconds of the popup appearing, before Base UI had armed the trap, which
|
|
106
|
+
* is a race in the probe rather than a property of the component.
|
|
107
|
+
*
|
|
108
|
+
* ## Stacking, which is the bug that forced this issue
|
|
109
|
+
*
|
|
110
|
+
* console#694: the drawer's Remove opens the existing confirm modal, both listen
|
|
111
|
+
* for Escape on the document, and one keystroke dismissed the confirm AND the
|
|
112
|
+
* drawer behind it. The console guards it by making the drawer's `onClose` inert
|
|
113
|
+
* while the confirm is open, which every caller has to remember.
|
|
114
|
+
*
|
|
115
|
+
* Base UI makes the topmost dialog the only one that hears Escape, as long as the
|
|
116
|
+
* nested `Drawer`/`Dialog` root is rendered INSIDE the parent's popup. Nothing
|
|
117
|
+
* here implements that, so the spec proves it rather than the docblock claiming
|
|
118
|
+
* it.
|
|
119
|
+
*
|
|
120
|
+
* What this library DOES have to implement is the layering. Base UI suppresses a
|
|
121
|
+
* nested dialog's backdrop by default, so the first version of this left the
|
|
122
|
+
* drawer at full brightness under an open confirm: two bright surfaces, reading as
|
|
123
|
+
* one broken one rather than as a dialog on top of a sheet. Both overlays here and
|
|
124
|
+
* in `dialog.tsx` therefore pass `forceRender`, so whatever is on top brings its
|
|
125
|
+
* own scrim and everything under it dims, the drawer included.
|
|
126
|
+
*
|
|
127
|
+
* Stacking order needs no z-index of its own. A nested dialog always OPENS after
|
|
128
|
+
* its parent, so its portal is appended later and paints above at the same `z-50`.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* The positioning container, and the edge the sheet is pinned to.
|
|
132
|
+
*
|
|
133
|
+
* `fixed inset-0` with one flex alignment per side, which is precisely the shape
|
|
134
|
+
* the console writes as `scrimClassName="flex justify-end"` five times over. It
|
|
135
|
+
* is Base UI's `Dialog.Viewport`, so it carries the open and nested data
|
|
136
|
+
* attributes and Base UI treats a press on it as an outside press.
|
|
137
|
+
*/
|
|
138
|
+
const drawerViewportVariants = cva("fixed inset-0 z-50 flex", {
|
|
139
|
+
variants: {
|
|
140
|
+
side: {
|
|
141
|
+
right: "justify-end",
|
|
142
|
+
left: "justify-start",
|
|
143
|
+
bottom: "flex-col justify-end",
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
defaultVariants: {
|
|
147
|
+
side: "right",
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* The sheet itself.
|
|
152
|
+
*
|
|
153
|
+
* `flex flex-col` with `overflow-hidden` is what lets `DrawerBody` scroll while
|
|
154
|
+
* the header and footer hold their place: the body is the only child that grows,
|
|
155
|
+
* and the popup never scrolls as a whole.
|
|
156
|
+
*/
|
|
157
|
+
const drawerPopupVariants = cva([
|
|
158
|
+
"relative isolate z-50 flex w-full flex-col overflow-hidden text-body-sm text-popover-foreground shadow-raised outline-none",
|
|
159
|
+
// The ground, opaque per scope. See the docblock: `bg-popover` alone is #230.
|
|
160
|
+
"bg-popover in-data-[cg-surface=panel]:bg-panel in-data-[cg-surface=ink]:bg-code",
|
|
161
|
+
"duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
162
|
+
], {
|
|
163
|
+
variants: {
|
|
164
|
+
side: {
|
|
165
|
+
right: "h-full border-l border-line data-open:slide-in-from-right data-closed:slide-out-to-right",
|
|
166
|
+
left: "h-full border-r border-line data-open:slide-in-from-left data-closed:slide-out-to-left",
|
|
167
|
+
// 85svh, not 85vh: on a phone `vh` is measured against the largest
|
|
168
|
+
// viewport, so the sheet sits partly under the browser chrome while the
|
|
169
|
+
// URL bar is showing. `PlugSheet` has the same 85% and the older unit.
|
|
170
|
+
bottom: "max-h-[85svh] rounded-t-xl border-t border-line data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
|
|
171
|
+
},
|
|
172
|
+
size: {
|
|
173
|
+
md: "max-w-md",
|
|
174
|
+
lg: "max-w-lg",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
compoundVariants: [
|
|
178
|
+
// A bottom sheet is full width. The rungs are the inline axis, and on this
|
|
179
|
+
// edge the inline axis is the whole screen.
|
|
180
|
+
{ side: "bottom", size: "md", class: "max-w-none" },
|
|
181
|
+
{ side: "bottom", size: "lg", class: "max-w-none" },
|
|
182
|
+
],
|
|
183
|
+
defaultVariants: {
|
|
184
|
+
side: "right",
|
|
185
|
+
size: "md",
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
function Drawer({ ...props }) {
|
|
189
|
+
return _jsx(DialogPrimitive.Root, { "data-slot": "drawer", ...props });
|
|
190
|
+
}
|
|
191
|
+
function DrawerTrigger({ ...props }) {
|
|
192
|
+
return _jsx(DialogPrimitive.Trigger, { "data-slot": "drawer-trigger", ...props });
|
|
193
|
+
}
|
|
194
|
+
function DrawerPortal({ ...props }) {
|
|
195
|
+
return _jsx(DialogPrimitive.Portal, { "data-slot": "drawer-portal", ...props });
|
|
196
|
+
}
|
|
197
|
+
function DrawerClose({ ...props }) {
|
|
198
|
+
return _jsx(DialogPrimitive.Close, { "data-slot": "drawer-close", ...props });
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The scrim.
|
|
202
|
+
*
|
|
203
|
+
* `bg-scrim` is `--cg-scrim`, the token the console's `Overlay` already reads
|
|
204
|
+
* inline and the one this system declares for exactly this job. It is deliberately
|
|
205
|
+
* declared at `:root` only and does not flip: a scrim composites over whatever is
|
|
206
|
+
* behind it, so it has no surface of its own.
|
|
207
|
+
*
|
|
208
|
+
* `forceRender` because Base UI does not render a nested dialog's backdrop by
|
|
209
|
+
* default. See the stacking note in the component docblock.
|
|
210
|
+
*/
|
|
211
|
+
function DrawerOverlay({ className, forceRender = true, ...props }) {
|
|
212
|
+
return (_jsx(DialogPrimitive.Backdrop, { "data-slot": "drawer-overlay", forceRender: forceRender, className: cn("fixed inset-0 isolate z-50 bg-scrim duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0", className), ...props }));
|
|
213
|
+
}
|
|
214
|
+
function DrawerContent({ side = "right", size = "md", container, className, children, ...props }) {
|
|
215
|
+
return (_jsxs(DrawerPortal, { container: container, children: [_jsx(DrawerOverlay, {}), _jsx(DialogPrimitive.Viewport, { className: drawerViewportVariants({ side }), children: _jsx(DialogPrimitive.Popup, { "data-slot": "drawer-content", "data-side": side, "data-size": size, className: cn(drawerPopupVariants({ side, size }), className), ...props, children: children }) })] }));
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The title row, with the close affordance built in.
|
|
219
|
+
*
|
|
220
|
+
* The part of #214 that is not geometry. All eight copies lay this row out
|
|
221
|
+
* themselves and the newest had to pick its own close-button size, so the header
|
|
222
|
+
* owns the arrangement: the title on the left, the close glyph top right at the
|
|
223
|
+
* library's own icon rung, an optional description under it, and `children` for
|
|
224
|
+
* the identity row the frames draw in that slot (the secret's key, the record's
|
|
225
|
+
* collection and status chip, the wizard's step strip).
|
|
226
|
+
*
|
|
227
|
+
* `aria-labelledby` needs no wiring: `DrawerTitle` is Base UI's `Dialog.Title`,
|
|
228
|
+
* which registers its own id on the popup. Passing `title` rather than leaving it
|
|
229
|
+
* to the caller is what guarantees every drawer has one.
|
|
230
|
+
*/
|
|
231
|
+
function DrawerHeader({ title, description, closeLabel = "Close", showCloseButton = true, className, children, ...props }) {
|
|
232
|
+
return (_jsxs("div", { "data-slot": "drawer-header", className: cn("flex shrink-0 flex-col gap-2 px-6 pt-5 pb-4", className), ...props, children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsx(DrawerTitle, { children: title }), showCloseButton && (_jsxs(DialogPrimitive.Close, { "data-slot": "drawer-close", render: _jsx(Button, { variant: "ghost", size: "icon-sm", className: "-mt-1 -mr-1.5" }), children: [_jsx(CloseIcon, {}), _jsx("span", { className: "sr-only", children: closeLabel })] }))] }), description ? _jsx(DrawerDescription, { children: description }) : null, children] }));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The scrolling half.
|
|
236
|
+
*
|
|
237
|
+
* `min-h-0` is load-bearing and invisible: a flex child's default `min-height` is
|
|
238
|
+
* `auto`, so without it the body refuses to shrink below its content and the
|
|
239
|
+
* whole sheet grows past the viewport instead of scrolling inside itself. The
|
|
240
|
+
* footer then leaves the screen, which is the one thing a pinned footer must not
|
|
241
|
+
* do.
|
|
242
|
+
*/
|
|
243
|
+
function DrawerBody({ className, ...props }) {
|
|
244
|
+
return (_jsx("div", { "data-slot": "drawer-body", className: cn("min-h-0 flex-1 overflow-y-auto px-6 pb-5", className), ...props }));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The pinned half.
|
|
248
|
+
*
|
|
249
|
+
* `shrink-0` keeps it out of the scroll. It carries a hairline where the header
|
|
250
|
+
* does not: the frames draw neither, but the header sits above content that
|
|
251
|
+
* starts at the top of the sheet while the footer sits above content that has
|
|
252
|
+
* been scrolled under it, and a pinned bar with nothing between it and moving
|
|
253
|
+
* text reads as part of the text.
|
|
254
|
+
*/
|
|
255
|
+
function DrawerFooter({ className, ...props }) {
|
|
256
|
+
return (_jsx("div", { "data-slot": "drawer-footer", className: cn("flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-hairline px-6 py-4", className), ...props }));
|
|
257
|
+
}
|
|
258
|
+
function DrawerTitle({ className, ...props }) {
|
|
259
|
+
return (_jsx(DialogPrimitive.Title, { "data-slot": "drawer-title", className: cn("font-heading text-body-lg leading-none font-medium", className), ...props }));
|
|
260
|
+
}
|
|
261
|
+
function DrawerDescription({ className, ...props }) {
|
|
262
|
+
return (_jsx(DialogPrimitive.Description, { "data-slot": "drawer-description", className: cn("text-caption text-body", className), ...props }));
|
|
263
|
+
}
|
|
264
|
+
export { Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, drawerPopupVariants, drawerViewportVariants, };
|
|
265
|
+
//# sourceMappingURL=drawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawer.js","sourceRoot":"","sources":["../../src/ui/drawer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
3
|
+
/**
|
|
4
|
+
* An anchored panel: a button, and a container that opens against it (#228).
|
|
5
|
+
*
|
|
6
|
+
* The third overlay in this library and the first one that is neither centred
|
|
7
|
+
* nor a menu. `Dialog` is modal and sits in the middle of the viewport, which is
|
|
8
|
+
* wrong for an editor attached to the control that opened it. `DropdownMenu` is
|
|
9
|
+
* a menu: its children are `role="menuitem"`, arrow keys move between them, and
|
|
10
|
+
* typing jumps to a label. A panel holding two radio groups, a checkbox list and
|
|
11
|
+
* two buttons is not a menu, and announcing it as one tells a screen reader user
|
|
12
|
+
* that Escape and the arrow keys mean something they do not.
|
|
13
|
+
*
|
|
14
|
+
* The console hand-rolls exactly this in `HeaderVisibilityPopover.tsx` — its own
|
|
15
|
+
* portal, its own outside-click listener, its own Escape handler, its own focus
|
|
16
|
+
* return. All four are Base UI defaults here, which is the whole reason this is a
|
|
17
|
+
* library primitive rather than a copy of that file.
|
|
18
|
+
*
|
|
19
|
+
* ## What comes from Base UI, and is therefore asserted rather than written
|
|
20
|
+
*
|
|
21
|
+
* `role="dialog"` on the popup, `aria-labelledby` pointing at `PopoverTitle`
|
|
22
|
+
* whenever one is rendered and absent when one is not, Escape and outside-press
|
|
23
|
+
* dismiss, focus moving to the first tabbable element on open, and focus
|
|
24
|
+
* returning to the trigger on close. None of it is reimplemented here. All of it
|
|
25
|
+
* is covered in `registry/e2e/radio-group-popover.spec.ts`, because "the library
|
|
26
|
+
* gives us this" is a claim and this repo measures claims.
|
|
27
|
+
*
|
|
28
|
+
* It is NOT modal by default. Page scroll stays live and the rest of the document
|
|
29
|
+
* stays interactive, which is right for an editor anchored to a header button.
|
|
30
|
+
* Pass `modal` through to `Popover` for the other behaviour.
|
|
31
|
+
*
|
|
32
|
+
* ## `container`, and the surface it exists for
|
|
33
|
+
*
|
|
34
|
+
* Base UI portals the popup to `<body>`. Every `--cg-*` value in this system is
|
|
35
|
+
* inherited, and `data-cg-surface` is a container attribute, so a popup portaled
|
|
36
|
+
* to the body escapes its surface scope entirely: a popover opened from a button
|
|
37
|
+
* inside an ink header renders with the page's tokens, and looks completely
|
|
38
|
+
* correct in the only place anyone checks.
|
|
39
|
+
*
|
|
40
|
+
* `container` forwards to `Popover.Portal`'s own prop, so a consumer can anchor
|
|
41
|
+
* the portal inside the surface. The gallery uses it to render this component on
|
|
42
|
+
* the panel and on ink for real; without it the three frames would be three
|
|
43
|
+
* copies of the page one wearing different borders.
|
|
44
|
+
*
|
|
45
|
+
* ## Tokens, and the one thing an overlay cannot inherit
|
|
46
|
+
*
|
|
47
|
+
* Card ground, control border, raised shadow, per #228. `bg-popover` resolves to
|
|
48
|
+
* `--cg-surface-card` and `--popover` is declared once per surface scope, so the
|
|
49
|
+
* ground flips with the surface (rule 2).
|
|
50
|
+
*
|
|
51
|
+
* On the two dark surfaces that card ground is `transparent` (panel) and a 4%
|
|
52
|
+
* white wash (ink), because a CARD on a purple panel is meant to let the purple
|
|
53
|
+
* through — `shadcn.css` says so in as many words. An OVERLAY is the one thing
|
|
54
|
+
* that cannot: it is drawn over arbitrary content, and a transparent panel over a
|
|
55
|
+
* list renders both sets of words on top of each other. Measured, not assumed:
|
|
56
|
+
* the first version of this component was completely illegible on both dark
|
|
57
|
+
* frames in the gallery and completely correct on white, which is the only place
|
|
58
|
+
* anyone would have looked.
|
|
59
|
+
*
|
|
60
|
+
* So the ground is opaque in every scope, out of values this system already has:
|
|
61
|
+
* the card ground on the page, and each dark surface's OWN ground on the dark
|
|
62
|
+
* surfaces. No new token and no new colour — `bg-panel` is `--cg-surface-panel`
|
|
63
|
+
* and `bg-code` is `--cg-surface-code`. The panel is then told apart from what is
|
|
64
|
+
* behind it by the border and the shadow rather than by a tint, which is what
|
|
65
|
+
* both of those are for.
|
|
66
|
+
*
|
|
67
|
+
* KNOWN, and not fixed here: `Dialog`, `DropdownMenu`, `Select` and `Combobox`
|
|
68
|
+
* are all authored with `bg-popover` and have the same hole on the same two
|
|
69
|
+
* surfaces. The one-line fix is `--popover` per scope in `shadcn.css`, beside the
|
|
70
|
+
* four backgrounds that already get that treatment, and it changes what four
|
|
71
|
+
* shipped components render on dark grounds — which is a design-system decision
|
|
72
|
+
* rather than a line in a PR about two primitives.
|
|
73
|
+
*/
|
|
74
|
+
declare function Popover({ ...props }: PopoverPrimitive.Root.Props): React.JSX.Element;
|
|
75
|
+
declare function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props): React.JSX.Element;
|
|
76
|
+
declare function PopoverPortal({ ...props }: PopoverPrimitive.Portal.Props): React.JSX.Element;
|
|
77
|
+
declare function PopoverClose({ ...props }: PopoverPrimitive.Close.Props): React.JSX.Element;
|
|
78
|
+
declare function PopoverContent({ align, alignOffset, side, sideOffset, container, className, ...props }: PopoverPrimitive.Popup.Props & Pick<PopoverPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset"> & Pick<PopoverPrimitive.Portal.Props, "container">): React.JSX.Element;
|
|
79
|
+
declare function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props): React.JSX.Element;
|
|
80
|
+
declare function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props): React.JSX.Element;
|
|
81
|
+
declare function PopoverFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
82
|
+
export { Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverFooter, PopoverPortal, PopoverTitle, PopoverTrigger, };
|
|
83
|
+
//# sourceMappingURL=popover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../src/ui/popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAIpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAEH,iBAAS,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,KAAK,qBAEzD;AAED,iBAAS,cAAc,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,qBAEnE;AAED,iBAAS,aAAa,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,CAAC,MAAM,CAAC,KAAK,qBAEjE;AAED,iBAAS,YAAY,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,qBAE/D;AAED,iBAAS,cAAc,CAAC,EACtB,KAAe,EACf,WAAe,EACf,IAAe,EACf,UAAc,EACd,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,GAC7B,IAAI,CACF,gBAAgB,CAAC,UAAU,CAAC,KAAK,EACjC,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,YAAY,CAChD,GACD,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,qBA2BjD;AAED,iBAAS,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,qBAW1E;AAED,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,CAAC,WAAW,CAAC,KAAK,qBAQpC;AAED,iBAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAQ1E;AAED,OAAO,EACL,OAAO,EACP,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,GACf,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
5
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
6
|
+
/**
|
|
7
|
+
* An anchored panel: a button, and a container that opens against it (#228).
|
|
8
|
+
*
|
|
9
|
+
* The third overlay in this library and the first one that is neither centred
|
|
10
|
+
* nor a menu. `Dialog` is modal and sits in the middle of the viewport, which is
|
|
11
|
+
* wrong for an editor attached to the control that opened it. `DropdownMenu` is
|
|
12
|
+
* a menu: its children are `role="menuitem"`, arrow keys move between them, and
|
|
13
|
+
* typing jumps to a label. A panel holding two radio groups, a checkbox list and
|
|
14
|
+
* two buttons is not a menu, and announcing it as one tells a screen reader user
|
|
15
|
+
* that Escape and the arrow keys mean something they do not.
|
|
16
|
+
*
|
|
17
|
+
* The console hand-rolls exactly this in `HeaderVisibilityPopover.tsx` — its own
|
|
18
|
+
* portal, its own outside-click listener, its own Escape handler, its own focus
|
|
19
|
+
* return. All four are Base UI defaults here, which is the whole reason this is a
|
|
20
|
+
* library primitive rather than a copy of that file.
|
|
21
|
+
*
|
|
22
|
+
* ## What comes from Base UI, and is therefore asserted rather than written
|
|
23
|
+
*
|
|
24
|
+
* `role="dialog"` on the popup, `aria-labelledby` pointing at `PopoverTitle`
|
|
25
|
+
* whenever one is rendered and absent when one is not, Escape and outside-press
|
|
26
|
+
* dismiss, focus moving to the first tabbable element on open, and focus
|
|
27
|
+
* returning to the trigger on close. None of it is reimplemented here. All of it
|
|
28
|
+
* is covered in `registry/e2e/radio-group-popover.spec.ts`, because "the library
|
|
29
|
+
* gives us this" is a claim and this repo measures claims.
|
|
30
|
+
*
|
|
31
|
+
* It is NOT modal by default. Page scroll stays live and the rest of the document
|
|
32
|
+
* stays interactive, which is right for an editor anchored to a header button.
|
|
33
|
+
* Pass `modal` through to `Popover` for the other behaviour.
|
|
34
|
+
*
|
|
35
|
+
* ## `container`, and the surface it exists for
|
|
36
|
+
*
|
|
37
|
+
* Base UI portals the popup to `<body>`. Every `--cg-*` value in this system is
|
|
38
|
+
* inherited, and `data-cg-surface` is a container attribute, so a popup portaled
|
|
39
|
+
* to the body escapes its surface scope entirely: a popover opened from a button
|
|
40
|
+
* inside an ink header renders with the page's tokens, and looks completely
|
|
41
|
+
* correct in the only place anyone checks.
|
|
42
|
+
*
|
|
43
|
+
* `container` forwards to `Popover.Portal`'s own prop, so a consumer can anchor
|
|
44
|
+
* the portal inside the surface. The gallery uses it to render this component on
|
|
45
|
+
* the panel and on ink for real; without it the three frames would be three
|
|
46
|
+
* copies of the page one wearing different borders.
|
|
47
|
+
*
|
|
48
|
+
* ## Tokens, and the one thing an overlay cannot inherit
|
|
49
|
+
*
|
|
50
|
+
* Card ground, control border, raised shadow, per #228. `bg-popover` resolves to
|
|
51
|
+
* `--cg-surface-card` and `--popover` is declared once per surface scope, so the
|
|
52
|
+
* ground flips with the surface (rule 2).
|
|
53
|
+
*
|
|
54
|
+
* On the two dark surfaces that card ground is `transparent` (panel) and a 4%
|
|
55
|
+
* white wash (ink), because a CARD on a purple panel is meant to let the purple
|
|
56
|
+
* through — `shadcn.css` says so in as many words. An OVERLAY is the one thing
|
|
57
|
+
* that cannot: it is drawn over arbitrary content, and a transparent panel over a
|
|
58
|
+
* list renders both sets of words on top of each other. Measured, not assumed:
|
|
59
|
+
* the first version of this component was completely illegible on both dark
|
|
60
|
+
* frames in the gallery and completely correct on white, which is the only place
|
|
61
|
+
* anyone would have looked.
|
|
62
|
+
*
|
|
63
|
+
* So the ground is opaque in every scope, out of values this system already has:
|
|
64
|
+
* the card ground on the page, and each dark surface's OWN ground on the dark
|
|
65
|
+
* surfaces. No new token and no new colour — `bg-panel` is `--cg-surface-panel`
|
|
66
|
+
* and `bg-code` is `--cg-surface-code`. The panel is then told apart from what is
|
|
67
|
+
* behind it by the border and the shadow rather than by a tint, which is what
|
|
68
|
+
* both of those are for.
|
|
69
|
+
*
|
|
70
|
+
* KNOWN, and not fixed here: `Dialog`, `DropdownMenu`, `Select` and `Combobox`
|
|
71
|
+
* are all authored with `bg-popover` and have the same hole on the same two
|
|
72
|
+
* surfaces. The one-line fix is `--popover` per scope in `shadcn.css`, beside the
|
|
73
|
+
* four backgrounds that already get that treatment, and it changes what four
|
|
74
|
+
* shipped components render on dark grounds — which is a design-system decision
|
|
75
|
+
* rather than a line in a PR about two primitives.
|
|
76
|
+
*/
|
|
77
|
+
function Popover({ ...props }) {
|
|
78
|
+
return _jsx(PopoverPrimitive.Root, { ...props });
|
|
79
|
+
}
|
|
80
|
+
function PopoverTrigger({ ...props }) {
|
|
81
|
+
return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
82
|
+
}
|
|
83
|
+
function PopoverPortal({ ...props }) {
|
|
84
|
+
return _jsx(PopoverPrimitive.Portal, { "data-slot": "popover-portal", ...props });
|
|
85
|
+
}
|
|
86
|
+
function PopoverClose({ ...props }) {
|
|
87
|
+
return _jsx(PopoverPrimitive.Close, { "data-slot": "popover-close", ...props });
|
|
88
|
+
}
|
|
89
|
+
function PopoverContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 8, container, className, ...props }) {
|
|
90
|
+
return (_jsx(PopoverPrimitive.Portal, { container: container, children: _jsx(PopoverPrimitive.Positioner, { className: "isolate z-50 outline-none", align: align, alignOffset: alignOffset, side: side, sideOffset: sideOffset, children: _jsx(PopoverPrimitive.Popup, { "data-slot": "popover-content", className: cn("grid max-h-(--available-height) w-80 max-w-[calc(100vw-2rem)] origin-(--transform-origin) gap-3 overflow-y-auto rounded-xl border border-line bg-popover p-4 text-body-sm text-popover-foreground shadow-raised duration-100 outline-none in-data-[cg-surface=ink]:bg-code in-data-[cg-surface=panel]:bg-panel data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className), ...props }) }) }));
|
|
91
|
+
}
|
|
92
|
+
function PopoverTitle({ className, ...props }) {
|
|
93
|
+
return (_jsx(PopoverPrimitive.Title, { "data-slot": "popover-title", className: cn("font-heading text-body-md leading-none font-medium", className), ...props }));
|
|
94
|
+
}
|
|
95
|
+
function PopoverDescription({ className, ...props }) {
|
|
96
|
+
return (_jsx(PopoverPrimitive.Description, { "data-slot": "popover-description", className: cn("text-caption text-body", className), ...props }));
|
|
97
|
+
}
|
|
98
|
+
function PopoverFooter({ className, ...props }) {
|
|
99
|
+
return (_jsx("div", { "data-slot": "popover-footer", className: cn("flex flex-wrap justify-end gap-2 pt-1", className), ...props }));
|
|
100
|
+
}
|
|
101
|
+
export { Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverFooter, PopoverPortal, PopoverTitle, PopoverTrigger, };
|
|
102
|
+
//# sourceMappingURL=popover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popover.js","sourceRoot":"","sources":["../../src/ui/popover.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAEH,SAAS,OAAO,CAAC,EAAE,GAAG,KAAK,EAA+B;IACxD,OAAO,KAAC,gBAAgB,CAAC,IAAI,OAAK,KAAK,GAAI,CAAA;AAC7C,CAAC;AAED,SAAS,cAAc,CAAC,EAAE,GAAG,KAAK,EAAkC;IAClE,OAAO,KAAC,gBAAgB,CAAC,OAAO,iBAAW,iBAAiB,KAAK,KAAK,GAAI,CAAA;AAC5E,CAAC;AAED,SAAS,aAAa,CAAC,EAAE,GAAG,KAAK,EAAiC;IAChE,OAAO,KAAC,gBAAgB,CAAC,MAAM,iBAAW,gBAAgB,KAAK,KAAK,GAAI,CAAA;AAC1E,CAAC;AAED,SAAS,YAAY,CAAC,EAAE,GAAG,KAAK,EAAgC;IAC9D,OAAO,KAAC,gBAAgB,CAAC,KAAK,iBAAW,eAAe,KAAK,KAAK,GAAI,CAAA;AACxE,CAAC;AAED,SAAS,cAAc,CAAC,EACtB,KAAK,GAAG,OAAO,EACf,WAAW,GAAG,CAAC,EACf,IAAI,GAAG,QAAQ,EACf,UAAU,GAAG,CAAC,EACd,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EAMwC;IAChD,OAAO,CACL,KAAC,gBAAgB,CAAC,MAAM,IAAC,SAAS,EAAE,SAAS,YAC3C,KAAC,gBAAgB,CAAC,UAAU,IAC1B,SAAS,EAAC,2BAA2B,EACrC,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,UAAU,YAQtB,KAAC,gBAAgB,CAAC,KAAK,iBACX,iBAAiB,EAC3B,SAAS,EAAE,EAAE,CACX,ilBAAilB,EACjlB,SAAS,CACV,KACG,KAAK,GACT,GAC0B,GACN,CAC3B,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAgC;IACzE,OAAO,CACL,KAAC,gBAAgB,CAAC,KAAK,iBACX,eAAe,EACzB,SAAS,EAAE,EAAE,CACX,oDAAoD,EACpD,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EAC2B;IACnC,OAAO,CACL,KAAC,gBAAgB,CAAC,WAAW,iBACjB,qBAAqB,EAC/B,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC,KAC9C,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IACzE,OAAO,CACL,2BACY,gBAAgB,EAC1B,SAAS,EAAE,EAAE,CAAC,uCAAuC,EAAE,SAAS,CAAC,KAC7D,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EACL,OAAO,EACP,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,GACf,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
3
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
4
|
+
declare function RadioGroup({ className, value, defaultValue, onValueChange, ...props }: RadioGroupPrimitive.Props): React.JSX.Element;
|
|
5
|
+
declare function Radio({ className, ...props }: RadioPrimitive.Root.Props): React.JSX.Element;
|
|
6
|
+
declare function RadioOption({ value, label, description, disabled, className, children, ...props }: Omit<React.ComponentProps<"div">, "ref" | "children"> & {
|
|
7
|
+
/** What the group reports when this row is picked. */
|
|
8
|
+
value: unknown;
|
|
9
|
+
/** The row's accessible name. One line. */
|
|
10
|
+
label: React.ReactNode;
|
|
11
|
+
/** A line under the label, wired as a description rather than part of the name. */
|
|
12
|
+
description?: React.ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The indented slot, rendered ONLY while this option is selected. The
|
|
16
|
+
* visibility editor puts its space checkboxes here.
|
|
17
|
+
*/
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
export { Radio, RadioGroup, RadioOption };
|
|
21
|
+
//# sourceMappingURL=radio-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../src/ui/radio-group.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,EAAE,UAAU,IAAI,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAwE9E,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,KAAK,EACL,YAAY,EACZ,aAAa,EACb,GAAG,KAAK,EACT,EAAE,mBAAmB,CAAC,KAAK,qBAsB3B;AAED,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,qBAgBhE;AAED,iBAAS,WAAW,CAAC,EACnB,KAAK,EACL,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,GAAG;IACzD,sDAAsD;IACtD,KAAK,EAAE,OAAO,CAAA;IACd,2CAA2C;IAC3C,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,mFAAmF;IACnF,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,qBA+DA;AAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAA"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
5
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
6
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
7
|
+
/**
|
|
8
|
+
* One of a set. `RadioGroup` is the set, `Radio` is the control, and
|
|
9
|
+
* `RadioOption` is the row the visibility editor is built out of: control,
|
|
10
|
+
* label, one line of description, and a slot that appears only while that
|
|
11
|
+
* option is the selected one (#226).
|
|
12
|
+
*
|
|
13
|
+
* ## Why this is Base UI's radio and not a styled input
|
|
14
|
+
*
|
|
15
|
+
* A radio group has ONE tab stop and roving focus: arrows move the selection,
|
|
16
|
+
* Space selects, Tab leaves the group. A column of `<input type="radio">`
|
|
17
|
+
* elements with the same `name` gets that from the browser; a column of styled
|
|
18
|
+
* divs does not, and the difference is invisible in a screenshot and in review.
|
|
19
|
+
* `OnboardingQuestion` (#119) already made this argument for its `single` mode
|
|
20
|
+
* and reached for the same primitive.
|
|
21
|
+
*
|
|
22
|
+
* ## The option row, and the name it must NOT have
|
|
23
|
+
*
|
|
24
|
+
* `RadioOption` wraps the control and both lines of text in a `<label>`, which
|
|
25
|
+
* is what makes the whole row clickable rather than the 16px circle.
|
|
26
|
+
*
|
|
27
|
+
* It then passes `aria-labelledby` and `aria-describedby` EXPLICITLY, and that
|
|
28
|
+
* is the load-bearing part. Base UI names a `role="radio"` span by looking for
|
|
29
|
+
* an associated `<label>` and using its id, which is right for its own demo
|
|
30
|
+
* where the label holds one word. Here the label holds the description too, so
|
|
31
|
+
* the fallback would announce "These spaces. Members of the spaces you choose."
|
|
32
|
+
* as the option's NAME on every row. The name is the label, the description is a
|
|
33
|
+
* description, and nothing goes red if they are collapsed into one.
|
|
34
|
+
*
|
|
35
|
+
* ## The child slot is a sibling, so the group has to carry its value
|
|
36
|
+
*
|
|
37
|
+
* The nested space list in the frame sits UNDER its option, indented, not inside
|
|
38
|
+
* the control. So neither `Radio.Root`'s state-function `className` nor its
|
|
39
|
+
* `render` prop can reach it, and Base UI does not export the group's context.
|
|
40
|
+
*
|
|
41
|
+
* `RadioGroup` mirrors the selected value into a context of its own for that one
|
|
42
|
+
* question: is this option the selected one. The Base UI group stays the source
|
|
43
|
+
* of truth in both directions — pass `value` for a controlled group,
|
|
44
|
+
* `defaultValue` for an uncontrolled one — so the mirror never becomes a second
|
|
45
|
+
* place the answer lives. Same shape `OnboardingQuestion` uses for its Continue
|
|
46
|
+
* button.
|
|
47
|
+
*
|
|
48
|
+
* The slot is not rendered at all when the option is unselected, rather than
|
|
49
|
+
* rendered and hidden. A hidden slot keeps its checkboxes in the tab order,
|
|
50
|
+
* which is a set of controls a keyboard can reach and a reader cannot see.
|
|
51
|
+
*
|
|
52
|
+
* ## Tokens, and the one utility this deliberately does not copy
|
|
53
|
+
*
|
|
54
|
+
* `checkbox.tsx` grows its hit area with `after:-inset-x-3 after:-inset-y-2`.
|
|
55
|
+
* That is right for a checkbox sitting on its own and wrong here: the rows in
|
|
56
|
+
* this frame are about 37px apart, so a 32px invisible overlay per control
|
|
57
|
+
* overlaps its neighbour and a click near the boundary selects the wrong option.
|
|
58
|
+
* The row is the target instead, and it is wider and taller than any overlay.
|
|
59
|
+
*
|
|
60
|
+
* Colour comes from `--primary` through the shadcn bridge, which is declared per
|
|
61
|
+
* surface scope, so the selected ring and dot are brand purple on the page and
|
|
62
|
+
* brand lime on both dark grounds. Selection is never colour alone: the dot is a
|
|
63
|
+
* shape that is either there or not.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* The selected value, for `RadioOption`'s slot and nothing else.
|
|
67
|
+
*
|
|
68
|
+
* `undefined` means "no RadioGroup above me", which is also what "nothing
|
|
69
|
+
* selected" looks like. Both answer `false` to every option, which is the
|
|
70
|
+
* correct behaviour for a `Radio` used outside a group.
|
|
71
|
+
*/
|
|
72
|
+
const RadioGroupValueContext = React.createContext(undefined);
|
|
73
|
+
function RadioGroup({ className, value, defaultValue, onValueChange, ...props }) {
|
|
74
|
+
// The mirror. Initialised from `defaultValue` and updated on every change, so
|
|
75
|
+
// an uncontrolled group can still answer the slot's question. A controlled
|
|
76
|
+
// group ignores it entirely: `value` is read first.
|
|
77
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
|
|
78
|
+
const selected = value !== undefined ? value : uncontrolled;
|
|
79
|
+
return (_jsx(RadioGroupValueContext.Provider, { value: selected, children: _jsx(RadioGroupPrimitive, { "data-slot": "radio-group", className: cn("grid gap-3", className), value: value, defaultValue: defaultValue, onValueChange: (next, details) => {
|
|
80
|
+
setUncontrolled(next);
|
|
81
|
+
onValueChange?.(next, details);
|
|
82
|
+
}, ...props }) }));
|
|
83
|
+
}
|
|
84
|
+
function Radio({ className, ...props }) {
|
|
85
|
+
return (_jsx(RadioPrimitive.Root, { "data-slot": "radio", className: cn("peer flex size-4 shrink-0 items-center justify-center rounded-pill border border-input p-0 transition-colors outline-none group-has-disabled/field:opacity-50 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-checked:border-primary data-checked:text-primary data-disabled:cursor-not-allowed data-disabled:opacity-50", className), ...props, children: _jsx(RadioPrimitive.Indicator, { "data-slot": "radio-indicator", className: "size-2 shrink-0 rounded-pill bg-current" }) }));
|
|
86
|
+
}
|
|
87
|
+
function RadioOption({ value, label, description, disabled, className, children, ...props }) {
|
|
88
|
+
const selected = React.useContext(RadioGroupValueContext);
|
|
89
|
+
const checked = selected === value;
|
|
90
|
+
const id = React.useId();
|
|
91
|
+
const labelId = `${id}-label`;
|
|
92
|
+
const descriptionId = `${id}-description`;
|
|
93
|
+
return (_jsxs("div", { "data-slot": "radio-option", "data-checked": checked ? "" : undefined, "data-disabled": disabled ? "" : undefined, className: cn("grid gap-1.5", className), ...props, children: [_jsxs("label", { className: cn("flex cursor-pointer items-start gap-2.5 select-none", disabled && "cursor-not-allowed opacity-50"), children: [_jsx(Radio, { value: value, disabled: disabled, "aria-labelledby": labelId, "aria-describedby": description ? descriptionId : undefined,
|
|
94
|
+
// Optically centred on the label's first line rather than its box.
|
|
95
|
+
className: "mt-0.5" }), _jsxs("span", { className: "grid gap-0.5", children: [_jsx("span", { id: labelId, "data-slot": "radio-option-label", className: "text-body-sm text-ink", children: label }), description ? (_jsx("span", { id: descriptionId, "data-slot": "radio-option-description", className: "text-caption text-faint", children: description })) : null] })] }), children && checked ? (_jsx("div", { "data-slot": "radio-option-detail",
|
|
96
|
+
// Indented to the label's edge: the control is 16px and the gap is
|
|
97
|
+
// 10px, so the text column starts at 26px.
|
|
98
|
+
className: "grid gap-2 ps-[26px]", children: children })) : null] }));
|
|
99
|
+
}
|
|
100
|
+
export { Radio, RadioGroup, RadioOption };
|
|
101
|
+
//# sourceMappingURL=radio-group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio-group.js","sourceRoot":"","sources":["../../src/ui/radio-group.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "CloudGrid design system: React components, brand design tokens, Tailwind theme, self-hosted fonts.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@base-ui/react": "^1.7.0",
|
|
33
33
|
"@fontsource-variable/jetbrains-mono": "^5.3.0",
|
|
34
34
|
"@fontsource-variable/manrope": "^5.3.0",
|
|
35
|
+
"@fontsource-variable/space-grotesk": "^5.3.0",
|
|
35
36
|
"class-variance-authority": "^0.7.1",
|
|
36
37
|
"clsx": "^2.1.1",
|
|
37
38
|
"sonner": "^2.0.8",
|