@cloudgrid-io/ui 0.39.0 → 0.41.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/README.md +9 -5
- package/dist/badge/palette.d.ts +1 -1
- package/dist/badge/palette.d.ts.map +1 -1
- package/dist/badge/palette.js +2 -2
- package/dist/badge/palette.js.map +1 -1
- package/dist/blocks/action-link.d.ts +31 -0
- package/dist/blocks/action-link.d.ts.map +1 -0
- package/dist/blocks/action-link.js +40 -0
- package/dist/blocks/action-link.js.map +1 -0
- package/dist/blocks/activity-row.d.ts +42 -0
- package/dist/blocks/activity-row.d.ts.map +1 -0
- package/dist/blocks/activity-row.js +37 -0
- package/dist/blocks/activity-row.js.map +1 -0
- package/dist/blocks/add-tile.d.ts +31 -0
- package/dist/blocks/add-tile.d.ts.map +1 -0
- package/dist/blocks/add-tile.js +29 -0
- package/dist/blocks/add-tile.js.map +1 -0
- package/dist/blocks/avatar-stack.d.ts +40 -0
- package/dist/blocks/avatar-stack.d.ts.map +1 -0
- package/dist/blocks/avatar-stack.js +41 -0
- package/dist/blocks/avatar-stack.js.map +1 -0
- package/dist/blocks/avatar.d.ts +31 -5
- package/dist/blocks/avatar.d.ts.map +1 -1
- package/dist/blocks/avatar.js +57 -9
- package/dist/blocks/avatar.js.map +1 -1
- package/dist/blocks/copy-button.d.ts +59 -0
- package/dist/blocks/copy-button.d.ts.map +1 -0
- package/dist/blocks/copy-button.js +76 -0
- package/dist/blocks/copy-button.js.map +1 -0
- package/dist/blocks/dashed-banner.d.ts +36 -0
- package/dist/blocks/dashed-banner.d.ts.map +1 -0
- package/dist/blocks/dashed-banner.js +31 -0
- package/dist/blocks/dashed-banner.js.map +1 -0
- package/dist/blocks/dashed-line.d.ts +44 -0
- package/dist/blocks/dashed-line.d.ts.map +1 -0
- package/dist/blocks/dashed-line.js +46 -0
- package/dist/blocks/dashed-line.js.map +1 -0
- package/dist/blocks/empty-creations.d.ts +18 -1
- package/dist/blocks/empty-creations.d.ts.map +1 -1
- package/dist/blocks/empty-creations.js +25 -9
- package/dist/blocks/empty-creations.js.map +1 -1
- package/dist/blocks/entity-card.d.ts +37 -2
- package/dist/blocks/entity-card.d.ts.map +1 -1
- package/dist/blocks/entity-card.js +70 -7
- package/dist/blocks/entity-card.js.map +1 -1
- package/dist/blocks/filter-tabs.d.ts +45 -0
- package/dist/blocks/filter-tabs.d.ts.map +1 -0
- package/dist/blocks/filter-tabs.js +48 -0
- package/dist/blocks/filter-tabs.js.map +1 -0
- package/dist/blocks/header-menu.d.ts +54 -0
- package/dist/blocks/header-menu.d.ts.map +1 -0
- package/dist/blocks/header-menu.js +63 -0
- package/dist/blocks/header-menu.js.map +1 -0
- package/dist/blocks/header-pill.d.ts +41 -0
- package/dist/blocks/header-pill.d.ts.map +1 -0
- package/dist/blocks/header-pill.js +67 -0
- package/dist/blocks/header-pill.js.map +1 -0
- package/dist/blocks/icons.d.ts +47 -2
- package/dist/blocks/icons.d.ts.map +1 -1
- package/dist/blocks/icons.js +50 -2
- package/dist/blocks/icons.js.map +1 -1
- package/dist/blocks/menu-trigger.d.ts +35 -0
- package/dist/blocks/menu-trigger.d.ts.map +1 -0
- package/dist/blocks/menu-trigger.js +24 -0
- package/dist/blocks/menu-trigger.js.map +1 -0
- package/dist/blocks/meter.d.ts +9 -1
- package/dist/blocks/meter.d.ts.map +1 -1
- package/dist/blocks/meter.js +6 -2
- package/dist/blocks/meter.js.map +1 -1
- package/dist/blocks/phosphor-marks.d.ts +48 -2
- package/dist/blocks/phosphor-marks.d.ts.map +1 -1
- package/dist/blocks/phosphor-marks.js +72 -3
- package/dist/blocks/phosphor-marks.js.map +1 -1
- package/dist/blocks/plugged-dialog.d.ts +102 -0
- package/dist/blocks/plugged-dialog.d.ts.map +1 -0
- package/dist/blocks/plugged-dialog.js +93 -0
- package/dist/blocks/plugged-dialog.js.map +1 -0
- package/dist/blocks/ranked-list.d.ts +15 -1
- package/dist/blocks/ranked-list.d.ts.map +1 -1
- package/dist/blocks/ranked-list.js +18 -5
- package/dist/blocks/ranked-list.js.map +1 -1
- package/dist/blocks/scroll-rail.d.ts +15 -0
- package/dist/blocks/scroll-rail.d.ts.map +1 -0
- package/dist/blocks/scroll-rail.js +134 -0
- package/dist/blocks/scroll-rail.js.map +1 -0
- package/dist/blocks/section-heading.d.ts +21 -0
- package/dist/blocks/section-heading.d.ts.map +1 -0
- package/dist/blocks/section-heading.js +18 -0
- package/dist/blocks/section-heading.js.map +1 -0
- package/dist/blocks/side-nav.d.ts +12 -2
- package/dist/blocks/side-nav.d.ts.map +1 -1
- package/dist/blocks/side-nav.js +49 -15
- package/dist/blocks/side-nav.js.map +1 -1
- package/dist/blocks/stat-tile.d.ts +21 -1
- package/dist/blocks/stat-tile.d.ts.map +1 -1
- package/dist/blocks/stat-tile.js +11 -6
- package/dist/blocks/stat-tile.js.map +1 -1
- package/dist/console.css +55 -19
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/theme.css +36 -0
- package/dist/tokens/base.json +55 -5
- package/dist/tokens.css +46 -12
- package/dist/tokens.json +8 -3
- package/dist/ui/button.d.ts +2 -2
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +26 -0
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/dialog.d.ts +6 -0
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +7 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/drawer.d.ts +7 -5
- package/dist/ui/drawer.d.ts.map +1 -1
- package/dist/ui/drawer.js +7 -5
- package/dist/ui/drawer.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
5
|
+
import { RailNextIcon, RailPreviousIcon } from "./icons.js";
|
|
6
|
+
/**
|
|
7
|
+
* A horizontal row that scrolls: Home's My Creations and On the Grid rails
|
|
8
|
+
* (#382, spec §4.4 and §4.8, Figma scroll button `11355:10564`).
|
|
9
|
+
*
|
|
10
|
+
* ## What it draws
|
|
11
|
+
*
|
|
12
|
+
* Any nodes, one list item each, `gap` apart (default 20, the frames'), in a
|
|
13
|
+
* viewport that scrolls sideways and snaps each item's start edge. At either
|
|
14
|
+
* side that has more, a 64px fade into the rail's fade colour (the page colour
|
|
15
|
+
* by default, spec §8.6: the frames fade into pure white on a page-coloured
|
|
16
|
+
* page) and a 48px square button (white, radius 16, a 1px `--cg-tertiary-dark`
|
|
17
|
+
* line, a 20px arrow in the same teal), at the edge and vertically centred. At
|
|
18
|
+
* an end the button is disabled and invisible and its fade is gone, so a rail
|
|
19
|
+
* that fits draws neither.
|
|
20
|
+
*
|
|
21
|
+
* ## Measured, so the server draws no controls
|
|
22
|
+
*
|
|
23
|
+
* Which side has more is a measurement, and the server has no layout. The
|
|
24
|
+
* first paint shows the row alone; the browser measures on mount, on scroll and
|
|
25
|
+
* whenever the viewport or the list changes size (a `ResizeObserver`), so a
|
|
26
|
+
* window resized from overflowing to fitting drops its controls.
|
|
27
|
+
*
|
|
28
|
+
* ## A press
|
|
29
|
+
*
|
|
30
|
+
* Moves by the viewport's width less one fade, so the item that was half under
|
|
31
|
+
* the fade becomes the first one shown; the snap then settles it on its edge.
|
|
32
|
+
* Smooth, or instant under `prefers-reduced-motion: reduce`.
|
|
33
|
+
*
|
|
34
|
+
* ## Keyboard and touch
|
|
35
|
+
*
|
|
36
|
+
* The viewport is a named, focusable region, so the arrow keys scroll it even
|
|
37
|
+
* when the items hold nothing focusable; a focused card inside it is scrolled
|
|
38
|
+
* into view by the browser. A button that reaches its end hands focus to the
|
|
39
|
+
* other one, so pressing Enter through the row never drops focus to the page. On a coarse pointer the buttons are not drawn
|
|
40
|
+
* (`pointer-coarse:hidden`): the row scrolls under a finger, and the fades stay
|
|
41
|
+
* to say there is more.
|
|
42
|
+
*
|
|
43
|
+
* The line and the arrow are `--cg-tertiary-dark` (ui#381), the frames'
|
|
44
|
+
* Tertiary Dark.
|
|
45
|
+
*/
|
|
46
|
+
/** The fade's width, and how much of the viewport a press does not travel. */
|
|
47
|
+
const FADE = 64;
|
|
48
|
+
/** How long a press counts for the focus hand-off: longer than a smooth scroll. */
|
|
49
|
+
const PRESS_WINDOW = 1000;
|
|
50
|
+
const useIsoLayoutEffect = typeof document === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
51
|
+
function ScrollRail({ children, label, gap = 20, previousLabel = "Scroll back", nextLabel = "Scroll forward", className, style, ...props }) {
|
|
52
|
+
const id = React.useId();
|
|
53
|
+
const viewport = React.useRef(null);
|
|
54
|
+
const list = React.useRef(null);
|
|
55
|
+
const startButton = React.useRef(null);
|
|
56
|
+
const endButton = React.useRef(null);
|
|
57
|
+
// Both "at the end" until measured: the server draws no controls.
|
|
58
|
+
const [edges, setEdges] = React.useState({ start: true, end: true });
|
|
59
|
+
const measure = React.useCallback(() => {
|
|
60
|
+
const el = viewport.current;
|
|
61
|
+
if (!el)
|
|
62
|
+
return;
|
|
63
|
+
const start = el.scrollLeft <= 1;
|
|
64
|
+
const end = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1;
|
|
65
|
+
setEdges((prev) => (prev.start === start && prev.end === end ? prev : { start, end }));
|
|
66
|
+
}, []);
|
|
67
|
+
useIsoLayoutEffect(() => {
|
|
68
|
+
const el = viewport.current;
|
|
69
|
+
if (!el)
|
|
70
|
+
return;
|
|
71
|
+
measure();
|
|
72
|
+
el.addEventListener("scroll", measure, { passive: true });
|
|
73
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
|
|
74
|
+
observer?.observe(el);
|
|
75
|
+
if (list.current)
|
|
76
|
+
observer?.observe(list.current);
|
|
77
|
+
return () => {
|
|
78
|
+
el.removeEventListener("scroll", measure);
|
|
79
|
+
observer?.disconnect();
|
|
80
|
+
};
|
|
81
|
+
}, [measure]);
|
|
82
|
+
// A button disables itself at its end, and a disabled button loses focus to
|
|
83
|
+
// the page. When that happens to the focused one, hand focus to the other
|
|
84
|
+
// button, or to the region when the row fits, so a keyboard reader stays here.
|
|
85
|
+
//
|
|
86
|
+
// The browser has already moved focus to the body by the time this runs, so
|
|
87
|
+
// what is tracked is the button last pressed while it held focus. A press
|
|
88
|
+
// only counts for the scroll it starts: it is forgotten after PRESS_WINDOW,
|
|
89
|
+
// so reaching the end later by wheel or trackpad never pulls focus in.
|
|
90
|
+
const pressed = React.useRef(null);
|
|
91
|
+
const pressedTimer = React.useRef(undefined);
|
|
92
|
+
React.useEffect(() => () => clearTimeout(pressedTimer.current), []);
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
const side = pressed.current;
|
|
95
|
+
if (!side || !edges[side])
|
|
96
|
+
return;
|
|
97
|
+
pressed.current = null;
|
|
98
|
+
const active = document.activeElement;
|
|
99
|
+
const own = side === "start" ? startButton.current : endButton.current;
|
|
100
|
+
if (active !== own && active !== document.body && active !== null)
|
|
101
|
+
return;
|
|
102
|
+
const other = side === "start" ? (edges.end ? null : endButton.current) : edges.start ? null : startButton.current;
|
|
103
|
+
(other ?? viewport.current)?.focus();
|
|
104
|
+
}, [edges]);
|
|
105
|
+
const scroll = (direction) => {
|
|
106
|
+
const el = viewport.current;
|
|
107
|
+
if (!el)
|
|
108
|
+
return;
|
|
109
|
+
const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
110
|
+
el.scrollBy({ left: direction * Math.max(el.clientWidth - FADE, el.clientWidth / 2), behavior: reduce ? "auto" : "smooth" });
|
|
111
|
+
};
|
|
112
|
+
const items = React.Children.toArray(children);
|
|
113
|
+
const sideButton = (side) => {
|
|
114
|
+
const atEnd = side === "start" ? edges.start : edges.end;
|
|
115
|
+
const Icon = side === "start" ? RailPreviousIcon : RailNextIcon;
|
|
116
|
+
return (_jsx("button", { ref: side === "start" ? startButton : endButton, type: "button", "data-slot": "scroll-rail-button", "data-side": side, "aria-label": side === "start" ? previousLabel : nextLabel, "aria-controls": id, disabled: atEnd, onClick: (event) => {
|
|
117
|
+
pressed.current = event.currentTarget === document.activeElement ? side : null;
|
|
118
|
+
clearTimeout(pressedTimer.current);
|
|
119
|
+
pressedTimer.current = setTimeout(() => {
|
|
120
|
+
pressed.current = null;
|
|
121
|
+
}, PRESS_WINDOW);
|
|
122
|
+
scroll(side === "start" ? -1 : 1);
|
|
123
|
+
}, className: cn("absolute top-1/2 z-10 inline-flex size-12 -translate-y-1/2 items-center justify-center rounded-xl border border-(--cg-tertiary-dark) bg-card text-(--cg-tertiary-dark) outline-none", "hover:[background-image:linear-gradient(var(--cg-hover),var(--cg-hover))] focus-visible:ring-3 focus-visible:ring-ring", "pointer-coarse:hidden", side === "start" ? "left-0" : "right-0", atEnd && "invisible"), children: _jsx(Icon, { className: "size-5" }) }));
|
|
124
|
+
};
|
|
125
|
+
const sideFade = (side) => {
|
|
126
|
+
const atEnd = side === "start" ? edges.start : edges.end;
|
|
127
|
+
return (_jsx("span", { "data-slot": "scroll-rail-fade", "data-side": side, "aria-hidden": "true", className: cn("pointer-events-none absolute inset-y-0 z-[1] w-16 from-(--scroll-rail-fade) to-transparent", side === "start" ? "left-0 bg-linear-to-r" : "right-0 bg-linear-to-l", atEnd && "hidden") }));
|
|
128
|
+
};
|
|
129
|
+
return (_jsxs("div", { "data-slot": "scroll-rail", className: cn("relative min-w-0", className), style: { "--scroll-rail-fade": "var(--cg-surface-page)", ...style }, ...props, children: [_jsx("div", { ref: viewport, id: id, role: "region", "aria-label": label,
|
|
130
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- a scroll region must be reachable by keyboard even when its items hold nothing focusable (WCAG 2.1.1; axe's scrollable-region-focusable)
|
|
131
|
+
tabIndex: 0, "data-slot": "scroll-rail-viewport", className: "snap-x snap-mandatory overflow-x-auto overscroll-x-contain outline-none [scrollbar-width:none] focus-visible:ring-3 focus-visible:ring-ring [&::-webkit-scrollbar]:hidden", children: _jsx("ul", { ref: list, "data-slot": "scroll-rail-list", className: "m-0 flex w-max list-none p-0", style: { gap }, children: items.map((child, i) => (_jsx("li", { "data-slot": "scroll-rail-item", className: "shrink-0 snap-start", children: child }, React.isValidElement(child) && child.key != null ? child.key : i))) }) }), sideFade("start"), sideFade("end"), sideButton("start"), sideButton("end")] }));
|
|
132
|
+
}
|
|
133
|
+
export { ScrollRail };
|
|
134
|
+
//# sourceMappingURL=scroll-rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-rail.js","sourceRoot":"","sources":["../../src/blocks/scroll-rail.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A section's heading row on the 2026 Home (#382, spec §4.4, §4.6 to §4.8).
|
|
4
|
+
*
|
|
5
|
+
* The title at the frames' H4 (`text-heading-lg`, 22 / 1.2, Medium) in Ink, not
|
|
6
|
+
* the `#000000` two of the frames paint (spec §8.3). `inline` sits 24 to its
|
|
7
|
+
* right: My Creations' filter tabs. `action` sits at the far end: On the Grid's
|
|
8
|
+
* "Explore All". The heading level is the caller's, because the page decides
|
|
9
|
+
* the outline.
|
|
10
|
+
*/
|
|
11
|
+
declare function SectionHeading({ title, level, inline, action, className, ...props }: Omit<React.ComponentProps<"div">, "title" | "children"> & {
|
|
12
|
+
title: React.ReactNode;
|
|
13
|
+
/** The heading element. Default 2. */
|
|
14
|
+
level?: 2 | 3 | 4;
|
|
15
|
+
/** 24px after the title: the filter tabs. */
|
|
16
|
+
inline?: React.ReactNode;
|
|
17
|
+
/** At the far end: "Explore All". */
|
|
18
|
+
action?: React.ReactNode;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
export { SectionHeading };
|
|
21
|
+
//# sourceMappingURL=section-heading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-heading.d.ts","sourceRoot":"","sources":["../../src/blocks/section-heading.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;GAQG;AACH,iBAAS,cAAc,CAAC,EACtB,KAAK,EACL,KAAS,EACT,MAAM,EACN,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAC3D,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,sCAAsC;IACtC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,qCAAqC;IACrC,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB,qBA0BA;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
4
|
+
/**
|
|
5
|
+
* A section's heading row on the 2026 Home (#382, spec §4.4, §4.6 to §4.8).
|
|
6
|
+
*
|
|
7
|
+
* The title at the frames' H4 (`text-heading-lg`, 22 / 1.2, Medium) in Ink, not
|
|
8
|
+
* the `#000000` two of the frames paint (spec §8.3). `inline` sits 24 to its
|
|
9
|
+
* right: My Creations' filter tabs. `action` sits at the far end: On the Grid's
|
|
10
|
+
* "Explore All". The heading level is the caller's, because the page decides
|
|
11
|
+
* the outline.
|
|
12
|
+
*/
|
|
13
|
+
function SectionHeading({ title, level = 2, inline, action, className, ...props }) {
|
|
14
|
+
const Heading = `h${level}`;
|
|
15
|
+
return (_jsxs("div", { "data-slot": "section-heading", className: cn("flex min-w-0 items-center justify-between gap-6 text-ink", className), ...props, children: [_jsxs("div", { "data-slot": "section-heading-start", className: "flex min-w-0 items-center gap-6", children: [_jsx(Heading, { "data-slot": "section-heading-title", className: "m-0 shrink-0 text-heading-lg font-medium text-ink", children: title }), inline ? (_jsx("div", { "data-slot": "section-heading-inline", className: "min-w-0", children: inline })) : null] }), action ? (_jsx("div", { "data-slot": "section-heading-action", className: "shrink-0", children: action })) : null] }));
|
|
16
|
+
}
|
|
17
|
+
export { SectionHeading };
|
|
18
|
+
//# sourceMappingURL=section-heading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-heading.js","sourceRoot":"","sources":["../../src/blocks/section-heading.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,EACtB,KAAK,EACL,KAAK,GAAG,CAAC,EACT,MAAM,EACN,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EAST;IACC,MAAM,OAAO,GAAG,IAAI,KAAK,EAAwB,CAAA;IAEjD,OAAO,CACL,4BACY,iBAAiB,EAC3B,SAAS,EAAE,EAAE,CAAC,0DAA0D,EAAE,SAAS,CAAC,KAChF,KAAK,aAET,4BAAe,uBAAuB,EAAC,SAAS,EAAC,iCAAiC,aAChF,KAAC,OAAO,iBAAW,uBAAuB,EAAC,SAAS,EAAC,mDAAmD,YACrG,KAAK,GACE,EACT,MAAM,CAAC,CAAC,CAAC,CACR,2BAAe,wBAAwB,EAAC,SAAS,EAAC,SAAS,YACxD,MAAM,GACH,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,MAAM,CAAC,CAAC,CAAC,CACR,2BAAe,wBAAwB,EAAC,SAAS,EAAC,UAAU,YACzD,MAAM,GACH,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -30,10 +30,20 @@ declare function SideNavGroup({ label, icon, collapsible, open, defaultOpen, onO
|
|
|
30
30
|
onOpenChange?: (open: boolean) => void;
|
|
31
31
|
children?: React.ReactNode;
|
|
32
32
|
}): React.JSX.Element;
|
|
33
|
-
declare function SideNavItem({ label, icon, count, active, render, className, onKeyDown, ...props }: Omit<useRender.ComponentProps<"button">, "children"> & {
|
|
33
|
+
declare function SideNavItem({ label, icon, activeIcon, count, active, render, className, onKeyDown, ...props }: Omit<useRender.ComponentProps<"button">, "children"> & {
|
|
34
34
|
label: React.ReactNode;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The row's glyph, drawn at 20px. The label is what gets announced, so the
|
|
37
|
+
* glyph carries no name.
|
|
38
|
+
*/
|
|
36
39
|
icon?: React.ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* The same glyph, filled, for when this row is the page you are on (#377).
|
|
42
|
+
* Pass the pair once, `icon={<PeopleIcon />} activeIcon={<PeopleFilledIcon />}`,
|
|
43
|
+
* and the row draws the right one for its state. Without it the row draws
|
|
44
|
+
* `icon` in both states.
|
|
45
|
+
*/
|
|
46
|
+
activeIcon?: React.ReactNode;
|
|
37
47
|
/**
|
|
38
48
|
* The trailing pill. The frame's Feedback row wears a `0`.
|
|
39
49
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-nav.d.ts","sourceRoot":"","sources":["../../src/blocks/side-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"side-nav.d.ts","sourceRoot":"","sources":["../../src/blocks/side-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AA2IrD,iBAAS,OAAO,CAAC,EACf,KAAK,EACL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG;IAC5C;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,qBAsCA;AAED,iBAAS,YAAY,CAAC,EACpB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,IAAI,EACJ,WAAmB,EACnB,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,GAAG;IACzD,6DAA6D;IAC7D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B,qBAkGA;AAED,iBAAS,WAAW,CAAC,EACnB,KAAK,EACL,IAAI,EACJ,UAAU,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACxD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,0EA4EA;AAED,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,CAAA"}
|
package/dist/blocks/side-nav.js
CHANGED
|
@@ -56,18 +56,28 @@ import { cn } from "../lib/cloudgrid-cn.js";
|
|
|
56
56
|
*
|
|
57
57
|
* ## The active row does not flip, and that is the decision
|
|
58
58
|
*
|
|
59
|
-
* The
|
|
60
|
-
* `--cg-primary
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* The active row is a pale lavender chip, `--cg-primary-tint`, with the glyph
|
|
60
|
+
* FILLED in `--cg-primary` and the label in ink (#377, the 2026 Admin menu,
|
|
61
|
+
* Figma 7414:5979; before that the label was purple too). The tint is primary
|
|
62
|
+
* at 10% over white to within one unit per channel, so the opaque token is the
|
|
63
|
+
* frame's ground. The tint, the purple and the label's ink
|
|
64
|
+
* (`--cg-neutral-900`, read from the ramp, NOT `text-ink`) are all declared at
|
|
65
|
+
* `:root` only, so the row paints the same three values on the page, on the
|
|
66
|
+
* purple panel and on ink — and that is the point rather than an oversight.
|
|
63
67
|
*
|
|
64
68
|
* It is the kind badge's rule (#196) applied to a row: the chip brings its own
|
|
65
|
-
* ground, so it does not need the surface's answer for one. The
|
|
66
|
-
*
|
|
69
|
+
* ground, so it does not need the surface's answer for one. The ink label
|
|
70
|
+
* measures 13.69:1 on it and the purple glyph 5.02:1, and the chip measures
|
|
67
71
|
* 1.16:1 against the white card, 5.02:1 against the panel and 13.68:1 on ink.
|
|
68
72
|
* `packages/ui/test/contrast-guardrail.test.mjs` holds every one of those
|
|
69
73
|
* numbers, and says why the chip's own floor is not 3:1.
|
|
70
74
|
*
|
|
75
|
+
* The state is said three ways: the chip, the glyph's SHAPE (filled, when the
|
|
76
|
+
* caller hands `activeIcon`), and `aria-current`. An inactive row's glyph is
|
|
77
|
+
* N3 (`--cg-slate-500`, the `steel` utility) on the light page; N3 is a :root
|
|
78
|
+
* ramp value that measures 1.71:1 on the panel, so on the panel and on ink the
|
|
79
|
+
* glyph takes that surface's own `--cg-text-muted` instead.
|
|
80
|
+
*
|
|
71
81
|
* The alternative was a surface-scoped `--cg-primary-tint`, and it fails on the
|
|
72
82
|
* panel for a measured reason the token file already records: a white wash over
|
|
73
83
|
* the brand purple gives white text 4.31:1, under the floor, and white is the
|
|
@@ -87,6 +97,24 @@ import { cn } from "../lib/cloudgrid-cn.js";
|
|
|
87
97
|
* the obvious reach and it is lavender at `:root` only, so a hovered row on the
|
|
88
98
|
* purple panel would have been white text on near-white.
|
|
89
99
|
*/
|
|
100
|
+
/**
|
|
101
|
+
* A row's box, and a collapsible group's header, which sits in the column and
|
|
102
|
+
* looks like one. Figma 7414:5979 (#377): 12 vertical and 20 horizontal, a 12px
|
|
103
|
+
* gap, radius 12, Manrope Medium 15; with the 20px glyph that makes a 44px row.
|
|
104
|
+
*
|
|
105
|
+
* The frame's `leading-none` stays on the row, but the LABEL span sets a 20px
|
|
106
|
+
* line box (`leading-5`). It is `truncate`, which is `overflow: hidden`, and a
|
|
107
|
+
* 15px box clips Manrope's descenders: the g in Billing, the y in Activity
|
|
108
|
+
* (measured scrollHeight 18 against clientHeight 15 on #380). The Figma text
|
|
109
|
+
* node does not clip, so its `leading-none` does not carry over to the label.
|
|
110
|
+
*/
|
|
111
|
+
const ROW = cn("flex w-full items-center gap-3 rounded-md px-5 py-3 text-left font-ui text-body-sm leading-none font-medium no-underline", "transition-colors focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-none");
|
|
112
|
+
/**
|
|
113
|
+
* The 20px glyph box. It sizes the caller's svg as well as itself, so a mark
|
|
114
|
+
* passed with a `size-4` of its own (the Console's, today) still draws at the
|
|
115
|
+
* frame's 20px rather than sitting small inside a 20px box.
|
|
116
|
+
*/
|
|
117
|
+
const GLYPH = "flex size-5 shrink-0 items-center justify-center [&>svg]:size-5";
|
|
90
118
|
/** Rows and group headers, in the order the column draws them. */
|
|
91
119
|
const ROW_SELECTOR = '[data-slot="side-nav-item"],[data-slot="side-nav-group-header"]';
|
|
92
120
|
/**
|
|
@@ -123,7 +151,7 @@ function SideNav({ label, context, className, children, onKeyDown, ...props }) {
|
|
|
123
151
|
// text-ink because text does not flip by inheritance: without it every
|
|
124
152
|
// row inside a panel would inherit the page's dark ink and stay dark on
|
|
125
153
|
// purple.
|
|
126
|
-
className: cn("flex w-full flex-col gap-6 text-ink", className), ...props, children: [context ? (_jsx("div", { "data-slot": "side-nav-context", className: "px-
|
|
154
|
+
className: cn("flex w-full flex-col gap-6 text-ink", className), ...props, children: [context ? (_jsx("div", { "data-slot": "side-nav-context", className: "px-5 text-caption text-faint", children: context })) : null, children] }));
|
|
127
155
|
}
|
|
128
156
|
function SideNavGroup({ label, icon, collapsible, open, defaultOpen = false, onOpenChange, className, children, ...props }) {
|
|
129
157
|
const panelId = React.useId();
|
|
@@ -157,9 +185,9 @@ function SideNavGroup({ label, icon, collapsible, open, defaultOpen = false, onO
|
|
|
157
185
|
// step is one edit in eyebrow.tsx. Caps and the caption size are this
|
|
158
186
|
// frame's treatment of it, which is what `className` is for — see the
|
|
159
187
|
// eyebrow's own note on not being uppercase by default.
|
|
160
|
-
_jsx(Eyebrow, { className: "px-
|
|
188
|
+
_jsx(Eyebrow, { className: "px-5 text-caption uppercase", children: label })) : null, rows] }));
|
|
161
189
|
}
|
|
162
|
-
return (_jsxs("div", { "data-slot": "side-nav-group", "data-collapsible": "", "data-open": isOpen ? "" : undefined, className: cn("flex flex-col gap-0.5", className), ...props, children: [_jsxs("button", { type: "button", "data-slot": "side-nav-group-header", "aria-expanded": isOpen, "aria-controls": panelId, onClick: toggle, className: cn(
|
|
190
|
+
return (_jsxs("div", { "data-slot": "side-nav-group", "data-collapsible": "", "data-open": isOpen ? "" : undefined, className: cn("flex flex-col gap-0.5", className), ...props, children: [_jsxs("button", { type: "button", "data-slot": "side-nav-group-header", "aria-expanded": isOpen, "aria-controls": panelId, onClick: toggle, className: cn(ROW, "text-ink hover:bg-(--cg-hover)"), children: [icon ? (_jsx("span", { "data-slot": "side-nav-icon", className: cn(GLYPH, "text-steel in-data-[cg-surface=panel]:text-faint in-data-[cg-surface=ink]:text-faint"), children: icon })) : null, _jsx("span", { className: "min-w-0 flex-1 truncate leading-5", children: label }), _jsx(ChevronDownIcon
|
|
163
191
|
// The caret is the only thing in this block that moves. It rotates
|
|
164
192
|
// rather than swapping to a second glyph, so the direction it points
|
|
165
193
|
// is continuous with the panel opening under it.
|
|
@@ -169,7 +197,7 @@ function SideNavGroup({ label, icon, collapsible, open, defaultOpen = false, onO
|
|
|
169
197
|
// is continuous with the panel opening under it.
|
|
170
198
|
className: cn("size-4 shrink-0 transition-transform duration-[var(--cg-duration)] ease-out", isOpen ? "rotate-0" : "-rotate-90") })] }), _jsx("div", { id: panelId, hidden: !isOpen, className: isOpen ? "pl-3" : undefined, children: rows })] }));
|
|
171
199
|
}
|
|
172
|
-
function SideNavItem({ label, icon, count, active, render, className, onKeyDown, ...props }) {
|
|
200
|
+
function SideNavItem({ label, icon, activeIcon, count, active, render, className, onKeyDown, ...props }) {
|
|
173
201
|
function handleKeyDown(event) {
|
|
174
202
|
onKeyDown?.(event);
|
|
175
203
|
if (event.defaultPrevented)
|
|
@@ -184,6 +212,7 @@ function SideNavItem({ label, icon, count, active, render, className, onKeyDown,
|
|
|
184
212
|
event.preventDefault();
|
|
185
213
|
el.click();
|
|
186
214
|
}
|
|
215
|
+
const glyph = active && activeIcon ? activeIcon : icon;
|
|
187
216
|
return useRender({
|
|
188
217
|
render,
|
|
189
218
|
defaultTagName: "button",
|
|
@@ -197,16 +226,21 @@ function SideNavItem({ label, icon, count, active, render, className, onKeyDown,
|
|
|
197
226
|
// and `render={<Link />}` is the common case.
|
|
198
227
|
...(render ? {} : { type: "button" }),
|
|
199
228
|
onKeyDown: handleKeyDown,
|
|
200
|
-
className: cn(
|
|
229
|
+
className: cn(ROW, active
|
|
201
230
|
? // The self-contained chip. See the module note for the numbers and
|
|
202
|
-
// for why it is the same two values on all three surfaces.
|
|
203
|
-
|
|
204
|
-
|
|
231
|
+
// for why it is the same two values on all three surfaces. The
|
|
232
|
+
// label is the :root ink, not `text-ink`, which flips to white
|
|
233
|
+
// inside a panel while the chip under it does not.
|
|
234
|
+
"bg-brand-tint text-(--cg-neutral-900)"
|
|
235
|
+
: "text-ink hover:bg-(--cg-hover)", className),
|
|
205
236
|
// The row's content is composed from the three slots rather than taken as
|
|
206
237
|
// `children`, which is why `children` is omitted from the props type: a
|
|
207
238
|
// caller who could pass both would have two ways to fill one row and no
|
|
208
239
|
// rule about which wins.
|
|
209
|
-
children: (_jsxs(_Fragment, { children: [
|
|
240
|
+
children: (_jsxs(_Fragment, { children: [glyph ? (_jsx("span", { "data-slot": "side-nav-icon", className: cn(GLYPH, active
|
|
241
|
+
? "text-brand"
|
|
242
|
+
: // N3, and the surface's own muted text where N3 vanishes.
|
|
243
|
+
"text-steel in-data-[cg-surface=panel]:text-faint in-data-[cg-surface=ink]:text-faint"), children: glyph })) : null, _jsx("span", { className: "min-w-0 flex-1 truncate leading-5", children: label }), count === undefined ? null : (_jsx("span", { "data-slot": "side-nav-count",
|
|
210
244
|
// The wash again, for the reason the hover uses it: a pale grey
|
|
211
245
|
// pill named `bg-raised` is neutral-50 at :root only, so it would
|
|
212
246
|
// be a near-white pill on the purple panel.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-nav.js","sourceRoot":"","sources":["../../src/blocks/side-nav.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC
|
|
1
|
+
{"version":3,"file":"side-nav.js","sourceRoot":"","sources":["../../src/blocks/side-nav.tsx"],"names":[],"mappings":"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"}
|
|
@@ -88,7 +88,25 @@ export type StatHintTone = "neutral" | "positive";
|
|
|
88
88
|
type StatTileIcon = React.ComponentType<{
|
|
89
89
|
className?: string;
|
|
90
90
|
}>;
|
|
91
|
-
|
|
91
|
+
/**
|
|
92
|
+
* ## `tone`, the Home tile (#382, spec §4.2, Figma 4964:4905)
|
|
93
|
+
*
|
|
94
|
+
* The App page's KPI tile puts a purple mark on a violet square. The 2026 Home
|
|
95
|
+
* draws the same arrangement in neutrals: a slate glyph on a page-coloured
|
|
96
|
+
* square, the number in Manrope Medium 24 (not the display face), the card
|
|
97
|
+
* rounded at 12 rather than 16, and 16 by 24 of padding with the hairline drawn
|
|
98
|
+
* inside it, so the tile is the frame's 79 tall.
|
|
99
|
+
*
|
|
100
|
+
* `brand` is the default and is the 0.39 tile exactly. `tone` only changes a
|
|
101
|
+
* tile that has an `icon`: without one there is no square to retone.
|
|
102
|
+
*
|
|
103
|
+
* The label stays on `--cg-text-muted-strong`, not the frame's slate-500:
|
|
104
|
+
* #828C98 is 3.41:1 on white, and a label is content (#232). So the neutral tile
|
|
105
|
+
* does not take the brand tile's ink label either; it reads muted, as drawn,
|
|
106
|
+
* at the rung that clears AA.
|
|
107
|
+
*/
|
|
108
|
+
export type StatTileTone = "brand" | "neutral";
|
|
109
|
+
declare function StatTile({ label, value, hint, hintTone, pending, icon: Icon, valueFirst, example, tone, className, ...props }: Omit<React.ComponentProps<"div">, "ref" | "children"> & {
|
|
92
110
|
/** Names the metric. Small and muted, above the value. */
|
|
93
111
|
label: React.ReactNode;
|
|
94
112
|
/** The measurement. Ignored when `pending`. */
|
|
@@ -104,6 +122,8 @@ declare function StatTile({ label, value, hint, hintTone, pending, icon: Icon, v
|
|
|
104
122
|
valueFirst?: boolean;
|
|
105
123
|
/** The values are illustrative: set them muted. Pair with a ComingSoonTag on the card. */
|
|
106
124
|
example?: boolean;
|
|
125
|
+
/** The icon square's palette: the App page's brand violet, or Home's neutrals. */
|
|
126
|
+
tone?: StatTileTone;
|
|
107
127
|
}): React.JSX.Element;
|
|
108
128
|
declare function StatTileRow({ className, children, ...props }: Omit<React.ComponentProps<"div">, "ref">): React.JSX.Element;
|
|
109
129
|
export { StatTile, StatTileRow };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-tile.d.ts","sourceRoot":"","sources":["../../src/blocks/stat-tile.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,iGAAiG;AACjG,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,CAAA;AAWjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE/D,iBAAS,QAAQ,CAAC,EAChB,KAAK,EACL,KAAK,EACL,IAAI,EACJ,QAAoB,EACpB,OAAO,EACP,IAAI,EAAE,IAAI,EACV,UAAU,EACV,OAAO,EACP,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,GAAG;IACzD,0DAA0D;IAC1D,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,qEAAqE;IACrE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"stat-tile.d.ts","sourceRoot":"","sources":["../../src/blocks/stat-tile.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,iGAAiG;AACjG,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,CAAA;AAWjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE/D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAA;AAE9C,iBAAS,QAAQ,CAAC,EAChB,KAAK,EACL,KAAK,EACL,IAAI,EACJ,QAAoB,EACpB,OAAO,EACP,IAAI,EAAE,IAAI,EACV,UAAU,EACV,OAAO,EACP,IAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,GAAG;IACzD,0DAA0D;IAC1D,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,qEAAqE;IACrE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,qBA+FA;AAsBD,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,qBAiB1C;AAcD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAA"}
|
package/dist/blocks/stat-tile.js
CHANGED
|
@@ -9,21 +9,24 @@ const HINT_TONE = {
|
|
|
9
9
|
neutral: "text-faint-strong",
|
|
10
10
|
positive: "text-online-strong",
|
|
11
11
|
};
|
|
12
|
-
function StatTile({ label, value, hint, hintTone = "neutral", pending, icon: Icon, valueFirst, example, className, ...props }) {
|
|
12
|
+
function StatTile({ label, value, hint, hintTone = "neutral", pending, icon: Icon, valueFirst, example, tone = "brand", className, ...props }) {
|
|
13
13
|
const muted = example && !pending;
|
|
14
14
|
const kpi = Icon !== undefined;
|
|
15
|
+
const neutral = kpi && tone === "neutral";
|
|
15
16
|
const order = kpi || valueFirst ? "value-first" : undefined;
|
|
16
17
|
const labelNode = (_jsx("span", { "data-slot": "stat-tile-label", className: "text-caption text-faint-strong", children: label }));
|
|
17
18
|
const valueNode = (_jsx("span", { "data-slot": "stat-tile-value", "data-pending": pending ? "" : undefined, className: cn(
|
|
18
19
|
// The KPI tile's value is the 24px title rung, whose own leading is
|
|
19
20
|
// tight; spelled as a separate string rather than an override, so no
|
|
20
21
|
// merge has to decide between two leadings.
|
|
21
|
-
|
|
22
|
-
? "font-
|
|
23
|
-
:
|
|
22
|
+
neutral
|
|
23
|
+
? "font-ui text-title-24 leading-none font-medium tabular-nums"
|
|
24
|
+
: kpi
|
|
25
|
+
? "font-display text-title-24 font-medium tabular-nums"
|
|
26
|
+
: "font-display text-display-sm leading-display font-medium tabular-nums", pending && "text-faint", muted && "text-faint-strong"), children: pending ? "Coming soon" : value }));
|
|
24
27
|
const hintNode = hint ? (_jsx("span", { "data-slot": "stat-tile-hint", "data-tone": hintTone, className: cn("text-caption", HINT_TONE[hintTone]), children: hint })) : null;
|
|
25
28
|
const lines = order ? (_jsxs(_Fragment, { children: [valueNode, labelNode, hintNode] })) : (_jsxs(_Fragment, { children: [labelNode, valueNode, hintNode] }));
|
|
26
|
-
return (_jsx("div", { "data-slot": "stat-tile", "data-pending": pending ? "" : undefined, "data-example": muted ? "" : undefined, "data-order": order, "data-icon": kpi ? "" : undefined,
|
|
29
|
+
return (_jsx("div", { "data-slot": "stat-tile", "data-pending": pending ? "" : undefined, "data-example": muted ? "" : undefined, "data-order": order, "data-icon": kpi ? "" : undefined, "data-tone": neutral ? "neutral" : undefined,
|
|
27
30
|
// text-ink on the root, because text does not flip by inheritance
|
|
28
31
|
// (rule 3): without it a tile inside a panel inherits the page's dark ink
|
|
29
32
|
// and its value stays dark on purple.
|
|
@@ -33,7 +36,9 @@ function StatTile({ label, value, hint, hintTone = "neutral", pending, icon: Ico
|
|
|
33
36
|
// In the KPI tile the label captions a value that leads: ink, as drawn.
|
|
34
37
|
// Set from the root so the label keeps its one class string (#232's
|
|
35
38
|
// guardrail pins it).
|
|
36
|
-
kpi && !muted && "**:data-[slot=stat-tile-label]:text-ink",
|
|
39
|
+
kpi && !neutral && !muted && "**:data-[slot=stat-tile-label]:text-ink",
|
|
40
|
+
// Home's tile: radius 12, and 16 by 24 less the hairline it draws inside.
|
|
41
|
+
neutral && "rounded-md px-[23px] py-[15px]", className), ...props, children: kpi ? (_jsxs(_Fragment, { children: [_jsx("span", { "data-slot": "stat-tile-icon", "aria-hidden": "true", className: cn("flex size-10 shrink-0 items-center justify-center", neutral ? "rounded-md bg-page text-steel" : "rounded-xl bg-brand-100 text-brand"), children: _jsx(Icon, { className: "size-6" }) }), _jsx("div", { className: "flex min-w-0 flex-col gap-1", children: lines })] })) : (lines) }));
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
44
|
* A row of tiles: two, three or four, side by side, stacking as the column
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-tile.js","sourceRoot":"","sources":["../../src/blocks/stat-tile.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AA0DvC,4EAA4E;AAC5E,8EAA8E;AAC9E,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,SAAS,GAAiC;IAC9C,OAAO,EAAE,mBAAmB;IAC5B,QAAQ,EAAE,oBAAoB;CAC/B,CAAA;
|
|
1
|
+
{"version":3,"file":"stat-tile.js","sourceRoot":"","sources":["../../src/blocks/stat-tile.tsx"],"names":[],"mappings":"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"}
|
package/dist/console.css
CHANGED
|
@@ -1,28 +1,69 @@
|
|
|
1
1
|
/*
|
|
2
2
|
A PRODUCT PRESET, not a theme and not a second brand.
|
|
3
3
|
|
|
4
|
-
The Console's type is the UI face, Manrope
|
|
5
|
-
file states that in the library, so the Console
|
|
6
|
-
its own. ui#250, 2026-09-20
|
|
4
|
+
The Console's body type is the UI face, Manrope; its headlines are the
|
|
5
|
+
display face, Tanker. This file states that in the library, so the Console
|
|
6
|
+
does not carry a font stack of its own. ui#250, 2026-09-20; revised by ui#371,
|
|
7
|
+
2026-11-01.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
One declaration, and deliberately nothing else: no colour, no spacing, no
|
|
9
10
|
size, no component. A preset that grew a second concern would become a theme,
|
|
10
11
|
and this package has one brand.
|
|
11
12
|
|
|
13
|
+
THE SOURCE. The CloudGrid 2026 Figma (file 5ouhISIL0mwYGHurvCGoEO), its text
|
|
14
|
+
variables as read on 2026-11-01:
|
|
15
|
+
|
|
16
|
+
Body Manrope Regular 15 / 1.46
|
|
17
|
+
Body Bold Manrope SemiBold 15 / 1.46
|
|
18
|
+
Small Manrope Regular 13 / 1.46
|
|
19
|
+
H4 Manrope Medium 22 / 1.2
|
|
20
|
+
fontHeadline Tanker (the Profile name; the Plugged popup's
|
|
21
|
+
headline, node 11324:93350, 46 / 1.2)
|
|
22
|
+
|
|
12
23
|
WHY IT EXISTS. #243 moved --cg-font-body to Space Grotesk and --cg-font-display
|
|
13
24
|
to Tanker, measured against the six canonical marketing frames, and that is
|
|
14
|
-
correct for the marketing site. The Console
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
correct for the marketing site. The Console inherits every family from here
|
|
26
|
+
(its own @theme font forwards were deleted in console#652), so without this
|
|
27
|
+
file every Console paragraph renders in Space Grotesk. The marketing default
|
|
28
|
+
in tokens.css is untouched by this file.
|
|
29
|
+
|
|
30
|
+
WHAT CHANGED IN #371, AND WHY. #250 re-declared BOTH faces as Manrope. Its
|
|
31
|
+
reason for the display half was StatTile: Tanker is an all-caps face, so a
|
|
32
|
+
value such as `15s` or `Coming soon` rendered as `15S` or `COMING SOON`. The
|
|
33
|
+
2026 Figma settles the display role the other way: its `fontHeadline` is
|
|
34
|
+
Tanker, in the Console as on the site. So this preset no longer touches
|
|
35
|
+
--cg-font-display, and headlines stay Tanker. The StatTile case is now the
|
|
36
|
+
caller's choice of role, not the preset's: a stat value that must read as
|
|
37
|
+
typed uses the body or UI face (`text-stat`, Manrope Medium 32), not the
|
|
38
|
+
display face. Repointing the display role to fix one component would have
|
|
39
|
+
taken every Console headline off the design with it.
|
|
40
|
+
|
|
41
|
+
THE FIVE ROLES, as tokens. A Console page picks a role, not a size. Every
|
|
42
|
+
role below already has a token and a utility; nothing new was added for them.
|
|
43
|
+
|
|
44
|
+
Body text-body-sm --cg-body-sm 15, --cg-body-line 1.46,
|
|
45
|
+
on the body face (this file: Manrope)
|
|
46
|
+
Body Bold text-body-sm font-bold the same, at --cg-weight-bold 600
|
|
47
|
+
Small text-caption --cg-caption 13, --cg-body-line 1.46
|
|
48
|
+
H4 text-heading-lg --cg-heading-lg 22, --cg-heading-line
|
|
49
|
+
font-medium 1.2, at --cg-weight-medium 500, on the
|
|
50
|
+
body face
|
|
51
|
+
Headline font-display the display face, Tanker. The face is
|
|
52
|
+
the role; the size is the page's
|
|
53
|
+
(the title-* and display-* rungs).
|
|
54
|
+
46 has no rung of its own today.
|
|
55
|
+
|
|
56
|
+
THE TANKER FILE. tokens.css names Tanker in the display stack, but this
|
|
57
|
+
package does not ship the font (ITF Free Font Licence, see fonts.css). A
|
|
58
|
+
consumer that has not declared its own @font-face for Tanker falls through to
|
|
59
|
+
the Manrope stack behind it. The Console declares Tanker itself.
|
|
19
60
|
|
|
20
61
|
WHY var(--cg-font-ui) RATHER THAN THE STACK. --cg-font-ui is already the
|
|
21
62
|
Manrope stack, and its docblock in tokens.css records that its value is
|
|
22
63
|
--cg-font-body's previous value byte for byte. Aliasing keeps one copy of the
|
|
23
64
|
fallback chain in the package. A consumer that spelled the stack out instead
|
|
24
|
-
went stale the moment the library's fallbacks moved
|
|
25
|
-
#132 did exactly that with the display stack
|
|
65
|
+
went stale the moment the library's fallbacks moved (cloudgrid-landing PR
|
|
66
|
+
#132 did exactly that with the display stack), and a copy one repo further
|
|
26
67
|
away goes stale the same way.
|
|
27
68
|
|
|
28
69
|
IMPORT ORDER. After tokens.css, which is what it re-declares. Where it sits
|
|
@@ -50,18 +91,13 @@
|
|
|
50
91
|
Three selectors is a cheaper price than that failure, and
|
|
51
92
|
test/console-preset.test.mjs pins it.
|
|
52
93
|
|
|
53
|
-
WHO IMPORTS IT. The Console first. Any product whose type is the UI face
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
SIDE EFFECT WORTH KNOWING. StatTile values read as typed again. Tanker is an
|
|
58
|
-
all-caps face, so `15s` and `Coming soon` rendered as `15S` and `COMING SOON`
|
|
59
|
-
under the marketing display token. No component changed to fix that.
|
|
94
|
+
WHO IMPORTS IT. The Console first. Any product whose body type is the UI face
|
|
95
|
+
imports the same file; nothing here is named for the Console except this
|
|
96
|
+
sentence.
|
|
60
97
|
*/
|
|
61
98
|
|
|
62
99
|
:root,
|
|
63
100
|
[data-cg-surface='panel'],
|
|
64
101
|
[data-cg-surface='ink'] {
|
|
65
102
|
--cg-font-body: var(--cg-font-ui);
|
|
66
|
-
--cg-font-display: var(--cg-font-ui);
|
|
67
103
|
}
|