@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.
- package/CHANGELOG.md +198 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/layout/_ground.d.ts +22 -0
- package/dist/layout/_ground.js +9 -0
- package/dist/layout/bento.js +1 -1
- package/dist/layout/frames.d.ts +70 -0
- package/dist/layout/frames.js +298 -0
- package/dist/layout/index.d.ts +4 -2
- package/dist/layout/index.js +2 -1
- package/dist/layout/preview.d.ts +50 -3
- package/dist/layout/preview.js +179 -31
- package/dist/overlays/drawer.js +1 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/manifest/components/bento.json +1 -1
- package/manifest/components/frames.json +210 -0
- package/manifest/components/preview.json +85 -8
- package/manifest/manifest.json +18 -5
- package/manifest/tokens.json +1 -1
- package/package.json +5 -5
package/dist/layout/preview.js
CHANGED
|
@@ -4,6 +4,7 @@ import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effec
|
|
|
4
4
|
import { AmbientThemeContext, DensityContext } from "../system/density.js";
|
|
5
5
|
import { OverridesContext } from "../system/overrides.js";
|
|
6
6
|
import { ThemeContext } from "../system/theme-provider.js";
|
|
7
|
+
import { GROUND_CLASSES } from "./_ground.js";
|
|
7
8
|
import * as React from "react";
|
|
8
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
//#region src/layout/preview.tsx
|
|
@@ -20,16 +21,29 @@ const BIG_STEP = 64;
|
|
|
20
21
|
const ROOT_CLASS = "relative flex min-w-0 flex-col rounded-(--radius-surface) border border-border bg-surface-1 text-fg";
|
|
21
22
|
const BENCH_CLASS = "relative flex min-w-0 flex-1 overflow-auto rounded-t-(--radius-surface)";
|
|
22
23
|
/**
|
|
23
|
-
* The stage: centred,
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The stage: centred, grounded, and at least three top rungs of the space
|
|
25
|
+
* ladder tall so a preview reserves its room before its specimen arrives.
|
|
26
|
+
* Static literals, because Tailwind scans text.
|
|
26
27
|
*
|
|
27
28
|
* Centred **safely** (`place-items: safe center`): a specimen wider than the
|
|
28
29
|
* stage starts at the stage's edge and overflows at its end, where the bench
|
|
29
30
|
* scrolls to it. Plain `center` split the overflow across both sides, and the
|
|
30
31
|
* left half sat before the scroll origin, where nothing reaches it.
|
|
32
|
+
*
|
|
33
|
+
* **Three pieces rather than one literal, and the seam is where the ground
|
|
34
|
+
* goes.** `cn` preserves the order it is given, so a ground appended after the
|
|
35
|
+
* padding would emit the dot grid in a different place from the one every
|
|
36
|
+
* caller's markup has today — and `/`'s seventeen bare Previews and the group
|
|
37
|
+
* pages' 325 tooled ones are both measured to the byte. Head, ground, tail, in
|
|
38
|
+
* that order, is today's string.
|
|
31
39
|
*/
|
|
32
|
-
const
|
|
40
|
+
const STAGE_HEAD = "grid h-full w-full min-h-[calc(var(--cue-space-8)*3)] place-items-center-safe";
|
|
41
|
+
const STAGE_TAIL = "p-(--cue-space-6) text-fg";
|
|
42
|
+
/** Which ground a presentation paints when the caller names none (spec §4.1). */
|
|
43
|
+
const DEFAULT_GROUND = {
|
|
44
|
+
panel: "dots",
|
|
45
|
+
frame: "none"
|
|
46
|
+
};
|
|
33
47
|
const TOOLS_CLASS = "absolute top-(--cue-space-2) right-(--cue-space-2) z-10 flex items-center gap-px rounded-(--radius-control) border border-border bg-surface-2 p-px";
|
|
34
48
|
/**
|
|
35
49
|
* The room the tools take, kept clear on the stage — only when there are tools.
|
|
@@ -52,6 +66,79 @@ const TOOLS_CLASS = "absolute top-(--cue-space-2) right-(--cue-space-2) z-10 fle
|
|
|
52
66
|
const TOOLS_RESERVE_BENCH = "[--preview-tools-clear:calc(var(--cue-space-2)*2+var(--cue-control-sm)+4px)]";
|
|
53
67
|
const TOOLS_RESERVE_STAGE = "pt-(--preview-tools-clear)";
|
|
54
68
|
const FOOTER_CLASS = "flex min-w-0 items-center gap-(--cue-space-2) border-t border-border px-(--cue-space-4) py-(--cue-space-2)";
|
|
69
|
+
/**
|
|
70
|
+
* A frame's root: no card, no border, no fill. The frame is a thing on a
|
|
71
|
+
* canvas, and the canvas is what paints. `shrink-0` because a canvas is a flex
|
|
72
|
+
* row that scrolls, and a frame that shrank to fit would defeat the scrolling.
|
|
73
|
+
*/
|
|
74
|
+
const FRAME_ROOT_CLASS = "relative flex min-w-0 shrink-0 flex-col gap-(--cue-space-2) text-fg";
|
|
75
|
+
/**
|
|
76
|
+
* A frame's footer: the same row as a panel's, without the rule across the top
|
|
77
|
+
* or the card's horizontal padding — there is no card for either to belong to.
|
|
78
|
+
*/
|
|
79
|
+
const FRAME_FOOTER_CLASS = "flex min-w-0 items-center gap-(--cue-space-2) text-(length:--cue-text-micro) text-fg-subtle";
|
|
80
|
+
/** The row above a frame's stage: the name (or its control), the count, and the tools. */
|
|
81
|
+
const FRAME_LABEL_CLASS = "flex min-w-0 items-center gap-(--cue-space-2) text-(length:--cue-text-label) text-fg-muted";
|
|
82
|
+
/** The name when the caller passed no control for it — the frame's `label`, as text. */
|
|
83
|
+
const FRAME_NAME_CLASS = "min-w-0 truncate font-mono text-(length:--cue-text-label) text-fg";
|
|
84
|
+
/** The count beside it: quieter, and never allowed to shrink the name's truncation away. */
|
|
85
|
+
const FRAME_COUNT_CLASS = "shrink-0 font-mono text-(length:--cue-text-micro) text-fg-subtle";
|
|
86
|
+
/**
|
|
87
|
+
* A frame's tools: pushed to the row's end, and carrying none of the panel's
|
|
88
|
+
* overlay chrome. A frame's stage is transparent, so a toolbar drawn over it
|
|
89
|
+
* would float on the canvas ground with nothing behind it (spec §4.1 Decision 2).
|
|
90
|
+
*/
|
|
91
|
+
const FRAME_TOOLS_CLASS = "ml-auto flex shrink-0 items-center gap-px";
|
|
92
|
+
/**
|
|
93
|
+
* The box that holds the bench and the four marks.
|
|
94
|
+
*
|
|
95
|
+
* One more element than the spec's Decision 3 priced, and it is not avoidable.
|
|
96
|
+
* The bench is `overflow-auto`: a mark positioned inside it is clipped at the
|
|
97
|
+
* padding box and scrolls with the content. The frame's root is no good either
|
|
98
|
+
* — its first child is the label row, so the root's top-left corner is the
|
|
99
|
+
* label's and not the stage's. So the frame gets one `relative` box whose
|
|
100
|
+
* borders are exactly the stage's, and the marks hang off that.
|
|
101
|
+
*/
|
|
102
|
+
const FRAME_BOX_CLASS = "relative flex min-w-0 flex-col";
|
|
103
|
+
/**
|
|
104
|
+
* Captions off, as properties the item reads rather than a prop each item takes.
|
|
105
|
+
*
|
|
106
|
+
* A reader turns captions off for a whole frame from its settings popover,
|
|
107
|
+
* never for one item — so the switch is on the frame and the item obeys it
|
|
108
|
+
* through inheritance.
|
|
109
|
+
*
|
|
110
|
+
* **`opacity: 0`, and deliberately not `visibility: hidden`.** §4.1 Decision 4
|
|
111
|
+
* refuses "removing a hidden caption from the accessibility tree", and
|
|
112
|
+
* `visibility: hidden` does exactly that — a browser drops such a node as
|
|
113
|
+
* surely as it drops a `display: none` one. Zero opacity keeps the box, so
|
|
114
|
+
* nothing re-flows, *and* keeps the caption announced, which is what the
|
|
115
|
+
* refusal is for: the caption is what names the specimen for a reader who
|
|
116
|
+
* cannot see the difference between two of them.
|
|
117
|
+
*
|
|
118
|
+
* **Two properties, declared together here so the pair is never half-applied.**
|
|
119
|
+
* Custom properties are untyped text: `opacity` wants a number and
|
|
120
|
+
* `pointer-events` wants a keyword, so one property cannot carry both. The
|
|
121
|
+
* second is what stops an unpainted caption being clicked, or a drag across the
|
|
122
|
+
* stage selecting words nobody can see.
|
|
123
|
+
*/
|
|
124
|
+
const CAPTIONS_OFF = "[--preview-captions:0] [--preview-captions-pe:none]";
|
|
125
|
+
/**
|
|
126
|
+
* One corner mark: an 11×11 box with a one-pixel cross drawn as two linear
|
|
127
|
+
* gradients — a vertical hairline and a horizontal one — so the mark is a
|
|
128
|
+
* background and not four more elements.
|
|
129
|
+
*
|
|
130
|
+
* Not a border and not an SVG. A border would have to be on the stage, and the
|
|
131
|
+
* stage is the theme island: a frame's chrome that moved with a stage theme is
|
|
132
|
+
* the one thing ruling 7 forbids.
|
|
133
|
+
*/
|
|
134
|
+
const CROSS_CLASS = "pointer-events-none absolute z-10 size-[11px] bg-[linear-gradient(var(--cue-fg-subtle),var(--cue-fg-subtle)),linear-gradient(var(--cue-fg-subtle),var(--cue-fg-subtle))] bg-[length:1px_11px,11px_1px] bg-[position:center,center] bg-no-repeat";
|
|
135
|
+
/** The four corners, each half outside the box so the cross straddles the corner. */
|
|
136
|
+
const CROSS_CORNERS = [
|
|
137
|
+
"-top-[5px] -left-[5px]",
|
|
138
|
+
"-top-[5px] -right-[5px]",
|
|
139
|
+
"-bottom-[5px] -left-[5px]",
|
|
140
|
+
"-bottom-[5px] -right-[5px]"
|
|
141
|
+
];
|
|
55
142
|
const GRIP_FOCUS = "touch-none outline-none hover:bg-(--cue-border-strong) focus-visible:bg-accent";
|
|
56
143
|
const GRIP_X_CLASS = `absolute top-0 right-0 z-10 h-full w-(--cue-space-2) cursor-col-resize ${GRIP_FOCUS}`;
|
|
57
144
|
const GRIP_Y_CLASS = `absolute bottom-0 left-0 z-10 h-(--cue-space-2) w-full cursor-row-resize ${GRIP_FOCUS}`;
|
|
@@ -357,9 +444,18 @@ function Grips({ label, options, size }) {
|
|
|
357
444
|
] });
|
|
358
445
|
}
|
|
359
446
|
/**
|
|
360
|
-
* A
|
|
447
|
+
* A card for one component: a stage that is its own theme island, a slot for
|
|
361
448
|
* tools at the top right, and a footer.
|
|
362
449
|
*
|
|
450
|
+
* **Two presentations, one stage.** `"panel"`, the default and what every
|
|
451
|
+
* existing caller gets, is that card: a border, a fill, a dot grid under the
|
|
452
|
+
* specimen, and the tools floating over the stage's top right. `"frame"` is a
|
|
453
|
+
* design-canvas frame — no card, no border, no fill; the name, the count and
|
|
454
|
+
* the tools sit in a row *above* the stage rather than over it, four crosshairs
|
|
455
|
+
* mark the stage's outer corners, and the stage paints nothing by default, so
|
|
456
|
+
* whatever canvas the frame sits on shows through. Everything below holds in
|
|
457
|
+
* both.
|
|
458
|
+
*
|
|
363
459
|
* **The stage is the island; the chrome is not.** `theme`, `mode`, `density`
|
|
364
460
|
* and `font` land on the stage element only, and the stage republishes the
|
|
365
461
|
* theme and density contexts so an overlay opened on it follows it. The tools,
|
|
@@ -375,7 +471,7 @@ function Grips({ label, options, size }) {
|
|
|
375
471
|
* that is under `maxWidth`. Not `Resizable`, which is split panes on an
|
|
376
472
|
* optional peer.
|
|
377
473
|
*
|
|
378
|
-
* Props, not a compound: one stage and
|
|
474
|
+
* Props, not a compound: one stage and its slots, each saying what it is.
|
|
379
475
|
*
|
|
380
476
|
* @example
|
|
381
477
|
* <Preview label="Button preview" tools={<IconButton aria-label="Configure Button" icon={Wrench} />}>
|
|
@@ -385,8 +481,12 @@ function Grips({ label, options, size }) {
|
|
|
385
481
|
* <Preview label="Chip preview" theme="terminal" density="compact" resizable>
|
|
386
482
|
* <Chip tone="accent">Standby</Chip>
|
|
387
483
|
* </Preview>
|
|
484
|
+
* @example
|
|
485
|
+
* <Preview label="Button" presentation="frame" count={3}>
|
|
486
|
+
* <Button variant="primary">Take cue</Button>
|
|
487
|
+
* </Preview>
|
|
388
488
|
*/
|
|
389
|
-
const Preview = React.forwardRef(function Preview({ children, label, theme, mode, density, font, resizable, tools, footer, className, ...elementProps }, ref) {
|
|
489
|
+
const Preview = React.forwardRef(function Preview({ children, label, theme, mode, density, font, resizable, tools, footer, presentation = "panel", name, count, ground, captions = true, className, ...elementProps }, ref) {
|
|
390
490
|
const outer = React.useContext(ThemeContext);
|
|
391
491
|
const bench = React.useRef(null);
|
|
392
492
|
const given = resolveResize(resizable);
|
|
@@ -399,10 +499,11 @@ const Preview = React.forwardRef(function Preview({ children, label, theme, mode
|
|
|
399
499
|
density,
|
|
400
500
|
font
|
|
401
501
|
}, outer);
|
|
502
|
+
const framed = presentation === "frame";
|
|
402
503
|
const stage = /* @__PURE__ */ jsx("div", {
|
|
403
504
|
"data-slot": "preview-stage",
|
|
404
505
|
...attributes,
|
|
405
|
-
className: cn(
|
|
506
|
+
className: cn(STAGE_HEAD, GROUND_CLASSES[ground ?? DEFAULT_GROUND[presentation]], STAGE_TAIL, options !== null && "flex-1", !framed && tools !== void 0 && TOOLS_RESERVE_STAGE, !captions && CAPTIONS_OFF),
|
|
406
507
|
style: size.height === null ? void 0 : { minHeight: 0 },
|
|
407
508
|
children: /* @__PURE__ */ jsx(StageScope, {
|
|
408
509
|
theme,
|
|
@@ -411,6 +512,75 @@ const Preview = React.forwardRef(function Preview({ children, label, theme, mode
|
|
|
411
512
|
children
|
|
412
513
|
})
|
|
413
514
|
});
|
|
515
|
+
const benched = /* @__PURE__ */ jsx("div", {
|
|
516
|
+
ref: bench,
|
|
517
|
+
"data-slot": "preview-bench",
|
|
518
|
+
className: cn(BENCH_CLASS, footer === void 0 && "rounded-b-(--radius-surface)", !framed && tools !== void 0 && TOOLS_RESERVE_BENCH),
|
|
519
|
+
children: options === null ? stage : /* @__PURE__ */ jsxs("div", {
|
|
520
|
+
ref: size.sizer,
|
|
521
|
+
"data-slot": "preview-sizer",
|
|
522
|
+
className: cn("relative flex flex-col", size.width === null && "w-full"),
|
|
523
|
+
style: {
|
|
524
|
+
width: size.width === null ? void 0 : `${size.shownWidth}px`,
|
|
525
|
+
height: size.height === null ? void 0 : `${size.shownHeight}px`,
|
|
526
|
+
minWidth: `${options.minWidth}px`,
|
|
527
|
+
maxWidth: `${options.maxWidth}px`,
|
|
528
|
+
minHeight: `${options.minHeight}px`,
|
|
529
|
+
maxHeight: `${options.maxHeight}px`
|
|
530
|
+
},
|
|
531
|
+
children: [stage, /* @__PURE__ */ jsx(Grips, {
|
|
532
|
+
label,
|
|
533
|
+
options,
|
|
534
|
+
size
|
|
535
|
+
})]
|
|
536
|
+
})
|
|
537
|
+
});
|
|
538
|
+
if (framed) return /* @__PURE__ */ jsxs("div", {
|
|
539
|
+
...elementProps,
|
|
540
|
+
ref,
|
|
541
|
+
role: "group",
|
|
542
|
+
"aria-label": label,
|
|
543
|
+
"data-slot": "preview",
|
|
544
|
+
"data-presentation": "frame",
|
|
545
|
+
className: cn(FRAME_ROOT_CLASS, options !== null && "w-full", className),
|
|
546
|
+
children: [
|
|
547
|
+
/* @__PURE__ */ jsxs("div", {
|
|
548
|
+
"data-slot": "preview-frame-label",
|
|
549
|
+
className: FRAME_LABEL_CLASS,
|
|
550
|
+
children: [
|
|
551
|
+
name === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
552
|
+
"data-slot": "preview-name",
|
|
553
|
+
className: FRAME_NAME_CLASS,
|
|
554
|
+
children: label
|
|
555
|
+
}) : name,
|
|
556
|
+
count === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
557
|
+
"data-slot": "preview-count",
|
|
558
|
+
className: FRAME_COUNT_CLASS,
|
|
559
|
+
children: count
|
|
560
|
+
}),
|
|
561
|
+
tools === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
562
|
+
"data-slot": "preview-tools",
|
|
563
|
+
className: FRAME_TOOLS_CLASS,
|
|
564
|
+
children: tools
|
|
565
|
+
})
|
|
566
|
+
]
|
|
567
|
+
}),
|
|
568
|
+
/* @__PURE__ */ jsxs("div", {
|
|
569
|
+
"data-slot": "preview-frame",
|
|
570
|
+
className: FRAME_BOX_CLASS,
|
|
571
|
+
children: [benched, CROSS_CORNERS.map((corner) => /* @__PURE__ */ jsx("span", {
|
|
572
|
+
"aria-hidden": "true",
|
|
573
|
+
"data-slot": "preview-crosshair",
|
|
574
|
+
className: cn(CROSS_CLASS, corner)
|
|
575
|
+
}, corner))]
|
|
576
|
+
}),
|
|
577
|
+
footer === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
578
|
+
"data-slot": "preview-footer",
|
|
579
|
+
className: FRAME_FOOTER_CLASS,
|
|
580
|
+
children: footer
|
|
581
|
+
})
|
|
582
|
+
]
|
|
583
|
+
});
|
|
414
584
|
return /* @__PURE__ */ jsxs("div", {
|
|
415
585
|
...elementProps,
|
|
416
586
|
ref,
|
|
@@ -419,29 +589,7 @@ const Preview = React.forwardRef(function Preview({ children, label, theme, mode
|
|
|
419
589
|
"data-slot": "preview",
|
|
420
590
|
className: cn(ROOT_CLASS, options !== null && "w-full", className),
|
|
421
591
|
children: [
|
|
422
|
-
|
|
423
|
-
ref: bench,
|
|
424
|
-
"data-slot": "preview-bench",
|
|
425
|
-
className: cn(BENCH_CLASS, footer === void 0 && "rounded-b-(--radius-surface)", tools !== void 0 && TOOLS_RESERVE_BENCH),
|
|
426
|
-
children: options === null ? stage : /* @__PURE__ */ jsxs("div", {
|
|
427
|
-
ref: size.sizer,
|
|
428
|
-
"data-slot": "preview-sizer",
|
|
429
|
-
className: cn("relative flex flex-col", size.width === null && "w-full"),
|
|
430
|
-
style: {
|
|
431
|
-
width: size.width === null ? void 0 : `${size.shownWidth}px`,
|
|
432
|
-
height: size.height === null ? void 0 : `${size.shownHeight}px`,
|
|
433
|
-
minWidth: `${options.minWidth}px`,
|
|
434
|
-
maxWidth: `${options.maxWidth}px`,
|
|
435
|
-
minHeight: `${options.minHeight}px`,
|
|
436
|
-
maxHeight: `${options.maxHeight}px`
|
|
437
|
-
},
|
|
438
|
-
children: [stage, /* @__PURE__ */ jsx(Grips, {
|
|
439
|
-
label,
|
|
440
|
-
options,
|
|
441
|
-
size
|
|
442
|
-
})]
|
|
443
|
-
})
|
|
444
|
-
}),
|
|
592
|
+
benched,
|
|
445
593
|
tools === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
446
594
|
"data-slot": "preview-tools",
|
|
447
595
|
className: TOOLS_CLASS,
|
package/dist/overlays/drawer.js
CHANGED
|
@@ -104,7 +104,7 @@ const Drawer$1 = {
|
|
|
104
104
|
children: [grabHandle ? /* @__PURE__ */ jsx("div", {
|
|
105
105
|
"data-slot": "drawer-grab-handle",
|
|
106
106
|
"aria-hidden": "true",
|
|
107
|
-
className: cn("shrink-0 rounded-
|
|
107
|
+
className: cn("shrink-0 rounded-full bg-border-strong", vertical ? "mx-auto my-(--cue-space-2) h-1 w-9" : "my-auto mx-(--cue-space-2) h-9 w-1")
|
|
108
108
|
}) : null, /* @__PURE__ */ jsx(Drawer.Content, {
|
|
109
109
|
"data-slot": "drawer-content",
|
|
110
110
|
className: "flex min-h-0 flex-1 flex-col gap-(--cue-space-4) p-(--cue-space-6)",
|
|
@@ -30,7 +30,7 @@ import * as React from "react";
|
|
|
30
30
|
* <span className={chipVariants({ variant: "tag" })}>mcp</span>
|
|
31
31
|
*/
|
|
32
32
|
declare const chipVariants: (props?: ({
|
|
33
|
-
tone?: "
|
|
33
|
+
tone?: "danger" | "info" | "warn" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
|
|
34
34
|
variant?: "outline" | "tinted" | "tag" | null | undefined;
|
|
35
35
|
interactive?: boolean | null | undefined;
|
|
36
36
|
} & ClassProp) | undefined) => string;
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
"--cue-surface-1",
|
|
255
255
|
"--cue-surface-2",
|
|
256
256
|
"--cue-text-emphasis",
|
|
257
|
-
"--cue-text-
|
|
257
|
+
"--cue-text-micro"
|
|
258
258
|
],
|
|
259
259
|
"summary": "The unequal grid: tiles of different sizes on one set of tracks, from a closed set of spans.",
|
|
260
260
|
"examples": [
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Frames",
|
|
3
|
+
"slug": "frames",
|
|
4
|
+
"group": "layout",
|
|
5
|
+
"importPath": "@cueplusplus/ui",
|
|
6
|
+
"peerDependencies": [],
|
|
7
|
+
"clientOnly": true,
|
|
8
|
+
"description": "A design canvas: frames side by side, each holding one axis of one component.\n\n`Frames.Root` is the canvas — short, wide, scrollable sideways, focusable,\nand pannable with the pointer. `Frames.Item` is one captioned cell, and it is\nused **inside a `Preview`** rather than inside the Root. The two are apart\nbecause that is how they are used, and a props-only API cannot say it.\n\n**The canvas knows nothing about its children.** It does not count them, does\nnot index them and does not select one; `hint` is where a caller puts what it\nknows. A canvas that knew its frames would be a manifest API, and this is a\nslot API.",
|
|
9
|
+
"props": [],
|
|
10
|
+
"typeReferences": [],
|
|
11
|
+
"subcomponents": [
|
|
12
|
+
"Root",
|
|
13
|
+
"Item"
|
|
14
|
+
],
|
|
15
|
+
"parts": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Root",
|
|
18
|
+
"usage": "Frames.Root",
|
|
19
|
+
"description": "The canvas: a short, wide, focusable scroll region that holds frames.\n\n**It knows nothing about its children.** It does not count them, index them\nor select one — a canvas that knew its frames would be a manifest API, and\nthis is a slot API. `hint` is where a caller puts the count it knows and this\ndoes not.",
|
|
20
|
+
"props": [
|
|
21
|
+
{
|
|
22
|
+
"name": "children",
|
|
23
|
+
"type": "ReactNode",
|
|
24
|
+
"required": true,
|
|
25
|
+
"defaultValue": null,
|
|
26
|
+
"description": "The frames. Anything — this never inspects them.",
|
|
27
|
+
"control": {
|
|
28
|
+
"kind": "node",
|
|
29
|
+
"fixture": "children.text"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "label",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"required": true,
|
|
36
|
+
"defaultValue": null,
|
|
37
|
+
"description": "The canvas's accessible name — \"Button frames\". Required: the canvas is a\nfocusable scroll region, and a scroll region with no name is one a screen\nreader announces as nothing.",
|
|
38
|
+
"control": {
|
|
39
|
+
"kind": "string"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "height",
|
|
44
|
+
"type": "\"short\" | \"tall\"",
|
|
45
|
+
"required": false,
|
|
46
|
+
"defaultValue": "short",
|
|
47
|
+
"description": "`\"short\"` is 18.5rem, `\"tall\"` 27rem. A CSS length in `style` overrides both.",
|
|
48
|
+
"control": {
|
|
49
|
+
"kind": "enum",
|
|
50
|
+
"members": [
|
|
51
|
+
"short",
|
|
52
|
+
"tall"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "ground",
|
|
58
|
+
"type": "\"none\" | \"plain\" | \"dots\"",
|
|
59
|
+
"required": false,
|
|
60
|
+
"defaultValue": "dots",
|
|
61
|
+
"description": "The canvas ground. Defaults to `\"dots\"`.",
|
|
62
|
+
"control": {
|
|
63
|
+
"kind": "enum",
|
|
64
|
+
"members": [
|
|
65
|
+
"none",
|
|
66
|
+
"plain",
|
|
67
|
+
"dots"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "hint",
|
|
73
|
+
"type": "ReactNode",
|
|
74
|
+
"required": false,
|
|
75
|
+
"defaultValue": null,
|
|
76
|
+
"description": "The line in the bottom-right corner — \"drag to pan · 8 frames\". Unset, none is drawn.",
|
|
77
|
+
"control": {
|
|
78
|
+
"kind": "node",
|
|
79
|
+
"fixture": "children.text"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "pan",
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"required": false,
|
|
86
|
+
"defaultValue": "true",
|
|
87
|
+
"description": "Drag-to-pan with the pointer. Defaults to `true`. A drag that starts on a\ncontrol is never a pan, so a frame's own controls stay usable — and \"on a\ncontrol\" counts the control's own contents: the words inside a\n`role=\"menuitem\"` row, the icon inside a `button`. A drag inside a\nrole-bearing box that is not itself a control — a `role=\"group\"` frame, a\n`role=\"region\"` — still pans, because a frame is such a box and a board is\nmostly frames. Only the primary button drags; `false` binds no pointer\nlistener at all; and the keyboard scrolls the canvas either way.",
|
|
88
|
+
"control": {
|
|
89
|
+
"kind": "boolean"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"typeReferences": [
|
|
94
|
+
"Omit<React.ComponentPropsWithoutRef<\"div\">, \"children\">"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Item",
|
|
99
|
+
"usage": "Frames.Item",
|
|
100
|
+
"description": "One captioned cell of a frame.\n\n**It is used inside a `Preview`, not inside `Frames.Root`** — that is the\nwhole reason this is a compound rather than a props API: the canvas holds\nframes, a frame holds a stage, and the stage holds these. `Frames.Root` never\nlooks for one.\n\n**A stage does not arrange them, and the caller does.** A stage is a centred\ngrid in both presentations, so two items left to it stack; the row every\nexample here draws is `<div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">`,\nand `items-end` is what lines the captions up when the specimens are\ndifferent heights.",
|
|
101
|
+
"props": [
|
|
102
|
+
{
|
|
103
|
+
"name": "children",
|
|
104
|
+
"type": "ReactNode",
|
|
105
|
+
"required": true,
|
|
106
|
+
"defaultValue": null,
|
|
107
|
+
"description": "The specimen.",
|
|
108
|
+
"control": {
|
|
109
|
+
"kind": "node",
|
|
110
|
+
"fixture": "children.text"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "caption",
|
|
115
|
+
"type": "ReactNode",
|
|
116
|
+
"required": false,
|
|
117
|
+
"defaultValue": null,
|
|
118
|
+
"description": "The line under it. Hidden — not removed — when the frame sets `captions={false}`.",
|
|
119
|
+
"control": {
|
|
120
|
+
"kind": "node",
|
|
121
|
+
"fixture": "children.text"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "selected",
|
|
126
|
+
"type": "boolean",
|
|
127
|
+
"required": false,
|
|
128
|
+
"defaultValue": "false",
|
|
129
|
+
"description": "Draws the selection outline the frame gives the item a reader is inspecting.",
|
|
130
|
+
"control": {
|
|
131
|
+
"kind": "boolean"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"typeReferences": [
|
|
136
|
+
"React.ComponentPropsWithoutRef<\"div\">"
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"variants": {},
|
|
141
|
+
"defaultVariants": {},
|
|
142
|
+
"tokensUsed": [
|
|
143
|
+
"--cue-accent",
|
|
144
|
+
"--cue-border",
|
|
145
|
+
"--cue-fg-subtle",
|
|
146
|
+
"--cue-font-mono",
|
|
147
|
+
"--cue-hair-strong",
|
|
148
|
+
"--cue-radius-control",
|
|
149
|
+
"--cue-radius-surface",
|
|
150
|
+
"--cue-space-2",
|
|
151
|
+
"--cue-space-3",
|
|
152
|
+
"--cue-space-4",
|
|
153
|
+
"--cue-space-6",
|
|
154
|
+
"--cue-space-8",
|
|
155
|
+
"--cue-surface-1",
|
|
156
|
+
"--cue-text-micro"
|
|
157
|
+
],
|
|
158
|
+
"summary": "A design canvas: frames side by side, each holding one axis of one component. Short, wide, scrollable sideways, focusable, and pannable with the pointer.",
|
|
159
|
+
"examples": [
|
|
160
|
+
{
|
|
161
|
+
"title": "A short board that pans",
|
|
162
|
+
"code": "<Frames.Root label=\"Button frames\" hint=\"drag to pan · 2 frames\">\n <Preview presentation=\"frame\" label=\"Tone\" count={2}>\n <div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">\n <Frames.Item caption=\"accent\">\n <Button variant=\"primary\">Take cue</Button>\n </Frames.Item>\n <Frames.Item caption=\"default\">\n <Button>Take cue</Button>\n </Frames.Item>\n </div>\n </Preview>\n</Frames.Root>",
|
|
163
|
+
"language": "tsx"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"title": "Tall, flat, and still",
|
|
167
|
+
"code": "<Frames.Root label=\"Grid frames\" height=\"tall\" ground=\"plain\" pan={false}>\n <Preview presentation=\"frame\" label=\"Columns\" count={3} captions={false}>\n …\n </Preview>\n</Frames.Root>",
|
|
168
|
+
"language": "tsx"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"title": "Usage",
|
|
172
|
+
"code": "<Frames.Root label=\"Button frames\" hint=\"drag to pan · 3 frames\">\n <Preview presentation=\"frame\" label=\"Tone\" count={3} name={<ToneMenu />}>\n <div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">\n <Frames.Item caption=\"accent\"><Button tone=\"accent\">Take cue</Button></Frames.Item>\n <Frames.Item caption=\"ok\"><Button tone=\"ok\">Take cue</Button></Frames.Item>\n <Frames.Item caption=\"warn\"><Button tone=\"warn\">Take cue</Button></Frames.Item>\n </div>\n </Preview>\n</Frames.Root>",
|
|
173
|
+
"language": "tsx"
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"status": "stable",
|
|
177
|
+
"url": "/docs/components/frames",
|
|
178
|
+
"mdUrl": "/docs/components/frames.md",
|
|
179
|
+
"jsonUrl": "/r/components/frames.json",
|
|
180
|
+
"whenToUse": [
|
|
181
|
+
"Showing a component's axes at once — one frame per axis, each with its own name row and its own settings — instead of one preview a reader has to re-configure.",
|
|
182
|
+
"Any short, wide board of specimens a reader scans rather than reads: a theme sheet, a size ladder, a state matrix.",
|
|
183
|
+
"Beside `Preview presentation=\"frame\"`, which is what a frame on this canvas is."
|
|
184
|
+
],
|
|
185
|
+
"whenNotToUse": [
|
|
186
|
+
"Laying components out in a page. That is `Grid` or `Bento`; a canvas is a viewport that scrolls, and everything in it is off-screen by design.",
|
|
187
|
+
"A board a reader zooms. This does not zoom, and it will not: a transformed subtree breaks every popover anchored inside it.",
|
|
188
|
+
"Scrolling down. The canvas is `overflow-y: hidden` on purpose — it is short and wide, not a second page."
|
|
189
|
+
],
|
|
190
|
+
"commonMistakes": [
|
|
191
|
+
"Putting `Frames.Item` inside `Frames.Root`. The item belongs on a frame's stage — `Frames.Root` holds frames, and a frame is a `Preview`.",
|
|
192
|
+
"Expecting a frame to arrange its items. A stage is a centred grid in both presentations, so two items stack; put them in a row yourself — the row is `<div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">`, and every example here uses exactly that one.",
|
|
193
|
+
"Leaving `label` off, or repeating the page's heading in it. The canvas is a focusable scroll region, and its name is what a screen reader announces when a reader tabs into it.",
|
|
194
|
+
"Expecting the canvas to count its frames. It never inspects its children — pass the count yourself, in `hint`.",
|
|
195
|
+
"Expecting a long `caption` to wrap or to ellipsise on its own. A cell does not shrink, so a caption longer than its specimen widens the cell instead, and the wrapping row it sits in wraps a cell sooner — the `truncate` on the caption has no narrower box to fire in. Bound the cell yourself when a caption can run long: `<Frames.Item className=\"max-w-[12rem]\" caption=\"…\">`.",
|
|
196
|
+
"Reading `captions={false}` as a way to take a caption out of the page. It is visual only — the caption goes to `opacity: 0` and stops taking pointer events, keeping its box so nothing re-flows and keeping its place in the accessibility tree, so a screen reader still reads it. Delete the `caption` prop if the words should not be there at all."
|
|
197
|
+
],
|
|
198
|
+
"specimens": [
|
|
199
|
+
{
|
|
200
|
+
"title": "Frames",
|
|
201
|
+
"group": "layout",
|
|
202
|
+
"components": [
|
|
203
|
+
"Frames"
|
|
204
|
+
],
|
|
205
|
+
"code": "<Frames.Root\n label=\"Button frames\"\n hint=\"drag to pan · 2 frames\"\n className=\"w-full\"\n>\n <Preview\n label=\"Button tone\"\n presentation=\"frame\"\n count={3}\n tools={<Chip variant=\"outline\">tone</Chip>}\n >\n <div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">\n <Frames.Item caption=\"accent\" selected>\n <Button variant=\"primary\">Take cue</Button>\n </Frames.Item>\n <Frames.Item caption=\"default\">\n <Button>Take cue</Button>\n </Frames.Item>\n <Frames.Item caption=\"subtle\">\n <Button variant=\"ghost\">Take cue</Button>\n </Frames.Item>\n </div>\n </Preview>\n <Preview label=\"Button density\" presentation=\"frame\" count={2} captions={false}>\n <div className=\"flex flex-wrap items-end gap-(--cue-space-6)\">\n <Frames.Item caption=\"compact\">\n <Button size=\"sm\">Go</Button>\n </Frames.Item>\n <Frames.Item caption=\"normal\">\n <Button>Go</Button>\n </Frames.Item>\n </div>\n </Preview>\n</Frames.Root>",
|
|
206
|
+
"note": "A design canvas: frames side by side, each holding one axis of one component. The canvas is a focusable scroll region; a frame is a `Preview` in its `frame` presentation, with its name row above the stage and four crosshairs at the corners; an item is one captioned cell on that stage.",
|
|
207
|
+
"interaction": "the canvas scrolls sideways: click it and Left and Right step one rung of the space ladder, PageUp and PageDown a viewport, Home and End the two ends. Dragging it pans it — except a drag that starts on a control, which stays a click. `pan={false}` turns the dragging off and leaves the keyboard."
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}
|