@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.
Files changed (56) hide show
  1. package/dist/blocks/avatar.d.ts +10 -4
  2. package/dist/blocks/avatar.d.ts.map +1 -1
  3. package/dist/blocks/avatar.js +8 -2
  4. package/dist/blocks/avatar.js.map +1 -1
  5. package/dist/blocks/icons.d.ts +1 -1
  6. package/dist/blocks/icons.d.ts.map +1 -1
  7. package/dist/blocks/icons.js +13 -1
  8. package/dist/blocks/icons.js.map +1 -1
  9. package/dist/blocks/side-nav.d.ts +49 -0
  10. package/dist/blocks/side-nav.d.ts.map +1 -0
  11. package/dist/blocks/side-nav.js +217 -0
  12. package/dist/blocks/side-nav.js.map +1 -0
  13. package/dist/blocks/stat-tile.d.ts +69 -0
  14. package/dist/blocks/stat-tile.d.ts.map +1 -0
  15. package/dist/blocks/stat-tile.js +54 -0
  16. package/dist/blocks/stat-tile.js.map +1 -0
  17. package/dist/blocks/tool-setup.d.ts +105 -4
  18. package/dist/blocks/tool-setup.d.ts.map +1 -1
  19. package/dist/blocks/tool-setup.js +238 -13
  20. package/dist/blocks/tool-setup.js.map +1 -1
  21. package/dist/fonts.css +38 -9
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/cloudgrid-cn.d.ts +1 -1
  27. package/dist/lib/cloudgrid-cn.d.ts.map +1 -1
  28. package/dist/lib/cloudgrid-cn.js +7 -0
  29. package/dist/lib/cloudgrid-cn.js.map +1 -1
  30. package/dist/theme.css +95 -0
  31. package/dist/tokens/base.json +389 -3
  32. package/dist/tokens/surface-ink.json +10 -0
  33. package/dist/tokens/surface-panel.json +10 -0
  34. package/dist/tokens.css +520 -10
  35. package/dist/tokens.json +43 -2
  36. package/dist/ui/button.d.ts +1 -1
  37. package/dist/ui/button.d.ts.map +1 -1
  38. package/dist/ui/button.js +28 -0
  39. package/dist/ui/button.js.map +1 -1
  40. package/dist/ui/dialog.d.ts +17 -1
  41. package/dist/ui/dialog.d.ts.map +1 -1
  42. package/dist/ui/dialog.js +18 -2
  43. package/dist/ui/dialog.js.map +1 -1
  44. package/dist/ui/drawer.d.ts +208 -0
  45. package/dist/ui/drawer.d.ts.map +1 -0
  46. package/dist/ui/drawer.js +265 -0
  47. package/dist/ui/drawer.js.map +1 -0
  48. package/dist/ui/popover.d.ts +83 -0
  49. package/dist/ui/popover.d.ts.map +1 -0
  50. package/dist/ui/popover.js +102 -0
  51. package/dist/ui/popover.js.map +1 -0
  52. package/dist/ui/radio-group.d.ts +21 -0
  53. package/dist/ui/radio-group.d.ts.map +1 -0
  54. package/dist/ui/radio-group.js +101 -0
  55. package/dist/ui/radio-group.js.map +1 -0
  56. 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.27.1",
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",