@cueplusplus/ui 0.11.1 → 0.12.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.
@@ -0,0 +1,298 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { GROUND_CLASSES } from "./_ground.js";
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/layout/frames.tsx
7
+ /** One arrow press where nothing on the page has declared the space ladder's top rung. */
8
+ const STEP_FALLBACK = 32;
9
+ /** What a rem is worth where no document can be asked — `use-density.ts`'s own assumption. */
10
+ const ASSUMED_ROOT_FONT_SIZE = 16;
11
+ /** The root's font size, for a rung declared in rem. */
12
+ function rootFontSize() {
13
+ if (typeof document === "undefined") return ASSUMED_ROOT_FONT_SIZE;
14
+ const size = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
15
+ return Number.isFinite(size) && size > 0 ? size : ASSUMED_ROOT_FONT_SIZE;
16
+ }
17
+ /**
18
+ * One rung of `--cue-space-8` in CSS pixels, read off the canvas itself.
19
+ *
20
+ * Off the canvas and not off the root, because the rung is a density token and
21
+ * a canvas may sit inside a `<Density>` island — a compact board steps by a
22
+ * compact rung, which is the whole point of spending a token instead of a
23
+ * number. Read at the keypress rather than cached: the density under it can
24
+ * change between two presses and nothing re-renders when it does.
25
+ *
26
+ * `null` is not a return value here. A page that declared nothing — a consumer
27
+ * who imported no stylesheet, a server's first paint, jsdom — still has to move
28
+ * when a reader presses an arrow, so there is a fallback and it is a number
29
+ * this file owns.
30
+ */
31
+ function rungOf(canvas) {
32
+ if (typeof window === "undefined") return STEP_FALLBACK;
33
+ const declared = window.getComputedStyle(canvas).getPropertyValue("--cue-space-8").trim();
34
+ const length = Number.parseFloat(declared);
35
+ if (!Number.isFinite(length) || length <= 0) return STEP_FALLBACK;
36
+ return declared.endsWith("rem") ? length * rootFontSize() : length;
37
+ }
38
+ /**
39
+ * Where a drag may not start: the elements whose whole job is to be pressed.
40
+ *
41
+ * Matched with `closest` and not on the target alone, because a control in this
42
+ * house is usually a box drawn round something else — an icon, a span, a label.
43
+ * A drag that starts on the icon started on the button, and a pan that began
44
+ * there would take the pointer capture with it and the press would never land.
45
+ *
46
+ * **The `[role=…]` half is the same sentence about controls this library draws
47
+ * out of a `div`.** A `DropdownMenu.Item` is a `<div role="menuitem">` whose
48
+ * label is a `<span>` inside it; an `option`, a `tab`, a `treeitem` are all that
49
+ * shape. Without them here a reader who clicked a menu row *on its words* had
50
+ * the canvas take the pointer capture and the `click` retargeted away — the row
51
+ * worked from the keyboard and worked on its padding, and did nothing where the
52
+ * text was. That was reported against the frame name menu and is a defect of
53
+ * this predicate rather than of that menu.
54
+ *
55
+ * The list is ARIA's **standalone widget roles** — the ones that *are* a control
56
+ * rather than a box holding controls, so a composite (`menu`, `listbox`,
57
+ * `tablist`, `tree`, `radiogroup`, `toolbar`, `grid`) is deliberately absent:
58
+ * those are chrome, and a drag on a menu's own padding is a pan. Three
59
+ * standalone ones are absent too, each for its own reason — `progressbar` is
60
+ * read and never operated, `tabpanel` is the content a `tab` switches to rather
61
+ * than the thing pressed, and `gridcell` is a box holding content, so a drag
62
+ * across a table inside a frame stays a pan.
63
+ *
64
+ * An allowlist and not a denylist of container roles, because the two fail in
65
+ * opposite directions: a role missing from this list leaves the old behaviour
66
+ * (the element itself is still matched by {@link ROLE_BEARING}), while a
67
+ * container role missing from a denylist would silently stop the board panning.
68
+ */
69
+ const CONTROLS = "button, a, input, select, textarea, [role=button], [role=checkbox], [role=link], [role=menuitem], [role=menuitemcheckbox], [role=menuitemradio], [role=option], [role=radio], [role=scrollbar], [role=searchbox], [role=separator], [role=slider], [role=spinbutton], [role=switch], [role=tab], [role=textbox], [role=treeitem]";
70
+ /**
71
+ * And the element directly under the pointer, if it bears a role of its own.
72
+ *
73
+ * On the target alone — `matches` and never `closest` — and that is the whole
74
+ * difference between a canvas that pans and one that only offers to. A
75
+ * `Preview` in its frame presentation is a `role="group"` and this canvas is a
76
+ * `role="region"`, so nearly every point on a board has a role-bearing
77
+ * ancestor. A board is mostly frames, and "drag to pan" that worked only in the
78
+ * gaps between them would be a hint that lies. So a role-bearing *ancestor* is
79
+ * a frame's chrome and a drag there is a pan; only a role-bearing element under
80
+ * the pointer itself is something to press rather than something to grab.
81
+ *
82
+ * That is still true, and it is not the whole rule: the ancestors that are
83
+ * *controls* rather than chrome are named in {@link CONTROLS} and matched with
84
+ * `closest` there. This line stays `matches` for everything else, which is what
85
+ * keeps a `role="group"` frame a thing to grab.
86
+ */
87
+ const ROLE_BEARING = "[role]";
88
+ /** Whether a pointer that went down here is pressing something rather than grabbing the board. */
89
+ function isControl(target, canvas) {
90
+ if (target === canvas) return false;
91
+ return target.closest(CONTROLS) !== null || target.matches(ROLE_BEARING);
92
+ }
93
+ /** Static class per height, off the two the spec measured. Tailwind scans text. */
94
+ const HEIGHT_CLASSES = {
95
+ short: "h-[18.5rem]",
96
+ tall: "h-[27rem]"
97
+ };
98
+ /**
99
+ * The box: the height, the ground, the border, and the positioning context the
100
+ * hint needs.
101
+ *
102
+ * Two elements and not one, because the hint has to stay in the corner while
103
+ * the frames move under it. A hint absolutely positioned inside the scroller
104
+ * would slide away with the first pan; one positioned against a box the
105
+ * scroller fills stays where a reader left it. The ground is on the box for the
106
+ * same reason — a board's grid does not move with its art.
107
+ */
108
+ const ROOT_CLASS = "relative min-w-0 overflow-hidden rounded-(--radius-surface) border border-border";
109
+ /**
110
+ * The scroller: a row of frames, sideways only, with a rung of the space
111
+ * ladder's top between them.
112
+ *
113
+ * `overflow-y: hidden` is the design and not an oversight — the board is "not
114
+ * very high, not full screen", and a canvas that scrolled both ways would be a
115
+ * viewport a reader can get lost in. `scroll-smooth` pairs with
116
+ * `motion-reduce:scroll-auto` so the preference is honoured declaratively;
117
+ * there is no frame loop here for `reduced-motion.test.ts` to find, and there
118
+ * must not be.
119
+ */
120
+ const CANVAS_CLASS = "flex h-full min-w-0 items-start gap-(--cue-space-8) overflow-x-auto overflow-y-hidden scroll-smooth p-(--cue-space-6) outline-none motion-reduce:scroll-auto focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-accent";
121
+ /** The affordance, and only where there is something to afford. */
122
+ const PANNABLE_CLASS = "cursor-grab";
123
+ /**
124
+ * While panning: the grabbing cursor, no text selection, and **no smooth
125
+ * scrolling**. `scroll-behavior: smooth` animates every `scrollLeft` write, and
126
+ * a drag writes one per pointer move — the canvas would lag the pointer by a
127
+ * whole animation. `twMerge` resolves this against `scroll-smooth` above,
128
+ * last one wins.
129
+ */
130
+ const PANNING_CLASS = "cursor-grabbing scroll-auto select-none";
131
+ /** The line in the corner. `pointer-events-none`, or it would swallow the drag it describes. */
132
+ const HINT_CLASS = "pointer-events-none absolute right-(--cue-space-4) bottom-(--cue-space-3) z-10 font-mono text-(length:--cue-text-micro) text-fg-subtle";
133
+ /**
134
+ * The canvas: a short, wide, focusable scroll region that holds frames.
135
+ *
136
+ * **It knows nothing about its children.** It does not count them, index them
137
+ * or select one — a canvas that knew its frames would be a manifest API, and
138
+ * this is a slot API. `hint` is where a caller puts the count it knows and this
139
+ * does not.
140
+ *
141
+ * @example
142
+ * <Frames.Root label="Button frames" hint="drag to pan · 8 frames">
143
+ * <Preview presentation="frame" label="Tone" count={4}>…</Preview>
144
+ * </Frames.Root>
145
+ */
146
+ const FramesRoot = React.forwardRef(function FramesRoot({ children, label, height = "short", ground = "dots", hint, pan = true, className, ...elementProps }, ref) {
147
+ const drag = React.useRef(null);
148
+ const canvasRef = React.useRef(null);
149
+ const [panning, setPanning] = React.useState(false);
150
+ React.useEffect(() => {
151
+ if (pan) return;
152
+ const started = drag.current;
153
+ if (started === null) return;
154
+ canvasRef.current?.releasePointerCapture?.(started.id);
155
+ drag.current = null;
156
+ setPanning(false);
157
+ }, [pan]);
158
+ const scroll = (event) => {
159
+ if (event.defaultPrevented || event.target !== event.currentTarget) return;
160
+ const canvas = event.currentTarget;
161
+ let next = null;
162
+ if (event.key === "ArrowRight") next = canvas.scrollLeft + rungOf(canvas);
163
+ else if (event.key === "ArrowLeft") next = canvas.scrollLeft - rungOf(canvas);
164
+ else if (event.key === "PageDown") next = canvas.scrollLeft + canvas.clientWidth;
165
+ else if (event.key === "PageUp") next = canvas.scrollLeft - canvas.clientWidth;
166
+ else if (event.key === "Home") next = 0;
167
+ else if (event.key === "End") next = canvas.scrollWidth;
168
+ if (next === null) return;
169
+ event.preventDefault();
170
+ canvas.scrollLeft = Math.max(0, next);
171
+ };
172
+ const pointer = {
173
+ onPointerDown: (event) => {
174
+ if (event.button !== 0) return;
175
+ const canvas = event.currentTarget;
176
+ const target = event.target;
177
+ if (target !== null && isControl(target, canvas)) return;
178
+ canvas.setPointerCapture?.(event.pointerId);
179
+ drag.current = {
180
+ x: event.clientX,
181
+ left: canvas.scrollLeft,
182
+ id: event.pointerId
183
+ };
184
+ setPanning(true);
185
+ },
186
+ onPointerMove: (event) => {
187
+ const started = drag.current;
188
+ if (started === null) return;
189
+ event.currentTarget.scrollLeft = Math.max(0, started.left - (event.clientX - started.x));
190
+ },
191
+ onPointerUp: (event) => {
192
+ if (drag.current === null) return;
193
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
194
+ drag.current = null;
195
+ setPanning(false);
196
+ },
197
+ onPointerCancel: () => {
198
+ drag.current = null;
199
+ setPanning(false);
200
+ }
201
+ };
202
+ return /* @__PURE__ */ jsxs("div", {
203
+ ...elementProps,
204
+ ref,
205
+ "data-slot": "frames",
206
+ className: cn(ROOT_CLASS, HEIGHT_CLASSES[height], GROUND_CLASSES[ground], className),
207
+ children: [/* @__PURE__ */ jsx("div", {
208
+ ref: canvasRef,
209
+ role: "region",
210
+ tabIndex: 0,
211
+ "aria-label": label,
212
+ "data-slot": "frames-canvas",
213
+ "data-panning": panning ? "true" : void 0,
214
+ className: cn(CANVAS_CLASS, pan && PANNABLE_CLASS, panning && PANNING_CLASS),
215
+ onKeyDown: scroll,
216
+ ...pan ? pointer : {},
217
+ children
218
+ }), hint === void 0 ? null : /* @__PURE__ */ jsx("span", {
219
+ "data-slot": "frames-hint",
220
+ className: HINT_CLASS,
221
+ children: hint
222
+ })]
223
+ });
224
+ });
225
+ /** One cell: the specimen, and the line under it. */
226
+ const ITEM_CLASS = "flex min-w-0 shrink-0 flex-col items-center gap-(--cue-space-2) rounded-(--radius-control) p-(--cue-space-2)";
227
+ /** The one a reader is inspecting. An outline and not a border: a border would move the cell. */
228
+ const ITEM_SELECTED_CLASS = "outline-2 outline-solid -outline-offset-1 outline-accent";
229
+ const ITEM_BODY_CLASS = "flex min-w-0 items-center justify-center";
230
+ /**
231
+ * The caption, and the two properties that decide whether it is painted.
232
+ *
233
+ * Properties and not props threaded down from the frame: the switch belongs to
234
+ * the frame — a reader turns captions off for a whole frame from its settings
235
+ * popover, never for one item — and a property is how a switch reaches every
236
+ * descendant without every descendant declaring it.
237
+ *
238
+ * **`opacity`, never `visibility`.** Zero opacity keeps the box *and* keeps the
239
+ * node in the accessibility tree, which is exactly what §4.1 Decision 4
240
+ * refuses to give up; `visibility: hidden` would keep the box and drop the
241
+ * node. `pointer-events` rides with it so an unpainted caption cannot be
242
+ * clicked or dragged over — two properties because `opacity` wants a number
243
+ * and `pointer-events` wants a keyword, and a custom property is untyped text
244
+ * that cannot be both.
245
+ *
246
+ * The **fallbacks** are what make an item legal outside a Preview: nothing
247
+ * declares either property there, so the caption resolves to `1` and `auto` and
248
+ * behaves like any other line, rather than inheriting the empty string and
249
+ * vanishing.
250
+ */
251
+ const ITEM_CAPTION_CLASS = "min-w-0 truncate text-center font-mono text-(length:--cue-text-micro) text-fg-subtle [opacity:var(--preview-captions,1)] [pointer-events:var(--preview-captions-pe,auto)]";
252
+ /**
253
+ * A design canvas: frames side by side, each holding one axis of one component.
254
+ *
255
+ * `Frames.Root` is the canvas — short, wide, scrollable sideways, focusable,
256
+ * and pannable with the pointer. `Frames.Item` is one captioned cell, and it is
257
+ * used **inside a `Preview`** rather than inside the Root. The two are apart
258
+ * because that is how they are used, and a props-only API cannot say it.
259
+ *
260
+ * **The canvas knows nothing about its children.** It does not count them, does
261
+ * not index them and does not select one; `hint` is where a caller puts what it
262
+ * knows. A canvas that knew its frames would be a manifest API, and this is a
263
+ * slot API.
264
+ *
265
+ * @example
266
+ * <Frames.Root label="Button frames" hint="drag to pan · 3 frames">
267
+ * <Preview presentation="frame" label="Tone" count={3} name={<ToneMenu />}>
268
+ * <div className="flex flex-wrap items-end gap-(--cue-space-6)">
269
+ * <Frames.Item caption="accent"><Button tone="accent">Take cue</Button></Frames.Item>
270
+ * <Frames.Item caption="ok"><Button tone="ok">Take cue</Button></Frames.Item>
271
+ * <Frames.Item caption="warn"><Button tone="warn">Take cue</Button></Frames.Item>
272
+ * </div>
273
+ * </Preview>
274
+ * </Frames.Root>
275
+ */
276
+ const Frames = {
277
+ Root: FramesRoot,
278
+ Item: React.forwardRef(function FramesItem({ children, caption, selected = false, className, ...elementProps }, ref) {
279
+ return /* @__PURE__ */ jsxs("div", {
280
+ ...elementProps,
281
+ ref,
282
+ "data-slot": "frames-item",
283
+ "data-selected": selected ? "true" : void 0,
284
+ className: cn(ITEM_CLASS, selected && ITEM_SELECTED_CLASS, className),
285
+ children: [/* @__PURE__ */ jsx("div", {
286
+ "data-slot": "frames-item-body",
287
+ className: ITEM_BODY_CLASS,
288
+ children
289
+ }), caption === void 0 ? null : /* @__PURE__ */ jsx("span", {
290
+ "data-slot": "frames-item-caption",
291
+ className: ITEM_CAPTION_CLASS,
292
+ children: caption
293
+ })]
294
+ });
295
+ })
296
+ };
297
+ //#endregion
298
+ export { Frames };
@@ -8,10 +8,12 @@ import { Breadcrumb, BreadcrumbItem, BreadcrumbProps } from "./breadcrumb.js";
8
8
  import { Card, CardContentProps, CardDescriptionProps, CardFooterProps, CardHeaderProps, CardRootProps, CardTitleProps } from "./card.js";
9
9
  import { Collapsible, CollapsiblePanelProps, CollapsibleRootProps, CollapsibleTriggerProps, Disclosure, DisclosureProps } from "./collapsible.js";
10
10
  import { DescriptionList, DescriptionListDetailProps, DescriptionListLayout, DescriptionListRootProps, DescriptionListTermProps } from "./description-list.js";
11
+ import { PreviewGround } from "./_ground.js";
12
+ import { Frames, FramesHeight, FramesItemProps, FramesRootProps } from "./frames.js";
11
13
  import { Grid, GridColumns, GridProps } from "./grid.js";
12
14
  import { Item, ItemProps, itemVariants } from "./item.js";
13
15
  import { Pagination, PaginationEntry, PaginationProps, paginationRange } from "./pagination.js";
14
- import { Preview, PreviewAxis, PreviewProps, PreviewResizeOptions } from "./preview.js";
16
+ import { Preview, PreviewAxis, PreviewPresentation, PreviewProps, PreviewResizeOptions } from "./preview.js";
15
17
  import { Stepper, StepperProps, StepperState, StepperStep } from "./stepper.js";
16
18
  import { Timeline, TimelineContentProps, TimelineItemProps, TimelineMarkerProps, TimelineRootProps } from "./timeline.js";
17
- export { Accordion, type AccordionHeaderProps, type AccordionItemProps, type AccordionPanelProps, type AccordionRootProps, type AccordionTriggerProps, Bento, type BentoColumns, type BentoMinHeight, type BentoRootProps, type BentoRowSpan, type BentoRows, type BentoSpan, type BentoTileProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Card, type CardContentProps, type CardDescriptionProps, type CardFooterProps, type CardHeaderProps, type CardRootProps, type CardTitleProps, Collapsible, type CollapsiblePanelProps, type CollapsibleRootProps, type CollapsibleTriggerProps, Container, type ContainerProps, type ContainerWidth, DescriptionList, type DescriptionListDetailProps, type DescriptionListLayout, type DescriptionListRootProps, type DescriptionListTermProps, Disclosure, type DisclosureProps, Grid, type GridColumns, type GridProps, Item, type ItemProps, Link, type LinkProps, Pagination, type PaginationEntry, type PaginationProps, Preview, type PreviewAxis, type PreviewProps, type PreviewResizeOptions, Sidebar, type SidebarItemProps, type SidebarRailProps, type SidebarRootProps, type SidebarSectionProps, type SpaceStep, Stack, type StackAlign, type StackJustify, type StackProps, Stepper, type StepperProps, type StepperState, type StepperStep, Timeline, type TimelineContentProps, type TimelineItemProps, type TimelineMarkerProps, type TimelineRootProps, itemVariants, linkVariants, paginationRange, useSidebar };
19
+ export { Accordion, type AccordionHeaderProps, type AccordionItemProps, type AccordionPanelProps, type AccordionRootProps, type AccordionTriggerProps, Bento, type BentoColumns, type BentoMinHeight, type BentoRootProps, type BentoRowSpan, type BentoRows, type BentoSpan, type BentoTileProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Card, type CardContentProps, type CardDescriptionProps, type CardFooterProps, type CardHeaderProps, type CardRootProps, type CardTitleProps, Collapsible, type CollapsiblePanelProps, type CollapsibleRootProps, type CollapsibleTriggerProps, Container, type ContainerProps, type ContainerWidth, DescriptionList, type DescriptionListDetailProps, type DescriptionListLayout, type DescriptionListRootProps, type DescriptionListTermProps, Disclosure, type DisclosureProps, Frames, type FramesHeight, type FramesItemProps, type FramesRootProps, Grid, type GridColumns, type GridProps, Item, type ItemProps, Link, type LinkProps, Pagination, type PaginationEntry, type PaginationProps, Preview, type PreviewAxis, type PreviewGround, type PreviewPresentation, type PreviewProps, type PreviewResizeOptions, Sidebar, type SidebarItemProps, type SidebarRailProps, type SidebarRootProps, type SidebarSectionProps, type SpaceStep, Stack, type StackAlign, type StackJustify, type StackProps, Stepper, type StepperProps, type StepperState, type StepperStep, Timeline, type TimelineContentProps, type TimelineItemProps, type TimelineMarkerProps, type TimelineRootProps, itemVariants, linkVariants, paginationRange, useSidebar };
@@ -8,10 +8,11 @@ import { Breadcrumb } from "./breadcrumb.js";
8
8
  import { Card } from "./card.js";
9
9
  import { Collapsible, Disclosure } from "./collapsible.js";
10
10
  import { DescriptionList } from "./description-list.js";
11
+ import { Frames } from "./frames.js";
11
12
  import { Grid } from "./grid.js";
12
13
  import { Item, itemVariants } from "./item.js";
13
14
  import { Pagination, paginationRange } from "./pagination.js";
14
15
  import { Preview } from "./preview.js";
15
16
  import { Stepper } from "./stepper.js";
16
17
  import { Timeline } from "./timeline.js";
17
- export { Accordion, Bento, Breadcrumb, Card, Collapsible, Container, DescriptionList, Disclosure, Grid, Item, Link, Pagination, Preview, Sidebar, Stack, Stepper, Timeline, itemVariants, linkVariants, paginationRange, useSidebar };
18
+ export { Accordion, Bento, Breadcrumb, Card, Collapsible, Container, DescriptionList, Disclosure, Frames, Grid, Item, Link, Pagination, Preview, Sidebar, Stack, Stepper, Timeline, itemVariants, linkVariants, paginationRange, useSidebar };
@@ -1,6 +1,9 @@
1
+ import { PreviewGround } from "./_ground.js";
1
2
  import * as React from "react";
2
3
  import { Density, FontName, Mode, ThemeName } from "@cueplusplus/theme-base";
3
4
  //#region src/layout/preview.d.ts
5
+ /** Where the chrome sits and what the stage's ground is. */
6
+ type PreviewPresentation = "panel" | "frame";
4
7
  /** Which edges of the stage a reader may drag. */
5
8
  type PreviewAxis = "x" | "y" | "both";
6
9
  /** How far a resizable stage may go, in CSS pixels, and along which axes. */
@@ -74,11 +77,51 @@ interface PreviewProps extends Omit<React.ComponentPropsWithoutRef<"div">, "chil
74
77
  tools?: React.ReactNode;
75
78
  /** The strip under the stage: the thin text and the way out. Drawn outside the stage. */
76
79
  footer?: React.ReactNode;
80
+ /**
81
+ * `"panel"` (the default, and what every existing caller gets): a bordered
82
+ * card, `tools` floating at the stage's top right, `footer` under it.
83
+ * `"frame"`: a design-canvas frame — the label row drawn *above* the stage
84
+ * rather than over it, four crosshair corners at the stage's outer corners,
85
+ * and a transparent stage so whatever the frame sits on shows through.
86
+ */
87
+ presentation?: PreviewPresentation;
88
+ /**
89
+ * The label row's leading slot, drawn only in `"frame"`. A plain node, or a
90
+ * control: apps/docs passes a `DropdownMenu` whose trigger is the frame's
91
+ * name. Unset, the row opens with `label` as text.
92
+ *
93
+ * A slot and not a built-in menu, because `Preview` imports no overlay today
94
+ * and `/` draws seventeen of it under a 766-byte headroom.
95
+ */
96
+ name?: React.ReactNode;
97
+ /** The count drawn after the name — how many items this frame holds. Frame only. */
98
+ count?: number;
99
+ /**
100
+ * The stage's ground. Defaults to `"dots"` in `"panel"` and `"none"` in
101
+ * `"frame"`, where the canvas under it already carries a ground and two dot
102
+ * grids at different pitches read as a moiré.
103
+ */
104
+ ground?: PreviewGround;
105
+ /**
106
+ * Whether `Frames.Item` captions are shown inside this stage. Defaults to
107
+ * `true`. Off is paint and nothing more: a hidden caption is still rendered,
108
+ * still in the tree and still read aloud (§4.1 Decision 4).
109
+ */
110
+ captions?: boolean;
77
111
  }
78
112
  /**
79
- * A frame for one component: a stage that is its own theme island, a slot for
113
+ * A card for one component: a stage that is its own theme island, a slot for
80
114
  * tools at the top right, and a footer.
81
115
  *
116
+ * **Two presentations, one stage.** `"panel"`, the default and what every
117
+ * existing caller gets, is that card: a border, a fill, a dot grid under the
118
+ * specimen, and the tools floating over the stage's top right. `"frame"` is a
119
+ * design-canvas frame — no card, no border, no fill; the name, the count and
120
+ * the tools sit in a row *above* the stage rather than over it, four crosshairs
121
+ * mark the stage's outer corners, and the stage paints nothing by default, so
122
+ * whatever canvas the frame sits on shows through. Everything below holds in
123
+ * both.
124
+ *
82
125
  * **The stage is the island; the chrome is not.** `theme`, `mode`, `density`
83
126
  * and `font` land on the stage element only, and the stage republishes the
84
127
  * theme and density contexts so an overlay opened on it follows it. The tools,
@@ -94,7 +137,7 @@ interface PreviewProps extends Omit<React.ComponentPropsWithoutRef<"div">, "chil
94
137
  * that is under `maxWidth`. Not `Resizable`, which is split panes on an
95
138
  * optional peer.
96
139
  *
97
- * Props, not a compound: one stage and two slots, each saying what it is.
140
+ * Props, not a compound: one stage and its slots, each saying what it is.
98
141
  *
99
142
  * @example
100
143
  * <Preview label="Button preview" tools={<IconButton aria-label="Configure Button" icon={Wrench} />}>
@@ -104,7 +147,11 @@ interface PreviewProps extends Omit<React.ComponentPropsWithoutRef<"div">, "chil
104
147
  * <Preview label="Chip preview" theme="terminal" density="compact" resizable>
105
148
  * <Chip tone="accent">Standby</Chip>
106
149
  * </Preview>
150
+ * @example
151
+ * <Preview label="Button" presentation="frame" count={3}>
152
+ * <Button variant="primary">Take cue</Button>
153
+ * </Preview>
107
154
  */
108
155
  declare const Preview: React.ForwardRefExoticComponent<PreviewProps & React.RefAttributes<HTMLDivElement>>;
109
156
  //#endregion
110
- export { Preview, PreviewAxis, PreviewProps, PreviewResizeOptions };
157
+ export { Preview, PreviewAxis, type PreviewGround, PreviewPresentation, PreviewProps, PreviewResizeOptions };