@cueplusplus/ui 0.18.2 → 0.19.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 +61 -0
- package/dist/brand/_motif-seed.js +20 -0
- package/dist/brand/gloss-object.d.ts +23 -0
- package/dist/brand/gloss-object.js +108 -0
- package/dist/brand/halftone.d.ts +23 -0
- package/dist/brand/halftone.js +54 -0
- package/dist/brand/index.d.ts +6 -1
- package/dist/brand/motif.d.ts +32 -0
- package/dist/brand/motif.js +71 -0
- package/dist/brand/particle-mark.d.ts +29 -0
- package/dist/brand/particle-mark.js +137 -0
- package/dist/brand/ribbon.d.ts +20 -0
- package/dist/brand/ribbon.js +54 -0
- package/dist/chrome/announcement-bar.d.ts +30 -0
- package/dist/chrome/announcement-bar.js +38 -0
- package/dist/chrome/app-bar.d.ts +4 -1
- package/dist/chrome/app-bar.js +7 -2
- package/dist/chrome/app-shell.js +8 -1
- package/dist/chrome/band.d.ts +3 -1
- package/dist/chrome/band.js +4 -3
- package/dist/chrome/hero.d.ts +61 -0
- package/dist/chrome/hero.js +108 -0
- package/dist/chrome/index.d.ts +6 -1
- package/dist/chrome/index.js +6 -1
- package/dist/chrome/keyword.d.ts +22 -0
- package/dist/chrome/keyword.js +28 -0
- package/dist/chrome/logo-strip.d.ts +23 -0
- package/dist/chrome/logo-strip.js +43 -0
- package/dist/chrome/page-shell.js +13 -1
- package/dist/chrome/stat-band.d.ts +32 -0
- package/dist/chrome/stat-band.js +39 -0
- package/dist/dmx/channel-matrix.js +5 -3
- package/dist/dmx/dmx-strip.js +5 -3
- package/dist/elements/animated-number.js +4 -1
- package/dist/elements/replay.d.ts +1 -1
- package/dist/elements/replay.js +5 -2
- package/dist/elements/scroll-anchor.js +4 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +14 -2
- package/dist/instruments/index.js +1 -1
- package/dist/instruments/stat.js +1 -1
- package/dist/instruments/terminal-frame.js +5 -2
- package/dist/layout/grid.js +4 -2
- package/dist/layout/preview.js +4 -188
- package/dist/layout/sidebar.js +88 -52
- package/dist/overlays/_surface.d.ts +2 -2
- package/dist/primitives/button.js +22 -3
- package/dist/primitives/chip.js +62 -6
- package/dist/styles.css +26 -0
- package/dist/system/_scoped-pairing.js +18 -0
- package/dist/system/_stage-scope.js +199 -0
- package/dist/system/_style-check.js +1 -1
- package/dist/system/_style-chip.js +1 -1
- package/dist/system/_style-group-label.js +7 -3
- package/dist/system/_style-hero-title.js +41 -0
- package/dist/system/_style-kbd.js +1 -1
- package/dist/system/_style-label-ink.js +12 -5
- package/dist/system/_style-menu.js +1 -1
- package/dist/system/_style-page-title.js +1 -1
- package/dist/system/_style-segmented-control.js +1 -1
- package/dist/system/_style-skeleton.js +1 -1
- package/dist/system/_style-tab.js +1 -1
- package/dist/system/font-scope.d.ts +38 -0
- package/dist/system/font-scope.js +69 -0
- package/dist/system/index.d.ts +3 -1
- package/dist/system/index.js +3 -1
- package/dist/system/portal.js +7 -1
- package/dist/system/still.d.ts +32 -0
- package/dist/system/still.js +47 -0
- package/manifest/components/alert-dialog.json +1 -1
- package/manifest/components/announcement-bar.json +82 -0
- package/manifest/components/app-bar.json +3 -2
- package/manifest/components/app-shell.json +3 -0
- package/manifest/components/autocomplete.json +1 -0
- package/manifest/components/band.json +2 -1
- package/manifest/components/bento.json +12 -12
- package/manifest/components/button.json +1 -0
- package/manifest/components/channel-matrix.json +1 -0
- package/manifest/components/chart-tooltip-content.json +1 -0
- package/manifest/components/checkbox-group.json +1 -1
- package/manifest/components/checkbox.json +1 -1
- package/manifest/components/chip.json +1 -0
- package/manifest/components/combobox.json +1 -0
- package/manifest/components/command-palette.json +1 -0
- package/manifest/components/context-menu.json +1 -1
- package/manifest/components/density.json +6 -2
- package/manifest/components/dialog.json +1 -1
- package/manifest/components/dmx-bar.json +1 -1
- package/manifest/components/dmx-strip.json +1 -1
- package/manifest/components/drawer.json +1 -1
- package/manifest/components/dropdown-menu.json +1 -0
- package/manifest/components/font-scope.json +81 -0
- package/manifest/components/footer.json +0 -1
- package/manifest/components/gloss-object.json +81 -0
- package/manifest/components/grid.json +6 -6
- package/manifest/components/halftone.json +79 -0
- package/manifest/components/hero.json +204 -0
- package/manifest/components/hover-card.json +1 -1
- package/manifest/components/kbd.json +1 -0
- package/manifest/components/keyword.json +67 -0
- package/manifest/components/ledger.json +4 -4
- package/manifest/components/logo-strip.json +93 -0
- package/manifest/components/markdown-text.json +2 -2
- package/manifest/components/menubar.json +1 -1
- package/manifest/components/motif.json +90 -0
- package/manifest/components/multi-select.json +1 -0
- package/manifest/components/navigation-menu.json +1 -0
- package/manifest/components/page-shell.json +5 -0
- package/manifest/components/particle-mark.json +75 -0
- package/manifest/components/popover.json +1 -1
- package/manifest/components/preview.json +9 -4
- package/manifest/components/radio-group.json +0 -1
- package/manifest/components/radio.json +0 -1
- package/manifest/components/ribbon.json +65 -0
- package/manifest/components/scrollable-tabs-list.json +1 -0
- package/manifest/components/segmented-control.json +1 -0
- package/manifest/components/sheet.json +1 -1
- package/manifest/components/sidebar.json +1 -0
- package/manifest/components/skeleton.json +1 -0
- package/manifest/components/stack.json +4 -4
- package/manifest/components/stat-band.json +68 -0
- package/manifest/components/stat.json +1 -1
- package/manifest/components/still-provider.json +60 -0
- package/manifest/components/switch.json +0 -1
- package/manifest/components/table.json +1 -1
- package/manifest/components/tabs.json +1 -0
- package/manifest/components/tags-input.json +1 -0
- package/manifest/components/theme-configurator.json +1 -0
- package/manifest/components/theme-provider.json +9 -4
- package/manifest/components/toast.json +1 -0
- package/manifest/components/tooltip.json +1 -1
- package/manifest/fixtures.json +18 -0
- package/manifest/manifest.json +267 -55
- package/manifest/tokens.json +91 -3
- package/package.json +4 -4
- package/dist/system/_paint-ground-pattern.js +0 -30
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/chrome/announcement-bar.d.ts
|
|
3
|
+
/** The bar's one link. */
|
|
4
|
+
interface AnnouncementBarAction {
|
|
5
|
+
/** The link's text, e.g. "Read the notes". */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Where it goes. */
|
|
8
|
+
href: string;
|
|
9
|
+
}
|
|
10
|
+
interface AnnouncementBarProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
11
|
+
/** The one line. */
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/** One link, read as the bar's action. */
|
|
14
|
+
action?: AnnouncementBarAction;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One line across the top of a page, and one link: a launch, a change, a date.
|
|
18
|
+
*
|
|
19
|
+
* Filled in the accent, centred, and as short as its line. It is not a
|
|
20
|
+
* notification: it says the same thing to every reader and cannot be dismissed,
|
|
21
|
+
* so use `Toast` or `Alert` for anything that is about the reader.
|
|
22
|
+
*
|
|
23
|
+
* Static markup — no `"use client"`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* <AnnouncementBar action={{ label: "Learn more", href: "/changelog" }}>Plans for teams are here.</AnnouncementBar>
|
|
27
|
+
*/
|
|
28
|
+
declare const AnnouncementBar: React.ForwardRefExoticComponent<AnnouncementBarProps & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { AnnouncementBar, AnnouncementBarAction, AnnouncementBarProps };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/chrome/announcement-bar.tsx
|
|
5
|
+
/**
|
|
6
|
+
* One line across the top of a page, and one link: a launch, a change, a date.
|
|
7
|
+
*
|
|
8
|
+
* Filled in the accent, centred, and as short as its line. It is not a
|
|
9
|
+
* notification: it says the same thing to every reader and cannot be dismissed,
|
|
10
|
+
* so use `Toast` or `Alert` for anything that is about the reader.
|
|
11
|
+
*
|
|
12
|
+
* Static markup — no `"use client"`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <AnnouncementBar action={{ label: "Learn more", href: "/changelog" }}>Plans for teams are here.</AnnouncementBar>
|
|
16
|
+
*/
|
|
17
|
+
const AnnouncementBar = React.forwardRef(function AnnouncementBar({ className, children, action, ...elementProps }, ref) {
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
19
|
+
ref,
|
|
20
|
+
"data-slot": "announcement-bar",
|
|
21
|
+
className: cn("flex w-full flex-wrap items-center justify-center gap-x-(--cue-space-3) bg-accent-fill px-(--cue-space-5) py-(--cue-space-2) text-center text-(length:--cue-text-ui) text-accent-fill-fg", className),
|
|
22
|
+
...elementProps,
|
|
23
|
+
children: [/* @__PURE__ */ jsx("span", { children }), action === void 0 ? null : /* @__PURE__ */ jsxs("a", {
|
|
24
|
+
href: action.href,
|
|
25
|
+
className: "font-semibold underline underline-offset-4",
|
|
26
|
+
children: [
|
|
27
|
+
action.label,
|
|
28
|
+
" ",
|
|
29
|
+
/* @__PURE__ */ jsx("span", {
|
|
30
|
+
"aria-hidden": "true",
|
|
31
|
+
children: "→"
|
|
32
|
+
})
|
|
33
|
+
]
|
|
34
|
+
})]
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//#endregion
|
|
38
|
+
export { AnnouncementBar };
|
package/dist/chrome/app-bar.d.ts
CHANGED
|
@@ -15,7 +15,10 @@ interface AppBarRootProps extends React.ComponentPropsWithoutRef<"header"> {
|
|
|
15
15
|
}
|
|
16
16
|
type AppBarBrandProps = Omit<LinkProps, "variant" | "external">;
|
|
17
17
|
interface AppBarNavProps extends Omit<React.ComponentPropsWithoutRef<"nav">, "children"> {
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Where the links sit when they are narrower than the space. Omitted, the style
|
|
20
|
+
* set's `style-nav-align` decides, and without one it is `"end"`.
|
|
21
|
+
*/
|
|
19
22
|
align?: "start" | "center" | "end";
|
|
20
23
|
/**
|
|
21
24
|
* What the navigation does when the links do not fit. Defaults to `"scroll"`.
|
package/dist/chrome/app-bar.js
CHANGED
|
@@ -89,6 +89,11 @@ const NAV_ALIGN = {
|
|
|
89
89
|
end: "justify-end"
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
92
|
+
* The style set's nav alignment (structural Looks, spec 2026-10-02 §3.5), for a nav
|
|
93
|
+
* whose caller named none: `flex-end` unset, which is what `justify-end` draws.
|
|
94
|
+
*/
|
|
95
|
+
const NAV_ALIGN_FROM_SET = "[justify-content:var(--cue-style-nav-align,flex-end)]";
|
|
96
|
+
/**
|
|
92
97
|
* The `<nav>` landmark between the brand and the actions, and the one part of
|
|
93
98
|
* the bar that gives up width.
|
|
94
99
|
*
|
|
@@ -102,14 +107,14 @@ const NAV_ALIGN = {
|
|
|
102
107
|
* scroll to reach it. Aligning the frame and letting the nav grow from its
|
|
103
108
|
* start keeps every link reachable at every width.
|
|
104
109
|
*/
|
|
105
|
-
const AppBarNav = React.forwardRef(function AppBarNav({ className, frameClassName, align
|
|
110
|
+
const AppBarNav = React.forwardRef(function AppBarNav({ className, frameClassName, align, overflow = "scroll", previousLabel = "Show earlier links", nextLabel = "Show later links", ...elementProps }, ref) {
|
|
106
111
|
return /* @__PURE__ */ jsx(EdgeScrollNav, {
|
|
107
112
|
ref,
|
|
108
113
|
"data-slot": "app-bar-nav",
|
|
109
114
|
scrolls: overflow === "scroll",
|
|
110
115
|
previousLabel,
|
|
111
116
|
nextLabel,
|
|
112
|
-
frameClassName: cn("min-w-0 flex-1", NAV_ALIGN[align], frameClassName),
|
|
117
|
+
frameClassName: cn("min-w-0 flex-1", align === void 0 ? NAV_ALIGN_FROM_SET : NAV_ALIGN[align], frameClassName),
|
|
113
118
|
className: cn("flex min-w-0 items-center gap-(--cue-space-4)", className),
|
|
114
119
|
...elementProps
|
|
115
120
|
});
|
package/dist/chrome/app-shell.js
CHANGED
|
@@ -10,6 +10,13 @@ import { DRAG_CLASSES, NO_DRAG_CLASSES } from "./_drag.js";
|
|
|
10
10
|
import * as React from "react";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region src/chrome/app-shell.tsx
|
|
13
|
+
/**
|
|
14
|
+
* A style set's page ground (structural Looks, spec 2026-10-02 §3.1): a gradient of
|
|
15
|
+
* the theme's own inks, tiled or not. The frame is the one element that covers the
|
|
16
|
+
* whole window, so the ground lands here. Unset, every reader falls back to the
|
|
17
|
+
* value it had — no image, `auto`, `no-repeat` — and the frame paints as it did.
|
|
18
|
+
*/
|
|
19
|
+
const PAGE_GROUND_CLASSES = "[background-image:var(--cue-style-page-ground-image,none)] [background-size:var(--cue-style-page-ground-tile,auto)] [background-repeat:var(--cue-style-page-ground-repeat,no-repeat)]";
|
|
13
20
|
const AppShellContext = React.createContext({
|
|
14
21
|
navOpen: false,
|
|
15
22
|
setNavOpen: () => {},
|
|
@@ -114,7 +121,7 @@ const AppShell = {
|
|
|
114
121
|
ref,
|
|
115
122
|
"data-slot": "app-shell",
|
|
116
123
|
"data-rail": rail ? "" : void 0,
|
|
117
|
-
className: cn("group/app-shell relative flex h-dvh flex-col overflow-clip bg-bg text-fg", TEXTURE_CLASSES, className),
|
|
124
|
+
className: cn("group/app-shell relative flex h-dvh flex-col overflow-clip bg-bg text-fg", TEXTURE_CLASSES, PAGE_GROUND_CLASSES, className),
|
|
118
125
|
style: {
|
|
119
126
|
[NAV_WIDTH_PROPERTY]: rail ? railWidth : navWidth,
|
|
120
127
|
...style
|
package/dist/chrome/band.d.ts
CHANGED
|
@@ -76,7 +76,9 @@ interface BandProps extends React.ComponentPropsWithoutRef<"section"> {
|
|
|
76
76
|
* nothing and they do not vanish symmetrically: `--cue-band-ground` is
|
|
77
77
|
* `transparent` on `:root` and on every set but `marketing` and `kiln`, so a
|
|
78
78
|
* `"ground"` paints nothing on most pages, while `--cue-band-rule` is `0px`
|
|
79
|
-
* under those two
|
|
79
|
+
* under those two and under `ledger` and `prism`, which separate bands by
|
|
80
|
+
* rhythm alone (and `prism`'s by a ground image, `style-band-ground-image`).
|
|
81
|
+
* Both are deliberate and nothing warns.
|
|
80
82
|
*
|
|
81
83
|
* **Two substitution boundaries, because `--cue-band-y` is a `calc()` resolved
|
|
82
84
|
* at the element that declares it.** A `<Density>` island nested inside a
|
package/dist/chrome/band.js
CHANGED
|
@@ -3,13 +3,12 @@ import { TEXTURE_CLASSES } from "../system/_paint-texture.js";
|
|
|
3
3
|
import { Container } from "../layout/container.js";
|
|
4
4
|
import { bandClasses } from "./_band.js";
|
|
5
5
|
import { BAND_BROAD_WIDTH, BAND_Y_HALVES } from "../system/_style-band.js";
|
|
6
|
-
import { GROUND_PATTERN_CLASSES } from "../system/_paint-ground-pattern.js";
|
|
7
6
|
import * as React from "react";
|
|
8
7
|
import { jsx } from "react/jsx-runtime";
|
|
9
8
|
//#region src/chrome/band.tsx
|
|
10
9
|
const SEPARATION_PAINT = {
|
|
11
10
|
rule: "border-(--cue-band-rule-color)",
|
|
12
|
-
ground: `bg-(--cue-band-ground)
|
|
11
|
+
ground: `bg-(--cue-band-ground) [background-image:var(--cue-paint-ground-pattern,none),var(--cue-style-band-ground-image,none)] [background-size:var(--cue-paint-ground-pattern-size,auto),auto] ${TEXTURE_CLASSES} before:[mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)] before:[-webkit-mask-image:linear-gradient(var(--cue-band-ground),var(--cue-band-ground)),var(--cue-paint-fade)]`,
|
|
13
12
|
bleed: null,
|
|
14
13
|
none: null
|
|
15
14
|
};
|
|
@@ -32,7 +31,9 @@ const SEPARATION_PAINT = {
|
|
|
32
31
|
* nothing and they do not vanish symmetrically: `--cue-band-ground` is
|
|
33
32
|
* `transparent` on `:root` and on every set but `marketing` and `kiln`, so a
|
|
34
33
|
* `"ground"` paints nothing on most pages, while `--cue-band-rule` is `0px`
|
|
35
|
-
* under those two
|
|
34
|
+
* under those two and under `ledger` and `prism`, which separate bands by
|
|
35
|
+
* rhythm alone (and `prism`'s by a ground image, `style-band-ground-image`).
|
|
36
|
+
* Both are deliberate and nothing warns.
|
|
36
37
|
*
|
|
37
38
|
* **Two substitution boundaries, because `--cue-band-y` is a `calc()` resolved
|
|
38
39
|
* at the element that declares it.** A `<Density>` island nested inside a
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/chrome/hero.d.ts
|
|
3
|
+
interface HeroProps extends Omit<React.ComponentPropsWithoutRef<"section">, "title"> {
|
|
4
|
+
/** A short line above the title, usually an `Eyebrow`. */
|
|
5
|
+
eyebrow?: React.ReactNode;
|
|
6
|
+
/** The title's first line. A hero without a title is a band; use `Band`. */
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The title's second line, drawn in the style set's second ink: the accent, a
|
|
10
|
+
* muted ink or the keyword gradient, by the Look. Omit it for a one-line title.
|
|
11
|
+
*/
|
|
12
|
+
titleSecond?: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* The title's heading level. `1`, the default, is a page's opening; `2` or `3`
|
|
15
|
+
* is a hero that opens a section under the page's own `<h1>`, or a preview of
|
|
16
|
+
* one on a page about something else.
|
|
17
|
+
*/
|
|
18
|
+
level?: 1 | 2 | 3;
|
|
19
|
+
/** The sentence under the title. */
|
|
20
|
+
lede?: React.ReactNode;
|
|
21
|
+
/** The calls to action, usually a primary and a secondary `Button`. */
|
|
22
|
+
actions?: React.ReactNode;
|
|
23
|
+
/** Art beside or behind the words: a `Motif`, an image, a product shot. */
|
|
24
|
+
media?: React.ReactNode;
|
|
25
|
+
/** A row of figures under the lede, usually a `StatBand`. */
|
|
26
|
+
stats?: React.ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* `page` draws the hero on the page's own ground. `inverse` draws it as a dark
|
|
29
|
+
* card on any page — its whole colour scope flipped, controls and focus included —
|
|
30
|
+
* which is how a report opens on a light page. Defaults to `"page"`.
|
|
31
|
+
*/
|
|
32
|
+
surface?: "page" | "inverse";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The opening of a page: a title, the line under it, what to do next, and art.
|
|
36
|
+
*
|
|
37
|
+
* **The Look decides the arrangement, not this component.** The grid reads the
|
|
38
|
+
* style set's hero recipe — `style-hero-columns`, `style-hero-align` and
|
|
39
|
+
* `style-hero-media-order` — so the same hero opens centred under one Look, with
|
|
40
|
+
* its art beside the words under another, and with the art first under a third,
|
|
41
|
+
* with no prop changed. Unset, it is one column, start-aligned, art after the
|
|
42
|
+
* words.
|
|
43
|
+
*
|
|
44
|
+
* **No media, one column**, whatever the recipe says: two columns with nothing in
|
|
45
|
+
* the second is half a page of nothing.
|
|
46
|
+
*
|
|
47
|
+
* `surface="inverse"` is a theme island in dark mode, so every ink inside it —
|
|
48
|
+
* the title, the lede, a button's fill and its focus ring — is the dark palette's,
|
|
49
|
+
* and an overlay opened from it inherits that. The island carries
|
|
50
|
+
* `data-cue-rescope`, where the token build re-declares the active set's and
|
|
51
|
+
* pack's inks, so the card is right in the first frame, before hydration.
|
|
52
|
+
*
|
|
53
|
+
* Static markup — no `"use client"`.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* <Hero eyebrow={<Eyebrow>Category report</Eyebrow>} title="Snacks & nuts" titleSecond="Range review"
|
|
57
|
+
* lede="Every recommendation has a boundary." actions={<Button>Open the report</Button>} />
|
|
58
|
+
*/
|
|
59
|
+
declare const Hero: React.ForwardRefExoticComponent<HeroProps & React.RefAttributes<HTMLElement>>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { Hero, HeroProps };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { ThemeProvider } from "../system/theme-provider.js";
|
|
3
|
+
import { BODY_VOICE } from "../system/_style-body.js";
|
|
4
|
+
import { LEDE_INK, LEDE_LEADING, LEDE_SIZE, LEDE_TRACKING } from "../system/_style-lede.js";
|
|
5
|
+
import { HERO_TITLE_VOICE } from "../system/_style-hero-title.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/chrome/hero.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The second title line's ink (spec 2026-10-02 §3.1, §15.2). A colour by default;
|
|
11
|
+
* under a set whose line takes the keyword gradient, the build restates the
|
|
12
|
+
* colour as the accent and derives a fill, a clip and a text fill, so the
|
|
13
|
+
* gradient paints the glyphs through `background-clip: text`. Each derived
|
|
14
|
+
* name's fallback is the property's own initial value, so a line no set
|
|
15
|
+
* touches computes exactly what it did. All of it sits behind
|
|
16
|
+
* `@supports (background-clip: text)`, so a browser that cannot clip keeps the
|
|
17
|
+
* solid accent; forced colours drop the image and the transparent fill, as
|
|
18
|
+
* `Keyword` does.
|
|
19
|
+
*/
|
|
20
|
+
const TITLE_SECOND = "block text-[color:var(--cue-style-hero-title-ink-2,var(--cue-fg))] supports-[background-clip:text]:[background-image:var(--cue-style-hero-title-fill-2,none)] supports-[background-clip:text]:[background-clip:var(--cue-style-hero-title-clip-2,border-box)] supports-[background-clip:text]:[-webkit-background-clip:var(--cue-style-hero-title-clip-2,border-box)] supports-[background-clip:text]:[-webkit-text-fill-color:var(--cue-style-hero-title-text-2,currentcolor)] forced-colors:[background-image:none] forced-colors:[-webkit-text-fill-color:CanvasText]";
|
|
21
|
+
/**
|
|
22
|
+
* The opening of a page: a title, the line under it, what to do next, and art.
|
|
23
|
+
*
|
|
24
|
+
* **The Look decides the arrangement, not this component.** The grid reads the
|
|
25
|
+
* style set's hero recipe — `style-hero-columns`, `style-hero-align` and
|
|
26
|
+
* `style-hero-media-order` — so the same hero opens centred under one Look, with
|
|
27
|
+
* its art beside the words under another, and with the art first under a third,
|
|
28
|
+
* with no prop changed. Unset, it is one column, start-aligned, art after the
|
|
29
|
+
* words.
|
|
30
|
+
*
|
|
31
|
+
* **No media, one column**, whatever the recipe says: two columns with nothing in
|
|
32
|
+
* the second is half a page of nothing.
|
|
33
|
+
*
|
|
34
|
+
* `surface="inverse"` is a theme island in dark mode, so every ink inside it —
|
|
35
|
+
* the title, the lede, a button's fill and its focus ring — is the dark palette's,
|
|
36
|
+
* and an overlay opened from it inherits that. The island carries
|
|
37
|
+
* `data-cue-rescope`, where the token build re-declares the active set's and
|
|
38
|
+
* pack's inks, so the card is right in the first frame, before hydration.
|
|
39
|
+
*
|
|
40
|
+
* Static markup — no `"use client"`.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* <Hero eyebrow={<Eyebrow>Category report</Eyebrow>} title="Snacks & nuts" titleSecond="Range review"
|
|
44
|
+
* lede="Every recommendation has a boundary." actions={<Button>Open the report</Button>} />
|
|
45
|
+
*/
|
|
46
|
+
const Hero = React.forwardRef(function Hero({ className, eyebrow, title, titleSecond, level = 1, lede, actions, media, stats, surface = "page", ...elementProps }, ref) {
|
|
47
|
+
const words = /* @__PURE__ */ jsxs("div", {
|
|
48
|
+
"data-slot": "hero-words",
|
|
49
|
+
className: "flex min-w-0 flex-col gap-(--cue-space-5) [align-items:var(--cue-style-hero-align,start)] [text-align:var(--cue-style-hero-align,start)]",
|
|
50
|
+
children: [
|
|
51
|
+
eyebrow,
|
|
52
|
+
/* @__PURE__ */ jsxs(level === 3 ? "h3" : level === 2 ? "h2" : "h1", {
|
|
53
|
+
"data-slot": "hero-title",
|
|
54
|
+
className: cn(HERO_TITLE_VOICE, "m-0"),
|
|
55
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
56
|
+
className: "block",
|
|
57
|
+
children: title
|
|
58
|
+
}), titleSecond === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
59
|
+
"data-slot": "hero-title-second",
|
|
60
|
+
className: TITLE_SECOND,
|
|
61
|
+
children: titleSecond
|
|
62
|
+
})]
|
|
63
|
+
}),
|
|
64
|
+
lede === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
65
|
+
"data-slot": "hero-lede",
|
|
66
|
+
className: cn(BODY_VOICE, LEDE_SIZE, LEDE_LEADING, LEDE_TRACKING, LEDE_INK, "m-0 max-w-[48ch]"),
|
|
67
|
+
children: lede
|
|
68
|
+
}),
|
|
69
|
+
actions === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
70
|
+
"data-slot": "hero-actions",
|
|
71
|
+
className: "flex flex-wrap items-center gap-(--cue-space-3) [justify-content:var(--cue-style-hero-align,start)]",
|
|
72
|
+
children: actions
|
|
73
|
+
}),
|
|
74
|
+
stats === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
75
|
+
"data-slot": "hero-stats",
|
|
76
|
+
className: "w-full",
|
|
77
|
+
children: stats
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
const body = /* @__PURE__ */ jsxs("div", {
|
|
82
|
+
"data-slot": "hero-grid",
|
|
83
|
+
"data-media": media === void 0 ? void 0 : "",
|
|
84
|
+
className: cn("grid w-full items-center gap-(--cue-space-7)", media === void 0 ? "grid-cols-1" : "grid-cols-1 md:[grid-template-columns:var(--cue-style-hero-columns,1fr)]"),
|
|
85
|
+
children: [words, media === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
86
|
+
"data-slot": "hero-media",
|
|
87
|
+
className: "min-w-0 [order:var(--cue-style-hero-media-order,1)]",
|
|
88
|
+
children: media
|
|
89
|
+
})]
|
|
90
|
+
});
|
|
91
|
+
return /* @__PURE__ */ jsx("section", {
|
|
92
|
+
ref,
|
|
93
|
+
"data-slot": "hero",
|
|
94
|
+
"data-surface": surface,
|
|
95
|
+
className: cn("w-full", className),
|
|
96
|
+
...elementProps,
|
|
97
|
+
children: surface === "inverse" ? /* @__PURE__ */ jsx(ThemeProvider, {
|
|
98
|
+
mode: "dark",
|
|
99
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
100
|
+
"data-cue-rescope": "",
|
|
101
|
+
className: "rounded-surface bg-bg p-(--cue-space-7) text-fg",
|
|
102
|
+
children: body
|
|
103
|
+
})
|
|
104
|
+
}) : body
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
export { Hero };
|
package/dist/chrome/index.d.ts
CHANGED
|
@@ -21,4 +21,9 @@ import { ScrollableTabsList, ScrollableTabsListProps, Tabs, TabsListProps, TabsP
|
|
|
21
21
|
import { TitleBar, TitleBarProps } from "./title-bar.js";
|
|
22
22
|
import { Tree, TreeProps, TreeVisibility, TreeVisibilityToggle, TreeVisibilityToggleProps } from "./tree.js";
|
|
23
23
|
import { Toolbar, ToolbarButtonProps, ToolbarGroupProps, ToolbarRootProps, ToolbarSeparatorProps } from "./toolbar.js";
|
|
24
|
-
|
|
24
|
+
import { AnnouncementBar, AnnouncementBarAction, AnnouncementBarProps } from "./announcement-bar.js";
|
|
25
|
+
import { Hero, HeroProps } from "./hero.js";
|
|
26
|
+
import { Keyword, KeywordProps } from "./keyword.js";
|
|
27
|
+
import { LogoStrip, LogoStripProps } from "./logo-strip.js";
|
|
28
|
+
import { StatBand, StatBandProps, StatBandStat } from "./stat-band.js";
|
|
29
|
+
export { AnnouncementBar, type AnnouncementBarAction, type AnnouncementBarProps, AppBar, type AppBarActionsProps, type AppBarBrandProps, type AppBarLinkProps, type AppBarNavProps, type AppBarRegister, type AppBarRootProps, type AppBarSeparatorProps, AppShell, type AppShellAsideProps, type AppShellBarProps, type AppShellBodyProps, type AppShellContentProps, type AppShellNavTriggerProps, type AppShellRootProps, type AppShellScrollerProps, type AppShellSidebarProps, Band, type BandProps, type BandSeparation, type BandWidth, type BarOverflow, EmptyState, type EmptyStateProps, Eyebrow, type EyebrowProps, Footer, type FooterProps, Hero, type HeroProps, Inspector, type InspectorFooterProps, type InspectorRootProps, type InspectorRowProps, type InspectorSectionProps, Keyword, type KeywordProps, LogoStrip, type LogoStripProps, NavigationMenu, type NavigationMenuContentProps, type NavigationMenuItemProps, type NavigationMenuLinkProps, type NavigationMenuListProps, type NavigationMenuRootProps, type NavigationMenuTriggerProps, type NavigationMenuViewportProps, PageShell, type PageShellProps, type PageWidth, Panel, PanelHeader, type PanelHeaderProps, type PanelProps, Row, type RowProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, type ScrollEdges, ScrollableTabsList, type ScrollableTabsListProps, SeamList, type SeamListProps, SectionHeader, type SectionHeaderProps, SegmentedControl, type SegmentedControlItem, type SegmentedControlProps, type SegmentedControlSize, StatBand, type StatBandProps, type StatBandStat, StatusBar, type StatusBarGroupProps, type StatusBarItemProps, type StatusBarProminence, type StatusBarProps, type StatusBarTone, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, type TabsListProps, type TabsPanelProps, type TabsRootProps, type TabsTabProps, TitleBar, type TitleBarProps, Toolbar, type ToolbarButtonProps, type ToolbarGroupProps, type ToolbarRootProps, type ToolbarSeparatorProps, Tree, type TreeGuide, type TreeItem, type TreeProps, type TreeRow, type TreeVisibility, TreeVisibilityToggle, type TreeVisibilityToggleProps, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
|
package/dist/chrome/index.js
CHANGED
|
@@ -19,4 +19,9 @@ import { ScrollableTabsList, Tabs } from "./tabs.js";
|
|
|
19
19
|
import { TitleBar } from "./title-bar.js";
|
|
20
20
|
import { Tree, TreeVisibilityToggle } from "./tree.js";
|
|
21
21
|
import { Toolbar } from "./toolbar.js";
|
|
22
|
-
|
|
22
|
+
import { AnnouncementBar } from "./announcement-bar.js";
|
|
23
|
+
import { Hero } from "./hero.js";
|
|
24
|
+
import { Keyword } from "./keyword.js";
|
|
25
|
+
import { LogoStrip } from "./logo-strip.js";
|
|
26
|
+
import { StatBand } from "./stat-band.js";
|
|
27
|
+
export { AnnouncementBar, AppBar, AppShell, Band, EmptyState, Eyebrow, Footer, Hero, Inspector, Keyword, LogoStrip, NavigationMenu, PageShell, Panel, PanelHeader, Row, ScrollArea, ScrollableTabsList, SeamList, SectionHeader, SegmentedControl, StatBand, StatusBar, TAB_FADE_PX, TREE_GUIDE_GLYPHS, Tabs, TitleBar, Toolbar, Tree, TreeVisibilityToggle, computeScrollEdges, edgeFadeMask, flattenTree, rowVariants };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/chrome/keyword.d.ts
|
|
3
|
+
interface KeywordProps extends React.ComponentPropsWithoutRef<"span"> {
|
|
4
|
+
/** The words to mark: one word or a short phrase in a title. */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A word in a title filled with the Look's keyword gradient.
|
|
9
|
+
*
|
|
10
|
+
* Reads `style-keyword-fill`, a gradient of the theme's own inks, through
|
|
11
|
+
* `background-clip: text`. Where the browser cannot clip a background to text, or
|
|
12
|
+
* in forced colours, it is the accent instead, so the word is never transparent.
|
|
13
|
+
* Unset, the fill is the accent itself.
|
|
14
|
+
*
|
|
15
|
+
* Static markup — no `"use client"`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <h1>The world's first <Keyword>predictive</Keyword> retail system.</h1>
|
|
19
|
+
*/
|
|
20
|
+
declare const Keyword: React.ForwardRefExoticComponent<KeywordProps & React.RefAttributes<HTMLSpanElement>>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { Keyword, KeywordProps };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/chrome/keyword.tsx
|
|
5
|
+
/**
|
|
6
|
+
* A word in a title filled with the Look's keyword gradient.
|
|
7
|
+
*
|
|
8
|
+
* Reads `style-keyword-fill`, a gradient of the theme's own inks, through
|
|
9
|
+
* `background-clip: text`. Where the browser cannot clip a background to text, or
|
|
10
|
+
* in forced colours, it is the accent instead, so the word is never transparent.
|
|
11
|
+
* Unset, the fill is the accent itself.
|
|
12
|
+
*
|
|
13
|
+
* Static markup — no `"use client"`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <h1>The world's first <Keyword>predictive</Keyword> retail system.</h1>
|
|
17
|
+
*/
|
|
18
|
+
const Keyword = React.forwardRef(function Keyword({ className, children, ...elementProps }, ref) {
|
|
19
|
+
return /* @__PURE__ */ jsx("span", {
|
|
20
|
+
ref,
|
|
21
|
+
"data-slot": "keyword",
|
|
22
|
+
className: cn("text-accent", "supports-[background-clip:text]:bg-[image:var(--cue-style-keyword-fill,linear-gradient(var(--cue-accent),var(--cue-accent)))] supports-[background-clip:text]:bg-clip-text supports-[background-clip:text]:text-transparent", "forced-colors:bg-none forced-colors:text-[CanvasText]", className),
|
|
23
|
+
...elementProps,
|
|
24
|
+
children
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Keyword };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/chrome/logo-strip.d.ts
|
|
3
|
+
interface LogoStripProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
4
|
+
/** The names, as text. A real company's logo is never drawn here. */
|
|
5
|
+
names: readonly string[];
|
|
6
|
+
/** The line above the row, e.g. "Trusted by teams at". */
|
|
7
|
+
caption?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A row of names under a caption: the "trusted by" strip of a product page.
|
|
11
|
+
*
|
|
12
|
+
* Text wordmarks only, set in the display face at a muted ink, never an image: a
|
|
13
|
+
* real company's mark is a trademark that is not this library's to draw, and a
|
|
14
|
+
* gallery page's names are fictional. The row wraps, and centres when it does.
|
|
15
|
+
*
|
|
16
|
+
* Static markup — no `"use client"`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <LogoStrip caption="Trusted by teams at" names={["Northwind", "Halcyon", "Kestrel"]} />
|
|
20
|
+
*/
|
|
21
|
+
declare const LogoStrip: React.ForwardRefExoticComponent<LogoStripProps & React.RefAttributes<HTMLDivElement>>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { LogoStrip, LogoStripProps };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { LABEL_INK, LABEL_INK_SUBTLE } from "../system/_style-label-ink.js";
|
|
3
|
+
import { LABEL_FACE } from "../system/_style-label-face.js";
|
|
4
|
+
import { LABEL_CASE } from "../system/_style-label-case.js";
|
|
5
|
+
import { LABEL_TRACKING } from "../system/_style-label-tracking.js";
|
|
6
|
+
import { LABEL_SIZE } from "../system/_style-label-size.js";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/chrome/logo-strip.tsx
|
|
10
|
+
/**
|
|
11
|
+
* A row of names under a caption: the "trusted by" strip of a product page.
|
|
12
|
+
*
|
|
13
|
+
* Text wordmarks only, set in the display face at a muted ink, never an image: a
|
|
14
|
+
* real company's mark is a trademark that is not this library's to draw, and a
|
|
15
|
+
* gallery page's names are fictional. The row wraps, and centres when it does.
|
|
16
|
+
*
|
|
17
|
+
* Static markup — no `"use client"`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <LogoStrip caption="Trusted by teams at" names={["Northwind", "Halcyon", "Kestrel"]} />
|
|
21
|
+
*/
|
|
22
|
+
const LogoStrip = React.forwardRef(function LogoStrip({ className, names, caption, ...elementProps }, ref) {
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
ref,
|
|
25
|
+
"data-slot": "logo-strip",
|
|
26
|
+
className: cn("flex flex-col items-center gap-(--cue-space-4)", className),
|
|
27
|
+
...elementProps,
|
|
28
|
+
children: [caption === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
29
|
+
"data-slot": "logo-strip-caption",
|
|
30
|
+
className: cn(LABEL_FACE, LABEL_SIZE, LABEL_CASE, LABEL_TRACKING, LABEL_INK_SUBTLE, "m-0"),
|
|
31
|
+
children: caption
|
|
32
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
33
|
+
role: "list",
|
|
34
|
+
className: "m-0 flex list-none flex-wrap items-center justify-center gap-x-(--cue-space-7) gap-y-(--cue-space-4) p-0",
|
|
35
|
+
children: names.map((name) => /* @__PURE__ */ jsx("li", {
|
|
36
|
+
className: cn("font-sans text-(length:--cue-text-title) font-semibold", LABEL_INK),
|
|
37
|
+
children: name
|
|
38
|
+
}, name))
|
|
39
|
+
})]
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
//#endregion
|
|
43
|
+
export { LogoStrip };
|
|
@@ -26,6 +26,18 @@ const WIDTHS = {
|
|
|
26
26
|
full: ""
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
|
+
* A style set's frame and grid lines (structural Looks, spec 2026-10-02 §3.1): a
|
|
30
|
+
* dashed hairline round the content column, and two hairlines at its edges. Each is
|
|
31
|
+
* a pseudo-element whose `content` is a token the build derives — `none` unless the
|
|
32
|
+
* set asks, which generates no box at all — and the shell is their containing block
|
|
33
|
+
* only while one is drawn, so a set without lines lays out and paints as before.
|
|
34
|
+
*/
|
|
35
|
+
const FRAME_LINES_CLASSES = [
|
|
36
|
+
"[position:var(--cue-style-page-frame-position,static)]",
|
|
37
|
+
"before:content-[var(--cue-style-frame-lines-content,none)] before:pointer-events-none before:absolute before:inset-0 before:border before:border-dashed before:border-(--cue-border)",
|
|
38
|
+
"after:content-[var(--cue-style-grid-lines-content,none)] after:pointer-events-none after:absolute after:inset-y-0 after:inset-x-(--cue-space-6) after:border-x after:border-(--cue-border)"
|
|
39
|
+
].join(" ");
|
|
40
|
+
/**
|
|
29
41
|
* A centred content column with a standard page header.
|
|
30
42
|
*
|
|
31
43
|
* The header is emitted only when something fills it, so a shell used purely
|
|
@@ -47,7 +59,7 @@ const PageShell = React.forwardRef(function PageShell({ className, width = "narr
|
|
|
47
59
|
return /* @__PURE__ */ jsxs("div", {
|
|
48
60
|
ref,
|
|
49
61
|
"data-slot": "page-shell",
|
|
50
|
-
className: cn("mx-auto flex w-full flex-col gap-(--cue-space-6) px-(--cue-space-6) py-(--cue-space-7)", WIDTHS[width], className),
|
|
62
|
+
className: cn("mx-auto flex w-full flex-col gap-(--cue-space-6) px-(--cue-space-6) py-(--cue-space-7)", FRAME_LINES_CLASSES, WIDTHS[width], className),
|
|
51
63
|
...elementProps,
|
|
52
64
|
children: [hasHeader ? /* @__PURE__ */ jsxs("header", {
|
|
53
65
|
"data-slot": "page-shell-header",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/chrome/stat-band.d.ts
|
|
3
|
+
interface StatBandStat {
|
|
4
|
+
/** What is measured. */
|
|
5
|
+
label: React.ReactNode;
|
|
6
|
+
/** The reading. */
|
|
7
|
+
value: React.ReactNode;
|
|
8
|
+
/** An optional second line: a delta, a window, a unit. */
|
|
9
|
+
note?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
interface StatBandProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
12
|
+
/** The figures, in reading order. One to six sit in one row; more wrap. */
|
|
13
|
+
stats: readonly StatBandStat[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A row of named figures: the KPI band under a report's title.
|
|
17
|
+
*
|
|
18
|
+
* Built from `Stat`, so each figure keeps its caption-over-reading voice and its
|
|
19
|
+
* tabular numerals. The style set decides the band's look: `style-stat-divider`
|
|
20
|
+
* draws a rule between figures, and `Stat` reads `style-stat-numeral-ink` for the
|
|
21
|
+
* reading's colour. Unset, there is no rule and the reading keeps its ink. A
|
|
22
|
+
* wrapped row's first figure draws no rule, so a narrow screen never shows a
|
|
23
|
+
* divider hanging off the left edge.
|
|
24
|
+
*
|
|
25
|
+
* Static markup — no `"use client"`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <StatBand stats={[{ label: "SKUs", value: "1,091" }, { label: "Brands", value: "148" }]} />
|
|
29
|
+
*/
|
|
30
|
+
declare const StatBand: React.ForwardRefExoticComponent<StatBandProps & React.RefAttributes<HTMLDivElement>>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { StatBand, StatBandProps, StatBandStat };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Stat } from "../instruments/stat.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/chrome/stat-band.tsx
|
|
6
|
+
/**
|
|
7
|
+
* A row of named figures: the KPI band under a report's title.
|
|
8
|
+
*
|
|
9
|
+
* Built from `Stat`, so each figure keeps its caption-over-reading voice and its
|
|
10
|
+
* tabular numerals. The style set decides the band's look: `style-stat-divider`
|
|
11
|
+
* draws a rule between figures, and `Stat` reads `style-stat-numeral-ink` for the
|
|
12
|
+
* reading's colour. Unset, there is no rule and the reading keeps its ink. A
|
|
13
|
+
* wrapped row's first figure draws no rule, so a narrow screen never shows a
|
|
14
|
+
* divider hanging off the left edge.
|
|
15
|
+
*
|
|
16
|
+
* Static markup — no `"use client"`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <StatBand stats={[{ label: "SKUs", value: "1,091" }, { label: "Brands", value: "148" }]} />
|
|
20
|
+
*/
|
|
21
|
+
const StatBand = React.forwardRef(function StatBand({ className, stats, ...elementProps }, ref) {
|
|
22
|
+
return /* @__PURE__ */ jsx("div", {
|
|
23
|
+
ref,
|
|
24
|
+
"data-slot": "stat-band",
|
|
25
|
+
className: cn("overflow-hidden", className),
|
|
26
|
+
...elementProps,
|
|
27
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
28
|
+
className: "-ms-(--cue-space-5) flex flex-wrap gap-y-(--cue-space-5)",
|
|
29
|
+
children: stats.map((stat, index) => /* @__PURE__ */ jsx(Stat, {
|
|
30
|
+
label: stat.label,
|
|
31
|
+
value: stat.value,
|
|
32
|
+
detail: stat.note,
|
|
33
|
+
className: "relative min-w-[10rem] flex-1 ps-(--cue-space-5) before:pointer-events-none before:absolute before:inset-y-0 before:start-0 before:[border-inline-start:var(--cue-style-stat-divider,none)] before:content-['']"
|
|
34
|
+
}, index))
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { StatBand };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { usePrefersReducedMotion } from "../instruments/_motion.js";
|
|
4
|
+
import { useStill } from "../system/still.js";
|
|
3
5
|
import { FOCUS_RING } from "../system/_focus-ring.js";
|
|
4
6
|
import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
|
|
5
7
|
import { useComposedRefs } from "../lib/compose.js";
|
|
6
8
|
import { useCuePortalProps } from "../system/portal.js";
|
|
7
|
-
import { usePrefersReducedMotion } from "../instruments/_motion.js";
|
|
8
9
|
import { DMX_RAMP_NAMES, activeChannels, dmxPalette, dmxPaletteColor, dmxPaletteInk } from "./_ramp.js";
|
|
9
10
|
import { DmxReadout } from "./_readout.js";
|
|
10
11
|
import * as React from "react";
|
|
@@ -77,8 +78,9 @@ const ChannelMatrix = React.forwardRef(function ChannelMatrix({ className, data,
|
|
|
77
78
|
universesRef.current = universes;
|
|
78
79
|
const [hover, setHover] = React.useState(null);
|
|
79
80
|
const portal = useCuePortalProps();
|
|
80
|
-
const
|
|
81
|
-
const
|
|
81
|
+
const prefersReduced = usePrefersReducedMotion();
|
|
82
|
+
const held = useStill();
|
|
83
|
+
const streaming = live && !(prefersReduced || held);
|
|
82
84
|
const paint = React.useCallback(() => {
|
|
83
85
|
const bytes = dataRef.current;
|
|
84
86
|
const palette = paletteRef.current;
|