@cueplusplus/ui 0.1.0 → 0.2.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 +684 -0
- package/dist/brand/_chassis.d.ts +54 -0
- package/dist/brand/_chassis.js +81 -0
- package/dist/brand/_geometry.js +41 -0
- package/dist/brand/cue-logotype.d.ts +29 -0
- package/dist/brand/cue-logotype.js +60 -0
- package/dist/brand/cue-mark.d.ts +30 -0
- package/dist/brand/cue-mark.js +61 -0
- package/dist/brand/index.d.ts +5 -0
- package/dist/brand/plussie.d.ts +67 -0
- package/dist/brand/plussie.js +125 -0
- package/dist/chat/agent-pile.js +1 -1
- package/dist/chat/ask-box.js +2 -2
- package/dist/chat/delegation-card.js +1 -1
- package/dist/chat/message.js +2 -2
- package/dist/chrome/_edge-scroller.d.ts +45 -0
- package/dist/chrome/_edge-scroller.js +138 -0
- package/dist/chrome/_glyphs.js +19 -1
- package/dist/chrome/_tabs-scroll.d.ts +12 -7
- package/dist/chrome/_tabs-scroll.js +12 -7
- package/dist/chrome/app-bar.d.ts +103 -0
- package/dist/chrome/app-bar.js +175 -0
- package/dist/chrome/app-shell.d.ts +125 -0
- package/dist/chrome/app-shell.js +167 -0
- package/dist/chrome/footer.d.ts +75 -0
- package/dist/chrome/footer.js +68 -0
- package/dist/chrome/index.d.ts +5 -1
- package/dist/chrome/index.js +4 -1
- package/dist/chrome/navigation-menu.js +1 -1
- package/dist/chrome/page-shell.d.ts +1 -1
- package/dist/chrome/page-shell.js +15 -5
- package/dist/chrome/segmented-control.d.ts +19 -0
- package/dist/chrome/segmented-control.js +40 -21
- package/dist/chrome/tabs.js +21 -65
- package/dist/chrome/toolbar.d.ts +22 -0
- package/dist/chrome/toolbar.js +26 -5
- package/dist/configurator/_overrides.js +4 -2
- package/dist/configurator/configurator.js +1 -1
- package/dist/configurator/panel-sections.js +10 -4
- package/dist/dmx/channel-matrix.js +1 -1
- package/dist/forms/otp-field.js +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.js +15 -9
- package/dist/instruments/data-table.js +1 -1
- package/dist/layout/carousel.js +1 -1
- package/dist/layout/container.d.ts +1 -1
- package/dist/layout/container.js +6 -3
- package/dist/layout/index.d.ts +3 -3
- package/dist/layout/index.js +3 -3
- package/dist/layout/pagination.js +1 -1
- package/dist/layout/sidebar.d.ts +23 -3
- package/dist/layout/sidebar.js +18 -7
- package/dist/midi/spectrum-visualizer.js +1 -1
- package/dist/midi/timeline-ruler.js +1 -1
- package/dist/overlays/command-palette.js +1 -1
- package/dist/overlays/dialog.js +1 -1
- package/dist/overlays/dropdown-menu.js +2 -2
- package/dist/overlays/hover-card.js +1 -1
- package/dist/overlays/index.js +1 -1
- package/dist/overlays/popover.js +1 -1
- package/dist/overlays/sheet.js +1 -1
- package/dist/overlays/toast.js +1 -1
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.js +1 -1
- package/dist/primitives/status-dot.d.ts +7 -0
- package/dist/primitives/status-dot.js +8 -1
- package/dist/styles.css +47 -1
- package/dist/system/density.d.ts +4 -1
- package/dist/system/density.js +12 -4
- package/dist/system/index.d.ts +2 -2
- package/dist/system/portal.d.ts +5 -5
- package/dist/system/portal.js +11 -6
- package/dist/system/prepaint.d.ts +15 -8
- package/dist/system/prepaint.js +26 -9
- package/dist/system/theme-provider.d.ts +36 -10
- package/dist/system/theme-provider.js +56 -18
- package/dist/system/use-density.d.ts +3 -3
- package/dist/system/use-density.js +16 -6
- package/dist/system/use-theme.d.ts +4 -2
- package/dist/system/use-theme.js +4 -2
- package/dist/theming/_presets.js +80 -5
- package/dist/theming/create-theme.js +13 -4
- package/package.json +4 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { computeScrollEdges, edgeFadeMask } from "./_tabs-scroll.js";
|
|
4
|
+
import { useComposedRefs } from "../lib/compose.js";
|
|
5
|
+
import { ChevronLeftGlyph, ChevronRightGlyph } from "./_glyphs.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/chrome/_edge-scroller.tsx
|
|
9
|
+
/** Page the strip by most of a screenful, keeping a sliver of context. */
|
|
10
|
+
const NUDGE_FRACTION = .7;
|
|
11
|
+
/**
|
|
12
|
+
* What an overflowing element wears: the scroll axis, the fade, and no
|
|
13
|
+
* scrollbar.
|
|
14
|
+
*
|
|
15
|
+
* A literal rather than a computed string, because Tailwind scans source text —
|
|
16
|
+
* and the mask travels as a custom property so the prefixed and unprefixed
|
|
17
|
+
* properties are written once here instead of twice per render.
|
|
18
|
+
*
|
|
19
|
+
* The property is always set, `none` when nothing overflows, and that word is
|
|
20
|
+
* load-bearing: custom properties **inherit**. Leaving it unset on a strip that
|
|
21
|
+
* fits does not mean "no mask", it means "whatever an ancestor said" — and a
|
|
22
|
+
* `SegmentedControl` inside an overflowing `Toolbar` then wears the bar's fade
|
|
23
|
+
* and loses its own last segment to it. Measured in a real browser at 320px
|
|
24
|
+
* before it was written down. `none` is the initial value either way, so the
|
|
25
|
+
* common case still creates no compositing layer.
|
|
26
|
+
*/
|
|
27
|
+
const EDGE_SCROLLER_CLASSES = "overflow-x-auto [mask-image:var(--cue-edge-fade)] [-webkit-mask-image:var(--cue-edge-fade)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden";
|
|
28
|
+
/**
|
|
29
|
+
* Track which edges of a horizontal scroller still hide content, and hand back
|
|
30
|
+
* the mask and the pager that go with it.
|
|
31
|
+
*
|
|
32
|
+
* @param enabled False to keep the hook mounted but inert — a component whose
|
|
33
|
+
* overflow behaviour is switched off must still call the same hooks in the same
|
|
34
|
+
* order.
|
|
35
|
+
*/
|
|
36
|
+
function useEdgeScroll(enabled = true) {
|
|
37
|
+
const ref = React.useRef(null);
|
|
38
|
+
const [edges, setEdges] = React.useState({
|
|
39
|
+
start: false,
|
|
40
|
+
end: false
|
|
41
|
+
});
|
|
42
|
+
const sync = React.useCallback(() => {
|
|
43
|
+
const el = ref.current;
|
|
44
|
+
if (el === null) return;
|
|
45
|
+
setEdges(computeScrollEdges(el.scrollLeft, el.scrollWidth, el.clientWidth));
|
|
46
|
+
}, []);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (!enabled) return;
|
|
49
|
+
const el = ref.current;
|
|
50
|
+
if (el === null) return;
|
|
51
|
+
sync();
|
|
52
|
+
el.addEventListener("scroll", sync, { passive: true });
|
|
53
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => sync());
|
|
54
|
+
observer?.observe(el);
|
|
55
|
+
if (el.firstElementChild !== null) observer?.observe(el.firstElementChild);
|
|
56
|
+
return () => {
|
|
57
|
+
el.removeEventListener("scroll", sync);
|
|
58
|
+
observer?.disconnect();
|
|
59
|
+
};
|
|
60
|
+
}, [enabled, sync]);
|
|
61
|
+
const nudge = React.useCallback((direction) => {
|
|
62
|
+
const el = ref.current;
|
|
63
|
+
el?.scrollBy?.({
|
|
64
|
+
left: direction * el.clientWidth * NUDGE_FRACTION,
|
|
65
|
+
behavior: "smooth"
|
|
66
|
+
});
|
|
67
|
+
}, []);
|
|
68
|
+
const mask = enabled ? edgeFadeMask(edges) : void 0;
|
|
69
|
+
return {
|
|
70
|
+
ref,
|
|
71
|
+
edges: enabled ? edges : {
|
|
72
|
+
start: false,
|
|
73
|
+
end: false
|
|
74
|
+
},
|
|
75
|
+
style: enabled ? { "--cue-edge-fade": mask ?? "none" } : void 0,
|
|
76
|
+
nudge
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The pager buttons over an overflowing strip, drawn only for the edges that
|
|
81
|
+
* actually hide something.
|
|
82
|
+
*
|
|
83
|
+
* Absolutely positioned, so the element they sit over must be inside a
|
|
84
|
+
* `relative` frame.
|
|
85
|
+
*/
|
|
86
|
+
function EdgeChevrons({ edges, onNudge, previousLabel, nextLabel, className }) {
|
|
87
|
+
const base = "absolute inset-y-0 z-10 flex w-(--cue-space-5) cursor-pointer items-center justify-center text-fg-muted outline-none hover:text-fg focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-accent";
|
|
88
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [edges.start ? /* @__PURE__ */ jsx("button", {
|
|
89
|
+
type: "button",
|
|
90
|
+
"data-slot": "edge-scroll-previous",
|
|
91
|
+
"aria-label": previousLabel,
|
|
92
|
+
onClick: () => onNudge(-1),
|
|
93
|
+
className: cn(base, "left-0", className),
|
|
94
|
+
children: /* @__PURE__ */ jsx(ChevronLeftGlyph, { className: "size-icon-sm" })
|
|
95
|
+
}) : null, edges.end ? /* @__PURE__ */ jsx("button", {
|
|
96
|
+
type: "button",
|
|
97
|
+
"data-slot": "edge-scroll-next",
|
|
98
|
+
"aria-label": nextLabel,
|
|
99
|
+
onClick: () => onNudge(1),
|
|
100
|
+
className: cn(base, "right-0", className),
|
|
101
|
+
children: /* @__PURE__ */ jsx(ChevronRightGlyph, { className: "size-icon-sm" })
|
|
102
|
+
}) : null] });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A `<nav>` that scrolls its links, wrapped in the frame the chevrons need.
|
|
106
|
+
*
|
|
107
|
+
* A component rather than the hook that `Toolbar` and `SegmentedControl` use
|
|
108
|
+
* directly, and for one specific reason: `AppBar` has to be renderable from a
|
|
109
|
+
* **server component** — it is a site header, and the site header is where a
|
|
110
|
+
* framework reads its data — so `app-bar.tsx` carries no `"use client"`
|
|
111
|
+
* directive and cannot call a hook. The client boundary is here instead, around
|
|
112
|
+
* the one part of a bar that has state.
|
|
113
|
+
*/
|
|
114
|
+
const EdgeScrollNav = React.forwardRef(function EdgeScrollNav({ className, frameClassName, scrolls = true, previousLabel, nextLabel, style, ...elementProps }, ref) {
|
|
115
|
+
const scroll = useEdgeScroll(scrolls);
|
|
116
|
+
const navRef = useComposedRefs(ref, scroll.ref);
|
|
117
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
118
|
+
"data-slot": "app-bar-nav-frame",
|
|
119
|
+
className: cn("relative flex", frameClassName),
|
|
120
|
+
children: [/* @__PURE__ */ jsx("nav", {
|
|
121
|
+
ref: navRef,
|
|
122
|
+
className: cn(scrolls ? EDGE_SCROLLER_CLASSES : null, className),
|
|
123
|
+
...elementProps,
|
|
124
|
+
style: {
|
|
125
|
+
...style,
|
|
126
|
+
...scroll.style
|
|
127
|
+
}
|
|
128
|
+
}), /* @__PURE__ */ jsx(EdgeChevrons, {
|
|
129
|
+
edges: scroll.edges,
|
|
130
|
+
onNudge: scroll.nudge,
|
|
131
|
+
previousLabel,
|
|
132
|
+
nextLabel,
|
|
133
|
+
className: "bg-surface-1"
|
|
134
|
+
})]
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//#endregion
|
|
138
|
+
export { EDGE_SCROLLER_CLASSES, EdgeChevrons, EdgeScrollNav, useEdgeScroll };
|
package/dist/chrome/_glyphs.js
CHANGED
|
@@ -97,5 +97,23 @@ function EyeOffGlyph(props) {
|
|
|
97
97
|
children: [/* @__PURE__ */ jsx("path", { d: "M6.2 4A6.5 6.5 0 0 1 8 3.5C12 3.5 14.5 8 14.5 8a12 12 0 0 1-2.3 2.7M9.8 12a6.5 6.5 0 0 1-1.8.5C4 12.5 1.5 8 1.5 8A12 12 0 0 1 4.4 4.8" }), /* @__PURE__ */ jsx("path", { d: "m2.5 2.5 11 11" })]
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Three stacked strokes — the navigation trigger's mark.
|
|
102
|
+
*
|
|
103
|
+
* Only ever drawn below the shell's one breakpoint, where the navigation column
|
|
104
|
+
* has become a drawer and the bar needs something to open it with.
|
|
105
|
+
*/
|
|
106
|
+
function MenuGlyph(props) {
|
|
107
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
108
|
+
viewBox: "0 0 16 16",
|
|
109
|
+
fill: "none",
|
|
110
|
+
stroke: "currentColor",
|
|
111
|
+
strokeWidth: "1.75",
|
|
112
|
+
strokeLinecap: "round",
|
|
113
|
+
"aria-hidden": "true",
|
|
114
|
+
...props,
|
|
115
|
+
children: /* @__PURE__ */ jsx("path", { d: "M2.5 4h11M2.5 8h11M2.5 12h11" })
|
|
116
|
+
});
|
|
117
|
+
}
|
|
100
118
|
//#endregion
|
|
101
|
-
export { CaretDownGlyph, ChevronLeftGlyph, ChevronRightGlyph, EyeGlyph, EyeOffGlyph, GhostGlyph };
|
|
119
|
+
export { CaretDownGlyph, ChevronLeftGlyph, ChevronRightGlyph, EyeGlyph, EyeOffGlyph, GhostGlyph, MenuGlyph };
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
//#region src/chrome/_tabs-scroll.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Pure geometry for
|
|
3
|
+
* Pure geometry for the library's edge-scroll affordance.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Written for {@link ScrollableTabsList} (see `./tabs.tsx`) and now the shared
|
|
6
|
+
* arithmetic under every strip in the system that can outgrow its container:
|
|
7
|
+
* the tab list, `Toolbar`, `SegmentedControl` and `AppBar`'s navigation all
|
|
8
|
+
* reach it through `./_edge-scroller`, which is where the React half lives.
|
|
9
|
+
*
|
|
10
|
+
* A separate module from the components on purpose. These are plain functions
|
|
6
11
|
* over numbers — testable without a DOM, reusable by anything else that needs a
|
|
7
|
-
* scroll-edge affordance — and keeping them out of
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* scroll-edge affordance — and keeping them out of a component file means that
|
|
13
|
+
* file can stay `"use client"` while this one has no directive at all. (It is
|
|
14
|
+
* also what makes React Fast Refresh work on the component: a file that exports
|
|
15
|
+
* both a component and non-component values gets a full reload instead of a hot
|
|
16
|
+
* swap.)
|
|
12
17
|
*/
|
|
13
18
|
/** How far the fade reaches in from an overflowing edge, in px. */
|
|
14
19
|
declare const TAB_FADE_PX = 24;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
//#region src/chrome/_tabs-scroll.ts
|
|
2
2
|
/**
|
|
3
|
-
* Pure geometry for
|
|
3
|
+
* Pure geometry for the library's edge-scroll affordance.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Written for {@link ScrollableTabsList} (see `./tabs.tsx`) and now the shared
|
|
6
|
+
* arithmetic under every strip in the system that can outgrow its container:
|
|
7
|
+
* the tab list, `Toolbar`, `SegmentedControl` and `AppBar`'s navigation all
|
|
8
|
+
* reach it through `./_edge-scroller`, which is where the React half lives.
|
|
9
|
+
*
|
|
10
|
+
* A separate module from the components on purpose. These are plain functions
|
|
6
11
|
* over numbers — testable without a DOM, reusable by anything else that needs a
|
|
7
|
-
* scroll-edge affordance — and keeping them out of
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* scroll-edge affordance — and keeping them out of a component file means that
|
|
13
|
+
* file can stay `"use client"` while this one has no directive at all. (It is
|
|
14
|
+
* also what makes React Fast Refresh work on the component: a file that exports
|
|
15
|
+
* both a component and non-component values gets a full reload instead of a hot
|
|
16
|
+
* swap.)
|
|
12
17
|
*/
|
|
13
18
|
/** How far the fade reaches in from an overflowing edge, in px. */
|
|
14
19
|
const TAB_FADE_PX = 24;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { BarOverflow } from "./_edge-scroller.js";
|
|
2
|
+
import { LinkProps } from "../layout/link.js";
|
|
3
|
+
import { SeparatorProps } from "../primitives/separator.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
//#region src/chrome/app-bar.d.ts
|
|
6
|
+
interface AppBarRootProps extends React.ComponentPropsWithoutRef<"header"> {
|
|
7
|
+
/**
|
|
8
|
+
* Keep the bar at the top of the viewport as the page scrolls. Defaults to
|
|
9
|
+
* `true`.
|
|
10
|
+
*
|
|
11
|
+
* Set it `false` inside `AppShell`, where nothing scrolls the document and a
|
|
12
|
+
* sticky bar would be solving a problem the shell has already solved.
|
|
13
|
+
*/
|
|
14
|
+
sticky?: boolean;
|
|
15
|
+
}
|
|
16
|
+
type AppBarBrandProps = Omit<LinkProps, "variant" | "external">;
|
|
17
|
+
interface AppBarNavProps extends Omit<React.ComponentPropsWithoutRef<"nav">, "children"> {
|
|
18
|
+
/** Where the links sit when they are narrower than the space. Defaults to `"end"`. */
|
|
19
|
+
align?: "start" | "center" | "end";
|
|
20
|
+
/**
|
|
21
|
+
* What the navigation does when the links do not fit. Defaults to `"scroll"`.
|
|
22
|
+
*
|
|
23
|
+
* The same behaviour `Toolbar` and `SegmentedControl` have, from the same
|
|
24
|
+
* module: the links scroll inside the bar, the overflowing edge is faded and
|
|
25
|
+
* a pager chevron appears there. This is what lets the bar survive a phone
|
|
26
|
+
* without a viewport breakpoint.
|
|
27
|
+
*/
|
|
28
|
+
overflow?: BarOverflow;
|
|
29
|
+
/** Accessible name for the chevron that reveals links off the leading edge. */
|
|
30
|
+
previousLabel?: string;
|
|
31
|
+
/** Accessible name for the chevron that reveals links off the trailing edge. */
|
|
32
|
+
nextLabel?: string;
|
|
33
|
+
/** Extra classes for the frame around the `<nav>`, which is what takes the remaining width. */
|
|
34
|
+
frameClassName?: string;
|
|
35
|
+
/** The links, and any separators between clusters of them. */
|
|
36
|
+
children?: React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Which of the bar's two registers a link is set in.
|
|
40
|
+
*
|
|
41
|
+
* `muted` is furniture — the sections a reader scans past. `strong` is a
|
|
42
|
+
* destination, and the difference is the point: a run of eight identical muted
|
|
43
|
+
* words has no shape, and the one entry that matters disappears into it. Split
|
|
44
|
+
* the run with an {@link AppBarSeparator} and set the second cluster `strong`,
|
|
45
|
+
* and the bar reads as one list, differently weighted, rather than as two
|
|
46
|
+
* navigations.
|
|
47
|
+
*/
|
|
48
|
+
type AppBarRegister = "muted" | "strong";
|
|
49
|
+
interface AppBarLinkProps extends LinkProps {
|
|
50
|
+
/** Which register the link is set in. Defaults to `"muted"`. */
|
|
51
|
+
register?: AppBarRegister;
|
|
52
|
+
/**
|
|
53
|
+
* This is the page the reader is on.
|
|
54
|
+
*
|
|
55
|
+
* Inks the link with the accent and sets `aria-current="page"` — the same
|
|
56
|
+
* bargain `Sidebar.Item` makes, so the current destination is announced and
|
|
57
|
+
* not merely coloured. Overrides `register`: a current page is never
|
|
58
|
+
* furniture.
|
|
59
|
+
*/
|
|
60
|
+
active?: boolean;
|
|
61
|
+
}
|
|
62
|
+
type AppBarSeparatorProps = Omit<SeparatorProps, "orientation">;
|
|
63
|
+
type AppBarActionsProps = React.ComponentPropsWithoutRef<"div">;
|
|
64
|
+
/**
|
|
65
|
+
* The horizontal bar a site or an application wears across the top: brand slot,
|
|
66
|
+
* navigation slot, actions slot, one chrome rung tall.
|
|
67
|
+
*
|
|
68
|
+
* `AppShell` is the frame for a screen that owns the viewport; this is the bar
|
|
69
|
+
* for a document that scrolls — a marketing page, a documentation site, a
|
|
70
|
+
* console's public half. `NavigationMenu` is neither: it is the *menu* that
|
|
71
|
+
* goes inside `AppBar.Nav` when a section has children.
|
|
72
|
+
*
|
|
73
|
+
* Six parts. `Root` is the `<header>`, `Brand` the leading link, `Nav` the
|
|
74
|
+
* `<nav>` landmark that takes the remaining width and scrolls when the links
|
|
75
|
+
* outgrow it, `Link` one destination in one of two registers, `Separator` the
|
|
76
|
+
* seam between clusters, and `Actions` the trailing controls.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* <AppBar.Root>
|
|
80
|
+
* <AppBar.Brand render={<NextLink href="/" />}>
|
|
81
|
+
* <Eyebrow>@cueplusplus/ui</Eyebrow>
|
|
82
|
+
* </AppBar.Brand>
|
|
83
|
+
* <AppBar.Nav aria-label="Sections">
|
|
84
|
+
* <AppBar.Link render={<NextLink href="/docs" />}>Docs</AppBar.Link>
|
|
85
|
+
* <AppBar.Link render={<NextLink href="/theming" />}>Theming</AppBar.Link>
|
|
86
|
+
* <AppBar.Separator />
|
|
87
|
+
* <AppBar.Link render={<NextLink href="/gallery" />} register="strong">Gallery</AppBar.Link>
|
|
88
|
+
* <AppBar.Link external render={<a href="https://skills.cueplusplus.com" />}>Skills</AppBar.Link>
|
|
89
|
+
* </AppBar.Nav>
|
|
90
|
+
* <AppBar.Separator />
|
|
91
|
+
* <AppBar.Actions><ThemePicker /></AppBar.Actions>
|
|
92
|
+
* </AppBar.Root>
|
|
93
|
+
*/
|
|
94
|
+
declare const AppBar: {
|
|
95
|
+
Root: React.ForwardRefExoticComponent<AppBarRootProps & React.RefAttributes<HTMLElement>>;
|
|
96
|
+
Brand: React.ForwardRefExoticComponent<AppBarBrandProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
97
|
+
Nav: React.ForwardRefExoticComponent<AppBarNavProps & React.RefAttributes<HTMLElement>>;
|
|
98
|
+
Link: React.ForwardRefExoticComponent<AppBarLinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
99
|
+
Separator: React.ForwardRefExoticComponent<Omit<AppBarSeparatorProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
100
|
+
Actions: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { AppBar, AppBarActionsProps, AppBarBrandProps, AppBarLinkProps, AppBarNavProps, AppBarRegister, AppBarRootProps, AppBarSeparatorProps };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Link } from "../layout/link.js";
|
|
3
|
+
import { Separator } from "../primitives/separator.js";
|
|
4
|
+
import { EdgeScrollNav } from "./_edge-scroller.js";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/chrome/app-bar.tsx
|
|
8
|
+
/**
|
|
9
|
+
* The bar across the top of a document: brand, navigation, actions.
|
|
10
|
+
*
|
|
11
|
+
* One rung of the chrome ladder tall, from `--cue-chrome-toolbar` — the same
|
|
12
|
+
* token `Toolbar` and the docs site's hand-rolled header use, so a page that
|
|
13
|
+
* stacks a bar on a toolbar gets two equal strips rather than two guesses.
|
|
14
|
+
*
|
|
15
|
+
* Sticky by default, and translucent because of it: a bar pinned over scrolling
|
|
16
|
+
* content needs a ground, and `bg-surface-1/95` plus `backdrop-blur` says
|
|
17
|
+
* "there is content under this" where an opaque fill would claim the content
|
|
18
|
+
* ends here. `z-40` is above the page and below the `z-50` this library's
|
|
19
|
+
* overlays float at, so a menu opened from the bar is never behind it.
|
|
20
|
+
*
|
|
21
|
+
* **This is a bar, not a menu.** `NavigationMenu` is the mega-menu — triggers
|
|
22
|
+
* that open panels — and it goes *inside* an `AppBar.Nav` when a section has
|
|
23
|
+
* children. This component is the strip it sits in.
|
|
24
|
+
*
|
|
25
|
+
* Static markup — no `"use client"` on this module, deliberately: a site header
|
|
26
|
+
* is where a framework reads its data, so the bar has to be renderable from a
|
|
27
|
+
* server component. The one part with state is the navigation's scroller, and
|
|
28
|
+
* the client boundary is drawn around that alone (`_edge-scroller.tsx`).
|
|
29
|
+
*
|
|
30
|
+
* It adds **no viewport breakpoint**. `AppShell` holds the library's only one,
|
|
31
|
+
* for navigation shape, and a horizontal bar does not need it: the brand and
|
|
32
|
+
* the actions are `shrink-0`, the navigation between them takes the remaining
|
|
33
|
+
* width and scrolls inside it, so a 390px viewport gets the same bar with a
|
|
34
|
+
* shorter window onto the same links rather than a second layout to maintain.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* <AppBar.Root>
|
|
38
|
+
* <AppBar.Brand render={<NextLink href="/" />}>
|
|
39
|
+
* <Eyebrow>@cueplusplus/ui</Eyebrow>
|
|
40
|
+
* <Chip tone="accent" variant="outline">0.1.0</Chip>
|
|
41
|
+
* </AppBar.Brand>
|
|
42
|
+
* <AppBar.Nav aria-label="Sections">
|
|
43
|
+
* <AppBar.Link render={<NextLink href="/docs" />}>Docs</AppBar.Link>
|
|
44
|
+
* <AppBar.Separator />
|
|
45
|
+
* <AppBar.Link render={<NextLink href="/gallery" />} register="strong">Gallery</AppBar.Link>
|
|
46
|
+
* </AppBar.Nav>
|
|
47
|
+
* <AppBar.Actions>
|
|
48
|
+
* <ThemePicker />
|
|
49
|
+
* </AppBar.Actions>
|
|
50
|
+
* </AppBar.Root>
|
|
51
|
+
*/
|
|
52
|
+
const AppBarRoot = React.forwardRef(function AppBarRoot({ className, sticky = true, ...elementProps }, ref) {
|
|
53
|
+
return /* @__PURE__ */ jsx("header", {
|
|
54
|
+
ref,
|
|
55
|
+
"data-slot": "app-bar",
|
|
56
|
+
className: cn("flex h-(--cue-chrome-toolbar) w-full shrink-0 items-center gap-(--cue-space-4) border-b border-border bg-surface-1/95 px-(--cue-space-5) backdrop-blur", sticky ? "sticky top-0 z-40" : null, className),
|
|
57
|
+
...elementProps
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* The leading slot, and the way home.
|
|
62
|
+
*
|
|
63
|
+
* A link rather than a box, because that is what a brand in a bar is: pass
|
|
64
|
+
* `href`, or `render={<NextLink href="/" />}` to go through a router. It sets
|
|
65
|
+
* the bar's own type register (mono, UI size, full-contrast ink) so a wordmark
|
|
66
|
+
* and a version chip sit on one baseline without a class of their own.
|
|
67
|
+
*
|
|
68
|
+
* `shrink-0`: the brand is the one thing in the bar that must never be
|
|
69
|
+
* compressed, because a half-drawn wordmark reads as a broken page.
|
|
70
|
+
*/
|
|
71
|
+
const AppBarBrand = React.forwardRef(function AppBarBrand({ className, ...linkProps }, ref) {
|
|
72
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
73
|
+
ref,
|
|
74
|
+
className: cn("flex shrink-0 items-center gap-(--cue-space-3) font-mono text-(length:--cue-text-ui) text-fg", className),
|
|
75
|
+
...linkProps
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
/** Where a nav that does not fill its frame sits. */
|
|
79
|
+
const NAV_ALIGN = {
|
|
80
|
+
start: "justify-start",
|
|
81
|
+
center: "justify-center",
|
|
82
|
+
end: "justify-end"
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The `<nav>` landmark between the brand and the actions, and the one part of
|
|
86
|
+
* the bar that gives up width.
|
|
87
|
+
*
|
|
88
|
+
* Name it: `aria-label="Sections"`, or whatever this bar's links are. A
|
|
89
|
+
* document with more than one navigation and no names on them is a screen
|
|
90
|
+
* reader listing "navigation, navigation".
|
|
91
|
+
*
|
|
92
|
+
* The alignment sits on the frame rather than on the scrolling `<nav>` itself,
|
|
93
|
+
* and that is load-bearing: `justify-content: flex-end` on a scroll container
|
|
94
|
+
* pushes the overflow off the *start* edge, where several browsers cannot
|
|
95
|
+
* scroll to reach it. Aligning the frame and letting the nav grow from its
|
|
96
|
+
* start keeps every link reachable at every width.
|
|
97
|
+
*/
|
|
98
|
+
const AppBarNav = React.forwardRef(function AppBarNav({ className, frameClassName, align = "end", overflow = "scroll", previousLabel = "Show earlier links", nextLabel = "Show later links", ...elementProps }, ref) {
|
|
99
|
+
return /* @__PURE__ */ jsx(EdgeScrollNav, {
|
|
100
|
+
ref,
|
|
101
|
+
"data-slot": "app-bar-nav",
|
|
102
|
+
scrolls: overflow === "scroll",
|
|
103
|
+
previousLabel,
|
|
104
|
+
nextLabel,
|
|
105
|
+
frameClassName: cn("min-w-0 flex-1", NAV_ALIGN[align], frameClassName),
|
|
106
|
+
className: cn("flex min-w-0 items-center gap-(--cue-space-4)", className),
|
|
107
|
+
...elementProps
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
const REGISTERS = {
|
|
111
|
+
muted: "text-fg-muted",
|
|
112
|
+
strong: "text-fg"
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* The horizontal bar a site or an application wears across the top: brand slot,
|
|
116
|
+
* navigation slot, actions slot, one chrome rung tall.
|
|
117
|
+
*
|
|
118
|
+
* `AppShell` is the frame for a screen that owns the viewport; this is the bar
|
|
119
|
+
* for a document that scrolls — a marketing page, a documentation site, a
|
|
120
|
+
* console's public half. `NavigationMenu` is neither: it is the *menu* that
|
|
121
|
+
* goes inside `AppBar.Nav` when a section has children.
|
|
122
|
+
*
|
|
123
|
+
* Six parts. `Root` is the `<header>`, `Brand` the leading link, `Nav` the
|
|
124
|
+
* `<nav>` landmark that takes the remaining width and scrolls when the links
|
|
125
|
+
* outgrow it, `Link` one destination in one of two registers, `Separator` the
|
|
126
|
+
* seam between clusters, and `Actions` the trailing controls.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* <AppBar.Root>
|
|
130
|
+
* <AppBar.Brand render={<NextLink href="/" />}>
|
|
131
|
+
* <Eyebrow>@cueplusplus/ui</Eyebrow>
|
|
132
|
+
* </AppBar.Brand>
|
|
133
|
+
* <AppBar.Nav aria-label="Sections">
|
|
134
|
+
* <AppBar.Link render={<NextLink href="/docs" />}>Docs</AppBar.Link>
|
|
135
|
+
* <AppBar.Link render={<NextLink href="/theming" />}>Theming</AppBar.Link>
|
|
136
|
+
* <AppBar.Separator />
|
|
137
|
+
* <AppBar.Link render={<NextLink href="/gallery" />} register="strong">Gallery</AppBar.Link>
|
|
138
|
+
* <AppBar.Link external render={<a href="https://skills.cueplusplus.com" />}>Skills</AppBar.Link>
|
|
139
|
+
* </AppBar.Nav>
|
|
140
|
+
* <AppBar.Separator />
|
|
141
|
+
* <AppBar.Actions><ThemePicker /></AppBar.Actions>
|
|
142
|
+
* </AppBar.Root>
|
|
143
|
+
*/
|
|
144
|
+
const AppBar = {
|
|
145
|
+
Root: AppBarRoot,
|
|
146
|
+
Brand: AppBarBrand,
|
|
147
|
+
Nav: AppBarNav,
|
|
148
|
+
Link: React.forwardRef(function AppBarLink({ className, register = "muted", active = false, ...linkProps }, ref) {
|
|
149
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
150
|
+
ref,
|
|
151
|
+
"data-active": active ? "" : void 0,
|
|
152
|
+
"aria-current": active ? "page" : void 0,
|
|
153
|
+
className: cn("shrink-0 font-mono text-(length:--cue-text-ui)", active ? "text-accent" : REGISTERS[register], className),
|
|
154
|
+
...linkProps
|
|
155
|
+
});
|
|
156
|
+
}),
|
|
157
|
+
Separator: React.forwardRef(function AppBarSeparator({ className, ...elementProps }, ref) {
|
|
158
|
+
return /* @__PURE__ */ jsx(Separator, {
|
|
159
|
+
ref,
|
|
160
|
+
orientation: "vertical",
|
|
161
|
+
className: cn("h-(--cue-space-5)", className),
|
|
162
|
+
...elementProps
|
|
163
|
+
});
|
|
164
|
+
}),
|
|
165
|
+
Actions: React.forwardRef(function AppBarActions({ className, ...elementProps }, ref) {
|
|
166
|
+
return /* @__PURE__ */ jsx("div", {
|
|
167
|
+
ref,
|
|
168
|
+
"data-slot": "app-bar-actions",
|
|
169
|
+
className: cn("flex shrink-0 items-center gap-(--cue-space-4)", className),
|
|
170
|
+
...elementProps
|
|
171
|
+
});
|
|
172
|
+
})
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
export { AppBar };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { SidebarLandmark, SidebarRootProps } from "../layout/sidebar.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
//#region src/chrome/app-shell.d.ts
|
|
4
|
+
interface AppShellRootProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
5
|
+
/** Whether the mobile navigation drawer is open, when controlled. Pass with `onNavOpenChange`. */
|
|
6
|
+
navOpen?: boolean;
|
|
7
|
+
/** Initial open state of the mobile navigation drawer when uncontrolled. Defaults to `false`. */
|
|
8
|
+
defaultNavOpen?: boolean;
|
|
9
|
+
/** Called with the next open state whenever the drawer is opened or dismissed. */
|
|
10
|
+
onNavOpenChange?: (open: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
type AppShellBodyProps = React.ComponentPropsWithoutRef<"div">;
|
|
13
|
+
interface AppShellSidebarProps extends SidebarRootProps {
|
|
14
|
+
/** The drawer's heading, and its accessible name. Defaults to `"Navigation"`. */
|
|
15
|
+
drawerTitle?: React.ReactNode;
|
|
16
|
+
/** Accessible name for the drawer's close affordance. Defaults to `"Close navigation"`. */
|
|
17
|
+
closeLabel?: string;
|
|
18
|
+
/** Extra classes for the drawer's popup surface. */
|
|
19
|
+
drawerClassName?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Draw the column's collapse rail. Defaults to `true`.
|
|
22
|
+
*
|
|
23
|
+
* The shell renders the `Sidebar.Rail` itself rather than taking one in
|
|
24
|
+
* `children`, because collapsing is a property of the *column* and the same
|
|
25
|
+
* children are also the drawer's: a rail passed as a child would be laid out
|
|
26
|
+
* inside the overlay, where there is nothing to collapse.
|
|
27
|
+
*/
|
|
28
|
+
collapsible?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Which landmark **both** shapes announce as. Defaults to `"navigation"`.
|
|
31
|
+
*
|
|
32
|
+
* One prop rather than two, because the column and the drawer are one
|
|
33
|
+
* navigation wearing two shapes: the answer cannot differ by viewport.
|
|
34
|
+
*/
|
|
35
|
+
landmark?: SidebarLandmark;
|
|
36
|
+
}
|
|
37
|
+
interface AppShellNavTriggerProps extends Omit<React.ComponentPropsWithoutRef<"button">, "children"> {
|
|
38
|
+
/** Accessible name for the control. Defaults to `"Open navigation"`. */
|
|
39
|
+
label?: string;
|
|
40
|
+
/** The icon it draws. Defaults to the shell's own three-stroke glyph. */
|
|
41
|
+
icon?: React.ElementType;
|
|
42
|
+
/**
|
|
43
|
+
* Which rung of the control ladder the button takes. Defaults to `"md"`.
|
|
44
|
+
*
|
|
45
|
+
* The rest of the window furniture in this library is built at `"sm"`, and
|
|
46
|
+
* that is right for a mouse — but this control renders *only* below the `md`
|
|
47
|
+
* breakpoint, which is to say only on a touch screen, where `sm` (20px at
|
|
48
|
+
* normal density) is under the 24×24 CSS-pixel floor WCAG 2.2 SC 2.5.8 sets.
|
|
49
|
+
* `"md"` is 24px at normal and large, and the token layer's
|
|
50
|
+
* `@media (pointer: coarse)` rule lifts ultra-compact to the same 24px, so
|
|
51
|
+
* the default clears the floor at every density a finger can reach it at.
|
|
52
|
+
*/
|
|
53
|
+
size?: "sm" | "md" | "lg";
|
|
54
|
+
}
|
|
55
|
+
interface AppShellContentProps extends React.ComponentPropsWithoutRef<"main"> {
|
|
56
|
+
/**
|
|
57
|
+
* Render the `<main>` landmark. Defaults to `true`.
|
|
58
|
+
*
|
|
59
|
+
* Set it `false` when the shell is embedded in a document that already has a
|
|
60
|
+
* `<main>` — a specimen, a screenshot, a shell shown inside a device frame on
|
|
61
|
+
* a page of prose. A document may have only one, and a nested one is a defect
|
|
62
|
+
* rather than a nicety.
|
|
63
|
+
*/
|
|
64
|
+
main?: boolean;
|
|
65
|
+
}
|
|
66
|
+
interface AppShellScrollerProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
67
|
+
/** Draw the standard work-area gutters. Defaults to `true`. */
|
|
68
|
+
padded?: boolean;
|
|
69
|
+
}
|
|
70
|
+
interface AppShellAsideProps extends React.ComponentPropsWithoutRef<"aside"> {
|
|
71
|
+
/** Width of the column. Any CSS length. Defaults to `"18rem"`. */
|
|
72
|
+
width?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The console frame: title bar, navigation, work area, status bar — one
|
|
76
|
+
* viewport tall, with exactly one thing scrolling.
|
|
77
|
+
*
|
|
78
|
+
* Every console screen in this system is the same five parts in the same
|
|
79
|
+
* arrangement, and the value here is not the parts (they already exist as
|
|
80
|
+
* `TitleBar`, `Sidebar`, `Toolbar` and `StatusBar`) but the arithmetic between
|
|
81
|
+
* them: `h-dvh` on the frame rather than `min-h-dvh`, an unbroken `min-h-0`
|
|
82
|
+
* chain from the frame down to the scroller, `min-w-0` on the content column,
|
|
83
|
+
* and a scroller that is not the document. Get one of those wrong and the
|
|
84
|
+
* status bar leaves the screen or the navigation column gets shoved off the
|
|
85
|
+
* side; the shell exists so no page has to get them right again.
|
|
86
|
+
*
|
|
87
|
+
* The bars are ordinary children — `TitleBar` and `StatusBar` are already
|
|
88
|
+
* `shrink-0` and need no wrapper — so a shell reads as the page's outline.
|
|
89
|
+
*
|
|
90
|
+
* This is also the only component in the library with a viewport breakpoint:
|
|
91
|
+
* below `md` the navigation column becomes a drawer and a trigger appears in
|
|
92
|
+
* the bar. See {@link AppShellSidebar} for the ruling.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* <AppShell.Root>
|
|
96
|
+
* <TitleBar center="main-stage.cue" actions={<Chip tone="ok">live</Chip>}>
|
|
97
|
+
* <AppShell.NavTrigger />
|
|
98
|
+
* <Eyebrow>CUE++</Eyebrow>
|
|
99
|
+
* </TitleBar>
|
|
100
|
+
* <AppShell.Body>
|
|
101
|
+
* <AppShell.Sidebar aria-label="Console" drawerTitle="Console">
|
|
102
|
+
* <Sidebar.Section label="Show">
|
|
103
|
+
* <Sidebar.Item icon={List} active>Cues</Sidebar.Item>
|
|
104
|
+
* </Sidebar.Section>
|
|
105
|
+
* </AppShell.Sidebar>
|
|
106
|
+
* <AppShell.Content>
|
|
107
|
+
* <Toolbar.Root aria-label="Playback"><Toolbar.Button icon={Play}>Go</Toolbar.Button></Toolbar.Root>
|
|
108
|
+
* <AppShell.Scroller><Panel>…</Panel></AppShell.Scroller>
|
|
109
|
+
* </AppShell.Content>
|
|
110
|
+
* <AppShell.Aside aria-label="Inspector">…</AppShell.Aside>
|
|
111
|
+
* </AppShell.Body>
|
|
112
|
+
* <StatusBar><StatusBar.Item tone="ok">Art-Net</StatusBar.Item></StatusBar>
|
|
113
|
+
* </AppShell.Root>
|
|
114
|
+
*/
|
|
115
|
+
declare const AppShell: {
|
|
116
|
+
Root: React.ForwardRefExoticComponent<AppShellRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
117
|
+
Body: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
118
|
+
Sidebar: React.ForwardRefExoticComponent<AppShellSidebarProps & React.RefAttributes<HTMLElement>>;
|
|
119
|
+
NavTrigger: React.ForwardRefExoticComponent<AppShellNavTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
120
|
+
Content: React.ForwardRefExoticComponent<AppShellContentProps & React.RefAttributes<HTMLElement>>;
|
|
121
|
+
Scroller: React.ForwardRefExoticComponent<AppShellScrollerProps & React.RefAttributes<HTMLDivElement>>;
|
|
122
|
+
Aside: React.ForwardRefExoticComponent<AppShellAsideProps & React.RefAttributes<HTMLElement>>;
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { AppShell, AppShellAsideProps, AppShellBodyProps, AppShellContentProps, AppShellNavTriggerProps, AppShellRootProps, AppShellScrollerProps, AppShellSidebarProps };
|