@cloudgrid-io/ui 0.28.0 → 0.29.1
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 +8 -1
- package/dist/blocks/tab-bar.d.ts +33 -0
- package/dist/blocks/tab-bar.d.ts.map +1 -0
- package/dist/blocks/tab-bar.js +174 -0
- package/dist/blocks/tab-bar.js.map +1 -0
- package/dist/console.css +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/cloudgrid-cn.d.ts +1 -1
- package/dist/lib/cloudgrid-cn.d.ts.map +1 -1
- package/dist/lib/cloudgrid-cn.js +10 -0
- package/dist/lib/cloudgrid-cn.js.map +1 -1
- package/dist/theme.css +44 -0
- package/dist/tokens/base.json +101 -2
- package/dist/tokens.css +212 -2
- package/dist/tokens.json +10 -0
- package/dist/ui/tabs.d.ts +47 -6
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +131 -4
- package/dist/ui/tabs.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ component tree.
|
|
|
35
35
|
|
|
36
36
|
@import "@cloudgrid-io/ui/fonts.css";
|
|
37
37
|
@import "@cloudgrid-io/ui/tokens.css";
|
|
38
|
+
@import "@cloudgrid-io/ui/console.css"; /* optional, see below */
|
|
38
39
|
@import "@cloudgrid-io/ui/theme.css";
|
|
39
40
|
@import "@cloudgrid-io/ui/shadcn.css";
|
|
40
41
|
```
|
|
@@ -44,10 +45,15 @@ The order matters, and getting it wrong fails quietly rather than loudly.
|
|
|
44
45
|
- `theme.css` maps Tailwind utilities onto the tokens. Before `tokens.css` has run there is
|
|
45
46
|
nothing to map, so every utility resolves to nothing and the page renders unstyled
|
|
46
47
|
instead of erroring.
|
|
48
|
+
- `console.css` is a **product preset**, not part of the base. It re-declares the body and
|
|
49
|
+
display faces as the UI face, Manrope. The CloudGrid Console imports it; so should any
|
|
50
|
+
product whose type is the UI face rather than the display face. Everyone else leaves the
|
|
51
|
+
line out and keeps the default. It has to sit after `tokens.css`, which is what it
|
|
52
|
+
re-declares, and before `theme.css`, which maps the utilities onto the result.
|
|
47
53
|
- `shadcn.css` must come after whatever `:root` your own `shadcn init` wrote, because it
|
|
48
54
|
overrides those values. Put it earlier and the stock grey palette wins.
|
|
49
55
|
|
|
50
|
-
Not using shadcn: skip the last line. Not using Tailwind: `tokens.css` alone gives you every
|
|
56
|
+
Not using the Console's face: skip the `console.css` line. Not using shadcn: skip the last line. Not using Tailwind: `tokens.css` alone gives you every
|
|
51
57
|
value as a custom property.
|
|
52
58
|
|
|
53
59
|
## Point Tailwind at the package
|
|
@@ -92,6 +98,7 @@ property of a container, never a page-level theme.
|
|
|
92
98
|
| | |
|
|
93
99
|
|---|---|
|
|
94
100
|
| `@cloudgrid-io/ui/tokens.css` | every value as a CSS custom property |
|
|
101
|
+
| `@cloudgrid-io/ui/console.css` | product preset: body and display type set to Manrope |
|
|
95
102
|
| `@cloudgrid-io/ui/theme.css` | maps Tailwind utilities onto the tokens |
|
|
96
103
|
| `@cloudgrid-io/ui/shadcn.css` | maps shadcn's variables onto the tokens |
|
|
97
104
|
| `@cloudgrid-io/ui/fonts.css` | the three self-hosted families |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
declare function TabBar({ label, className, children, onKeyDown, ...props }: Omit<React.ComponentProps<"nav">, "ref"> & {
|
|
4
|
+
/**
|
|
5
|
+
* Names the landmark. Required, not optional: a page with two `<nav>`s and no
|
|
6
|
+
* names gives a screen-reader user two identical "navigation" entries and no
|
|
7
|
+
* way to tell the app's bar from the site's header.
|
|
8
|
+
*/
|
|
9
|
+
label: string;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
declare function TabBarItem({ label, icon, count, active, render, className, onKeyDown, ...props }: Omit<useRender.ComponentProps<"button">, "children"> & {
|
|
12
|
+
label: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* 22px in the frames. Sized by the caller, as every mark in this library is:
|
|
15
|
+
* the glyph box below is 22px and the mark fills what it is given.
|
|
16
|
+
*
|
|
17
|
+
* The label is what gets announced, so the glyph carries no name.
|
|
18
|
+
*/
|
|
19
|
+
icon?: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* The count pill, on `SideNavItem`'s terms: it renders as text inside the
|
|
22
|
+
* item, so the accessible name becomes "Feedback 2" — the same two pieces of
|
|
23
|
+
* information a sighted reader gets. A caller who needs "Feedback, 2 unread"
|
|
24
|
+
* passes that as `label`, because only the caller knows what is counted.
|
|
25
|
+
*
|
|
26
|
+
* It sits beside the label rather than as a badge over the glyph, which makes
|
|
27
|
+
* an item wider. Four items with a count each do not fit a phone; three do.
|
|
28
|
+
*/
|
|
29
|
+
count?: number;
|
|
30
|
+
active?: boolean;
|
|
31
|
+
}): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
32
|
+
export { TabBar, TabBarItem };
|
|
33
|
+
//# sourceMappingURL=tab-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-bar.d.ts","sourceRoot":"","sources":["../../src/blocks/tab-bar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAqGrD,iBAAS,MAAM,CAAC,EACd,KAAK,EACL,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;CACd,qBA2CA;AAED,iBAAS,UAAU,CAAC,EAClB,KAAK,EACL,IAAI,EACJ,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;;;;;OAKG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,0EAsEA;AAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { cn } from "../lib/cloudgrid-cn.js";
|
|
6
|
+
/**
|
|
7
|
+
* The bottom navigation a phone gets instead of a sidebar: three to five items
|
|
8
|
+
* pinned to the bottom of the viewport, each a glyph over a label, one of which
|
|
9
|
+
* is the page you are on.
|
|
10
|
+
*
|
|
11
|
+
* Read from the Console's founder-approved mobile frames (ui#256, Figma
|
|
12
|
+
* `5ouhISIL0mwYGHurvCGoEO`, `App / 01 Overview / Mobile` 8670:4808 and
|
|
13
|
+
* `App / 06 Versions / Mobile` 8670:4954). The same four destinations the
|
|
14
|
+
* `SideNav` column carries on a desktop — Overview, Insights, Build & run,
|
|
15
|
+
* Manage — reduced to a bar, because a 14-row column does not fit a phone and a
|
|
16
|
+
* hamburger hides the one thing a phone reader navigates with.
|
|
17
|
+
*
|
|
18
|
+
* ## It draws and it behaves. It does not know where anything is
|
|
19
|
+
*
|
|
20
|
+
* `SideNav`'s line, and the reason `render` exists here too. This block builds
|
|
21
|
+
* no `href`, matches no route and gates no role. The consumer decides which
|
|
22
|
+
* items a reader may see, which one is `active`, and what element an item
|
|
23
|
+
* actually is. Anything else would put the Console's URL helpers and its role
|
|
24
|
+
* model inside the design system, where neither can be right for long.
|
|
25
|
+
*
|
|
26
|
+
* ## Two parts
|
|
27
|
+
*
|
|
28
|
+
* `TabBar` the `<nav>` landmark, the fixed strip, the safe area and the
|
|
29
|
+
* keyboard.
|
|
30
|
+
* `TabBarItem` one destination: a glyph, a label, an optional count, and
|
|
31
|
+
* whether it is the page you are on.
|
|
32
|
+
*
|
|
33
|
+
* ## The keyboard
|
|
34
|
+
*
|
|
35
|
+
* Every item is a real tab stop, so Tab walks the bar in DOM order.
|
|
36
|
+
* ArrowRight and ArrowLeft are laid over that rather than replacing it: they
|
|
37
|
+
* move focus between items and wrap at the ends.
|
|
38
|
+
*
|
|
39
|
+
* As in `SideNav`, this is deliberately NOT a roving-tabindex tablist. A
|
|
40
|
+
* tablist owns one tab stop because its tabs are one control; these items are
|
|
41
|
+
* links to separate pages, and making three of four unreachable by Tab to
|
|
42
|
+
* satisfy a pattern they are not an instance of is a worse nav, not a more
|
|
43
|
+
* correct one.
|
|
44
|
+
*
|
|
45
|
+
* Horizontal keys only. The bar is a row, so ArrowUp and ArrowDown mean
|
|
46
|
+
* nothing in it — and a fixed strip at the bottom of a phone screen that ate
|
|
47
|
+
* them would take the one gesture that scrolls the page above it.
|
|
48
|
+
*
|
|
49
|
+
* Space is synthesised only where the browser does not already activate. A
|
|
50
|
+
* `<button>` fires on Enter and Space already; an `<a href>` fires on Enter and
|
|
51
|
+
* SCROLLS THE PAGE on Space. Since an item is normally an anchor — that is what
|
|
52
|
+
* `render={<Link />}` makes it — the bar synthesises the click for Space on
|
|
53
|
+
* anything that is not a button. Doing it unconditionally would fire a button
|
|
54
|
+
* twice.
|
|
55
|
+
*
|
|
56
|
+
* ## The active item reads the accent TEXT role, not the brand colour
|
|
57
|
+
*
|
|
58
|
+
* The frames paint the active glyph and label in the brand purple, and
|
|
59
|
+
* `text-brand` is the obvious reach. It is `--cg-primary`, declared at `:root`
|
|
60
|
+
* only, and the active item here has no ground of its own to carry — unlike
|
|
61
|
+
* `SideNavItem`, whose active row is a tinted chip and can therefore keep one
|
|
62
|
+
* pair of values on every surface (see that block's note for the measured
|
|
63
|
+
* argument).
|
|
64
|
+
*
|
|
65
|
+
* So this reads `var(--cg-text-accent)`, which is the accent role for TEXT and
|
|
66
|
+
* is declared in all three scopes: the brand purple on the page, white on the
|
|
67
|
+
* purple panel, lime on ink. On the surface the frames actually draw — a white
|
|
68
|
+
* bar on a white page — it resolves to exactly the purple they specify.
|
|
69
|
+
*
|
|
70
|
+
* Colour is never the only channel, so the active item also carries
|
|
71
|
+
* `aria-current="page"`.
|
|
72
|
+
*
|
|
73
|
+
* ## The label is 13px and the frame says 11
|
|
74
|
+
*
|
|
75
|
+
* `--cg-caption` is the smallest rung this brand ships. There is no 11px token,
|
|
76
|
+
* `component-source.spec.ts` bans `text-[11px]` along with every other
|
|
77
|
+
* arbitrary size, and adding a rung to the bottom of the type ramp is a brand
|
|
78
|
+
* decision with its own evidence bar (ui#248). Recorded in `docs/backlog.md`
|
|
79
|
+
* rather than decided here.
|
|
80
|
+
*
|
|
81
|
+
* ## Fixed, and what that means for a container
|
|
82
|
+
*
|
|
83
|
+
* The strip is `fixed inset-x-0 bottom-0`, because that is what a bottom
|
|
84
|
+
* navigation is, and a consumer who has to position it themselves has been
|
|
85
|
+
* handed half a component. To draw it inside a box instead — a phone frame in a
|
|
86
|
+
* gallery, a preview pane — give that box a transform (`[transform:translate(0)]`),
|
|
87
|
+
* which makes it the containing block for its fixed descendants.
|
|
88
|
+
*
|
|
89
|
+
* Nothing here reserves space for the bar in the page above it. That is the
|
|
90
|
+
* page's job and it needs the bar's height plus the safe area, which only the
|
|
91
|
+
* page knows how to spend.
|
|
92
|
+
*/
|
|
93
|
+
/** The items a key press can reach. */
|
|
94
|
+
const ITEM_SELECTOR = '[data-slot="tab-bar-item"]';
|
|
95
|
+
function reachableItems(root) {
|
|
96
|
+
return Array.from(root.querySelectorAll(ITEM_SELECTOR)).filter((el) => el.getClientRects().length > 0 && !el.hasAttribute("disabled"));
|
|
97
|
+
}
|
|
98
|
+
function TabBar({ label, className, children, onKeyDown, ...props }) {
|
|
99
|
+
function handleKeyDown(event) {
|
|
100
|
+
onKeyDown?.(event);
|
|
101
|
+
if (event.defaultPrevented)
|
|
102
|
+
return;
|
|
103
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
104
|
+
if (step === 0)
|
|
105
|
+
return;
|
|
106
|
+
const items = reachableItems(event.currentTarget);
|
|
107
|
+
const from = items.indexOf(document.activeElement);
|
|
108
|
+
// Not -1 only when focus is already on an item. A key press while focus is
|
|
109
|
+
// anywhere else should do what it does anywhere else.
|
|
110
|
+
if (from === -1 || items.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
items[(from + step + items.length) % items.length]?.focus();
|
|
114
|
+
}
|
|
115
|
+
return (_jsx("nav", { "data-slot": "tab-bar", "aria-label": label, onKeyDown: handleKeyDown, className: cn("fixed inset-x-0 bottom-0 z-40 border-t border-hairline bg-card",
|
|
116
|
+
// env(safe-area-inset-bottom) is 0 everywhere it does not apply, so
|
|
117
|
+
// this is not a phone-only branch: it is the home-indicator gutter on
|
|
118
|
+
// the hardware that has one and nothing on the hardware that does not.
|
|
119
|
+
// A class rather than an inline style, so it is assertable from markup.
|
|
120
|
+
"pb-[env(safe-area-inset-bottom)]",
|
|
121
|
+
// text-ink because text does not flip by inheritance (rule 3): without
|
|
122
|
+
// it every label inside a panel would inherit the page's dark ink and
|
|
123
|
+
// stay dark on purple.
|
|
124
|
+
"text-ink", className), ...props, children: _jsx("div", { "data-slot": "tab-bar-items", className: "flex items-stretch", children: children }) }));
|
|
125
|
+
}
|
|
126
|
+
function TabBarItem({ label, icon, count, active, render, className, onKeyDown, ...props }) {
|
|
127
|
+
function handleKeyDown(event) {
|
|
128
|
+
onKeyDown?.(event);
|
|
129
|
+
if (event.defaultPrevented)
|
|
130
|
+
return;
|
|
131
|
+
// See the module note: Space on an anchor scrolls. Synthesised only for the
|
|
132
|
+
// elements the browser does not already activate, or a button fires twice.
|
|
133
|
+
if (event.key !== " ")
|
|
134
|
+
return;
|
|
135
|
+
const el = event.currentTarget;
|
|
136
|
+
if (el.tagName === "BUTTON")
|
|
137
|
+
return;
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
el.click();
|
|
140
|
+
}
|
|
141
|
+
return useRender({
|
|
142
|
+
render,
|
|
143
|
+
defaultTagName: "button",
|
|
144
|
+
props: {
|
|
145
|
+
"data-slot": "tab-bar-item",
|
|
146
|
+
"data-active": active ? "" : undefined,
|
|
147
|
+
// aria-current is what says "this is the page you are on". The accent says
|
|
148
|
+
// it to a reader who can see it, and colour is never the only channel.
|
|
149
|
+
"aria-current": active ? "page" : undefined,
|
|
150
|
+
// Only when this really is a <button>. `type` on an anchor is invalid, and
|
|
151
|
+
// `render={<Link />}` is the common case.
|
|
152
|
+
...(render ? {} : { type: "button" }),
|
|
153
|
+
onKeyDown: handleKeyDown,
|
|
154
|
+
className: cn("flex flex-1 basis-0 flex-col items-center justify-center gap-1 px-1 pt-2 pb-1.5 text-caption no-underline", "transition-colors focus-visible:ring-3 focus-visible:ring-ring focus-visible:outline-none", active
|
|
155
|
+
? // The accent TEXT role, not `text-brand`. See the module note: it is
|
|
156
|
+
// the brand purple the frames draw at :root, and it is the only
|
|
157
|
+
// accent name declared on the panel and on ink as well.
|
|
158
|
+
"font-medium text-(--cg-text-accent)"
|
|
159
|
+
: "text-body hover:text-ink", className),
|
|
160
|
+
// Composed from the three slots rather than taken as `children`, which is
|
|
161
|
+
// why `children` is omitted from the props type: a caller who could pass
|
|
162
|
+
// both would have two ways to fill one item and no rule about which wins.
|
|
163
|
+
children: (_jsxs(_Fragment, { children: [icon ? (_jsx("span", { "data-slot": "tab-bar-icon", className: "flex size-[22px] shrink-0 items-center justify-center", children: icon })) : null, _jsxs("span", { "data-slot": "tab-bar-label", className: "flex items-center gap-1", children: [_jsx("span", { className: "truncate", children: label }), count === undefined ? null : (_jsx("span", { "data-slot": "tab-bar-count",
|
|
164
|
+
// The wash, for `SideNavItem`'s reason: a pale grey pill named
|
|
165
|
+
// `bg-raised` is neutral-50 at :root only, so it would be a
|
|
166
|
+
// near-white pill on the purple panel. --cg-hover is the one
|
|
167
|
+
// ground token declared in all three scopes.
|
|
168
|
+
className: "shrink-0 rounded-pill bg-(--cg-hover) px-1.5 text-caption text-body", children: count }))] })] })),
|
|
169
|
+
...props,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export { TabBar, TabBarItem };
|
|
174
|
+
//# sourceMappingURL=tab-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-bar.js","sourceRoot":"","sources":["../../src/blocks/tab-bar.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,oBAAoB,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAEH,uCAAuC;AACvC,MAAM,aAAa,GAAG,4BAA4B,CAAA;AAElD,SAAS,cAAc,CAAC,IAAiB;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAc,aAAa,CAAC,CAAC,CAAC,MAAM,CACzE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CACvE,CAAA;AACH,CAAC;AAED,SAAS,MAAM,CAAC,EACd,KAAK,EACL,SAAS,EACT,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EAQT;IACC,SAAS,aAAa,CAAC,KAAuC;QAC5D,SAAS,EAAE,CAAC,KAAK,CAAC,CAAA;QAClB,IAAI,KAAK,CAAC,gBAAgB;YAAE,OAAM;QAElC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChF,IAAI,IAAI,KAAK,CAAC;YAAE,OAAM;QAEtB,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QACjD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,aAA4B,CAAC,CAAA;QACjE,2EAA2E;QAC3E,sDAAsD;QACtD,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAE7C,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAC7D,CAAC;IAED,OAAO,CACL,2BACY,SAAS,gBACP,KAAK,EACjB,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,EAAE,CACX,gEAAgE;QAChE,oEAAoE;QACpE,sEAAsE;QACtE,uEAAuE;QACvE,wEAAwE;QACxE,kCAAkC;QAClC,uEAAuE;QACvE,sEAAsE;QACtE,uBAAuB;QACvB,UAAU,EACV,SAAS,CACV,KACG,KAAK,YAET,2BAAe,eAAe,EAAC,SAAS,EAAC,oBAAoB,YAC1D,QAAQ,GACL,GACF,CACP,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,IAAI,EACJ,KAAK,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,SAAS,EACT,GAAG,KAAK,EAqBT;IACC,SAAS,aAAa,CAAC,KAA6C;QAClE,SAAS,EAAE,CAAC,KAAK,CAAC,CAAA;QAClB,IAAI,KAAK,CAAC,gBAAgB;YAAE,OAAM;QAClC,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG;YAAE,OAAM;QAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,CAAA;QAC9B,IAAI,EAAE,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAM;QACnC,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,EAAE,CAAC,KAAK,EAAE,CAAA;IACZ,CAAC;IAED,OAAO,SAAS,CAAC;QACf,MAAM;QACN,cAAc,EAAE,QAAQ;QACxB,KAAK,EAAE;YACL,WAAW,EAAE,cAAc;YAC3B,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACtC,2EAA2E;YAC3E,uEAAuE;YACvE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;YAC3C,2EAA2E;YAC3E,0CAA0C;YAC1C,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,CAAC;YAC9C,SAAS,EAAE,aAAa;YACxB,SAAS,EAAE,EAAE,CACX,2GAA2G,EAC3G,2FAA2F,EAC3F,MAAM;gBACJ,CAAC,CAAC,qEAAqE;oBACrE,gEAAgE;oBAChE,wDAAwD;oBACxD,qCAAqC;gBACvC,CAAC,CAAC,0BAA0B,EAC9B,SAAS,CACV;YACD,0EAA0E;YAC1E,yEAAyE;YACzE,0EAA0E;YAC1E,QAAQ,EAAE,CACR,8BACG,IAAI,CAAC,CAAC,CAAC,CACN,4BACY,cAAc,EACxB,SAAS,EAAC,uDAAuD,YAEhE,IAAI,GACA,CACR,CAAC,CAAC,CAAC,IAAI,EACR,6BAAgB,eAAe,EAAC,SAAS,EAAC,yBAAyB,aACjE,eAAM,SAAS,EAAC,UAAU,YAAE,KAAK,GAAQ,EACxC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC5B,4BACY,eAAe;gCACzB,+DAA+D;gCAC/D,4DAA4D;gCAC5D,6DAA6D;gCAC7D,6CAA6C;gCAC7C,SAAS,EAAC,qEAAqE,YAE9E,KAAK,GACD,CACR,IACI,IACN,CACJ;YACD,GAAG,KAAK;SACT;KACF,CAAC,CAAA;AACJ,CAAC;AAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA"}
|
package/dist/console.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A PRODUCT PRESET, not a theme and not a second brand.
|
|
3
|
+
|
|
4
|
+
The Console's type is the UI face, Manrope, for body and for display. This
|
|
5
|
+
file states that in the library, so the Console does not carry a font stack of
|
|
6
|
+
its own. ui#250, 2026-09-20.
|
|
7
|
+
|
|
8
|
+
Two declarations, and deliberately nothing else: no colour, no spacing, no
|
|
9
|
+
size, no component. A preset that grew a second concern would become a theme,
|
|
10
|
+
and this package has one brand.
|
|
11
|
+
|
|
12
|
+
WHY IT EXISTS. #243 moved --cg-font-body to Space Grotesk and --cg-font-display
|
|
13
|
+
to Tanker, measured against the six canonical marketing frames, and that is
|
|
14
|
+
correct for the marketing site. The Console deleted its own @theme font
|
|
15
|
+
forwards (console#652) and inherits every family from here, so installing
|
|
16
|
+
0.28.0 changed every face in it at once. The marketing default is untouched by
|
|
17
|
+
this file; the Console imports it and gets back exactly the two values both
|
|
18
|
+
tokens held before ef8dc00.
|
|
19
|
+
|
|
20
|
+
WHY var(--cg-font-ui) RATHER THAN THE STACK. --cg-font-ui is already the
|
|
21
|
+
Manrope stack, and its docblock in tokens.css records that its value is
|
|
22
|
+
--cg-font-body's previous value byte for byte. Aliasing keeps one copy of the
|
|
23
|
+
fallback chain in the package. A consumer that spelled the stack out instead
|
|
24
|
+
went stale the moment the library's fallbacks moved — cloudgrid-landing PR
|
|
25
|
+
#132 did exactly that with the display stack — and a copy one repo further
|
|
26
|
+
away goes stale the same way.
|
|
27
|
+
|
|
28
|
+
IMPORT ORDER. After tokens.css, which is what it re-declares, and before
|
|
29
|
+
theme.css, which maps the Tailwind utilities onto the result:
|
|
30
|
+
|
|
31
|
+
@import "tailwindcss";
|
|
32
|
+
@import "@cloudgrid-io/ui/fonts.css";
|
|
33
|
+
@import "@cloudgrid-io/ui/tokens.css";
|
|
34
|
+
@import "@cloudgrid-io/ui/console.css"; <- here
|
|
35
|
+
@import "@cloudgrid-io/ui/theme.css";
|
|
36
|
+
@import "@cloudgrid-io/ui/shadcn.css";
|
|
37
|
+
|
|
38
|
+
Import it before tokens.css and tokens.css wins: same specificity, later
|
|
39
|
+
declaration, and the page renders Space Grotesk with no error anywhere.
|
|
40
|
+
|
|
41
|
+
THE SELECTOR LIST IS LOAD-BEARING, for the reason shadcn.css states at length.
|
|
42
|
+
A custom property that references another is substituted where it is DECLARED,
|
|
43
|
+
and descendants inherit the already-resolved value. tokens.css declares the
|
|
44
|
+
font families only at :root today, because a family has no on-purple value, so
|
|
45
|
+
a :root-only preset would render correctly as things stand. It is repeated on
|
|
46
|
+
both surface selectors anyway: the day --cg-font-ui gains a per-surface value,
|
|
47
|
+
a :root-only alias freezes to the root stack and silently stops following it.
|
|
48
|
+
Three selectors is a cheaper price than that failure, and
|
|
49
|
+
test/console-preset.test.mjs pins it.
|
|
50
|
+
|
|
51
|
+
WHO IMPORTS IT. The Console first. Any product whose type is the UI face
|
|
52
|
+
rather than the display face imports the same file; nothing here is named for
|
|
53
|
+
the Console except this sentence.
|
|
54
|
+
|
|
55
|
+
SIDE EFFECT WORTH KNOWING. StatTile values read as typed again. Tanker is an
|
|
56
|
+
all-caps face, so `15s` and `Coming soon` rendered as `15S` and `COMING SOON`
|
|
57
|
+
under the marketing display token. No component changed to fix that.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
:root,
|
|
61
|
+
[data-cg-surface='panel'],
|
|
62
|
+
[data-cg-surface='ink'] {
|
|
63
|
+
--cg-font-body: var(--cg-font-ui);
|
|
64
|
+
--cg-font-display: var(--cg-font-ui);
|
|
65
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export * from "./blocks/sign-in-options.js";
|
|
|
55
55
|
export * from "./blocks/stage-panel.js";
|
|
56
56
|
export * from "./blocks/stat-tile.js";
|
|
57
57
|
export * from "./blocks/step-item.js";
|
|
58
|
+
export * from "./blocks/tab-bar.js";
|
|
58
59
|
export * from "./blocks/token-specimen.js";
|
|
59
60
|
export * from "./blocks/tool-logo.js";
|
|
60
61
|
export * from "./blocks/tool-setup.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAG/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAGtC,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAG/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAGtC,cAAc,uBAAuB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,7 @@ export * from "./blocks/sign-in-options.js";
|
|
|
72
72
|
export * from "./blocks/stage-panel.js";
|
|
73
73
|
export * from "./blocks/stat-tile.js";
|
|
74
74
|
export * from "./blocks/step-item.js";
|
|
75
|
+
export * from "./blocks/tab-bar.js";
|
|
75
76
|
export * from "./blocks/token-specimen.js";
|
|
76
77
|
export * from "./blocks/tool-logo.js";
|
|
77
78
|
export * from "./blocks/tool-setup.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,kEAAkE;AAClE,8EAA8E;AAC9E,uEAAuE;AAEvE,cAAc;AACd,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAE/B,mBAAmB;AACnB,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,0EAA0E;AAC1E,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,kEAAkE;AAClE,8EAA8E;AAC9E,uEAAuE;AAEvE,cAAc;AACd,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAE/B,mBAAmB;AACnB,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA;AACzC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,0EAA0E;AAC1E,cAAc,uBAAuB,CAAA"}
|
|
@@ -60,6 +60,6 @@ import { type ClassValue } from "clsx";
|
|
|
60
60
|
* to write `font-regular` rediscovers this.
|
|
61
61
|
*/
|
|
62
62
|
export declare const BRAND_FONT_WEIGHTS: readonly ["heavy", "regular"];
|
|
63
|
-
export declare const BRAND_FONT_SIZES: readonly ["display-3xl", "display-2xl", "display-xl", "display-lg", "display-md", "display-sm", "title-lg", "title-md", "title-sm", "heading-lg", "heading-md", "heading-sm", "body-xl", "body-lg", "body-md", "body-sm", "caption", "mono", "label", "eyebrow"];
|
|
63
|
+
export declare const BRAND_FONT_SIZES: readonly ["display-3xl", "display-2xl", "display-xl", "display-48", "display-lg", "display-md", "display-sm", "title-lg", "title-36", "title-32", "title-md", "title-24", "title-sm", "heading-lg", "heading-md", "heading-sm", "body-xl", "body-lg", "body-18", "body-md", "body-sm", "caption", "mono", "label", "eyebrow", "stat"];
|
|
64
64
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
65
65
|
//# sourceMappingURL=cloudgrid-cn.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudgrid-cn.d.ts","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,+BAAgC,CAAA;AAE/D,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"cloudgrid-cn.d.ts","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,+BAAgC,CAAA;AAE/D,eAAO,MAAM,gBAAgB,uUA+BnB,CAAA;AAWV,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
package/dist/lib/cloudgrid-cn.js
CHANGED
|
@@ -65,23 +65,33 @@ export const BRAND_FONT_SIZES = [
|
|
|
65
65
|
"display-3xl",
|
|
66
66
|
"display-2xl",
|
|
67
67
|
"display-xl",
|
|
68
|
+
// Px-named rungs (#248). They do not match tailwind-merge's t-shirt size
|
|
69
|
+
// regex either -- `^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$` anchors on a t-shirt
|
|
70
|
+
// word, and "48" has none -- so they fall through to text-color exactly the
|
|
71
|
+
// way the role names do, and belong here for the same reason.
|
|
72
|
+
"display-48",
|
|
68
73
|
"display-lg",
|
|
69
74
|
"display-md",
|
|
70
75
|
"display-sm",
|
|
71
76
|
"title-lg",
|
|
77
|
+
"title-36",
|
|
78
|
+
"title-32",
|
|
72
79
|
"title-md",
|
|
80
|
+
"title-24",
|
|
73
81
|
"title-sm",
|
|
74
82
|
"heading-lg",
|
|
75
83
|
"heading-md",
|
|
76
84
|
"heading-sm",
|
|
77
85
|
"body-xl",
|
|
78
86
|
"body-lg",
|
|
87
|
+
"body-18",
|
|
79
88
|
"body-md",
|
|
80
89
|
"body-sm",
|
|
81
90
|
"caption",
|
|
82
91
|
"mono",
|
|
83
92
|
"label",
|
|
84
93
|
"eyebrow",
|
|
94
|
+
"stat",
|
|
85
95
|
];
|
|
86
96
|
const twMerge = extendTailwindMerge({
|
|
87
97
|
extend: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudgrid-cn.js","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAA;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,UAAU;IACV,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,MAAM;IACN,OAAO;IACP,SAAS;
|
|
1
|
+
{"version":3,"file":"cloudgrid-cn.js","sourceRoot":"","sources":["../../src/lib/cloudgrid-cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAA;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,yEAAyE;IACzE,0EAA0E;IAC1E,4EAA4E;IAC5E,8DAA8D;IAC9D,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;CACE,CAAA;AAEV,MAAM,OAAO,GAAG,mBAAmB,CAAC;IAClC,MAAM,EAAE;QACN,WAAW,EAAE;YACX,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,EAAE,CAAC;YAC9C,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC;SACnD;KACF;CACF,CAAC,CAAA;AAEF,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC"}
|
package/dist/theme.css
CHANGED
|
@@ -289,6 +289,20 @@
|
|
|
289
289
|
--text-display-xl: var(--cg-display-xl);
|
|
290
290
|
--text-display-xl--line-height: var(--cg-display-line);
|
|
291
291
|
--text-display-xl--letter-spacing: var(--cg-display-tracking);
|
|
292
|
+
/* Like 2xl and 3xl, and unlike the four rungs below it, this one is
|
|
293
|
+
leading-none with no tracking because its node is (#248). The legacy
|
|
294
|
+
--cg-display-line 1.06 and --cg-display-tracking 0.012em were tuned for a
|
|
295
|
+
condensed face this package no longer ships and are kept only for the rungs
|
|
296
|
+
that already rendered with them. */
|
|
297
|
+
--text-display-48: var(--cg-display-48);
|
|
298
|
+
--text-display-48--line-height: var(--cg-display-line-tight);
|
|
299
|
+
--text-display-48--letter-spacing: var(--cg-title-tracking);
|
|
300
|
+
/* --cg-display-64 has NO utility here, deliberately. Its two nodes disagree on
|
|
301
|
+
a leading -- thegrid sets 70px, home leaves Figma's Auto -- and a --text-*
|
|
302
|
+
entry cannot exist without a --line-height companion, so the utility could
|
|
303
|
+
only be built by inventing one. Same reasoning as --cg-body-14, and the same
|
|
304
|
+
reasoning that keeps 96px out of the ramp entirely. Spell it as
|
|
305
|
+
`[font-size:var(--cg-display-64)]` with the leading the page wants. */
|
|
292
306
|
--text-display-lg: var(--cg-display-lg);
|
|
293
307
|
--text-display-lg--line-height: var(--cg-display-line);
|
|
294
308
|
--text-display-lg--letter-spacing: var(--cg-display-tracking);
|
|
@@ -306,9 +320,21 @@
|
|
|
306
320
|
--text-title-lg: var(--cg-title-lg);
|
|
307
321
|
--text-title-lg--line-height: var(--cg-title-line);
|
|
308
322
|
--text-title-lg--letter-spacing: var(--cg-title-tracking);
|
|
323
|
+
/* Three rungs named by pixel value (#248): every size this round falls
|
|
324
|
+
strictly between two shipped ones, so there is no free t-shirt name in the
|
|
325
|
+
right place. tokens.css's --cg-display-64 note has the reasoning. */
|
|
326
|
+
--text-title-36: var(--cg-title-36);
|
|
327
|
+
--text-title-36--line-height: var(--cg-title-line);
|
|
328
|
+
--text-title-36--letter-spacing: var(--cg-title-tracking);
|
|
329
|
+
--text-title-32: var(--cg-title-32);
|
|
330
|
+
--text-title-32--line-height: var(--cg-title-line);
|
|
331
|
+
--text-title-32--letter-spacing: var(--cg-title-tracking);
|
|
309
332
|
--text-title-md: var(--cg-title-md);
|
|
310
333
|
--text-title-md--line-height: var(--cg-title-line);
|
|
311
334
|
--text-title-md--letter-spacing: var(--cg-title-tracking);
|
|
335
|
+
--text-title-24: var(--cg-title-24);
|
|
336
|
+
--text-title-24--line-height: var(--cg-title-line);
|
|
337
|
+
--text-title-24--letter-spacing: var(--cg-title-tracking);
|
|
312
338
|
--text-title-sm: var(--cg-title-sm);
|
|
313
339
|
--text-title-sm--line-height: var(--cg-title-line);
|
|
314
340
|
--text-title-sm--letter-spacing: var(--cg-title-tracking);
|
|
@@ -324,10 +350,20 @@
|
|
|
324
350
|
--text-body-xl--line-height: var(--cg-body-line);
|
|
325
351
|
--text-body-lg: var(--cg-body-lg);
|
|
326
352
|
--text-body-lg--line-height: var(--cg-body-line);
|
|
353
|
+
/* pricing's three plan names, at --cg-body-line exactly. A body rung and not a
|
|
354
|
+
heading one: nothing at 18 in six frames is near --cg-heading-line's 1.2
|
|
355
|
+
(#248). */
|
|
356
|
+
--text-body-18: var(--cg-body-18);
|
|
357
|
+
--text-body-18--line-height: var(--cg-body-line);
|
|
327
358
|
--text-body-md: var(--cg-body-md);
|
|
328
359
|
--text-body-md--line-height: var(--cg-body-line);
|
|
329
360
|
--text-body-sm: var(--cg-body-sm);
|
|
330
361
|
--text-body-sm--line-height: var(--cg-body-line);
|
|
362
|
+
/* --cg-body-14 has NO utility, for the same reason as --cg-display-64: its
|
|
363
|
+
three non-eyebrow runs read 1.295 and 1.55 and there is no leading to ship.
|
|
364
|
+
Sixteen of the nineteen 14px runs in the frames are the eyebrow, which is
|
|
365
|
+
--text-eyebrow below and already carries its own leading, tracking and
|
|
366
|
+
weight. */
|
|
331
367
|
--text-caption: var(--cg-caption);
|
|
332
368
|
--text-caption--line-height: var(--cg-body-line);
|
|
333
369
|
--text-mono: var(--cg-mono-size);
|
|
@@ -344,6 +380,14 @@
|
|
|
344
380
|
--text-eyebrow--line-height: var(--cg-eyebrow-line);
|
|
345
381
|
--text-eyebrow--letter-spacing: var(--cg-eyebrow-tracking);
|
|
346
382
|
--text-eyebrow--font-weight: var(--cg-eyebrow-weight);
|
|
383
|
+
/* The big figure in a stat block: the UI face at leading-none, which no other
|
|
384
|
+
rung on this ramp is (heading is 1.2, body is 1.46). Ten nodes on home and
|
|
385
|
+
enterprise, all Manrope Medium 32 (#248). A role set rather than a bare
|
|
386
|
+
size, on the eyebrow's pattern, so the leading and the weight cannot drift
|
|
387
|
+
into a component inline. */
|
|
388
|
+
--text-stat: var(--cg-stat-size);
|
|
389
|
+
--text-stat--line-height: var(--cg-stat-line);
|
|
390
|
+
--text-stat--font-weight: var(--cg-stat-weight);
|
|
347
391
|
|
|
348
392
|
/* ---- weights ----
|
|
349
393
|
`--font-weight-display` is unreachable and kept only for completeness. Both
|
package/dist/tokens/base.json
CHANGED
|
@@ -926,7 +926,7 @@
|
|
|
926
926
|
}
|
|
927
927
|
},
|
|
928
928
|
"h1-display-ramp": {
|
|
929
|
-
"$description": "H1 display ramp: uppercase, condensed, Primary or white\n\nStepped down one notch 2026-08-06 (operator: main headings read too\ndominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.",
|
|
929
|
+
"$description": "H1 display ramp: uppercase, condensed, Primary or white\n\nStepped down one notch 2026-08-06 (operator: main headings read too\ndominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.\n\n2026-09-19 (#248): display-64 and display-48 added, named by pixel value — see\ndisplay-64's note. With the eight \"SET FONT TO TANKER\" placeholder nodes\nexcluded, ALL 68 remaining runs at 56 across all six frames are explicit\nleading-none with zero tracking, as are all of 48, 36 and 32. #248's comment\nreports 56 carrying two ratios and two trackings; that was one un-converted Anton\nnode (home 8216:1762) and three of its runs. This does NOT reopen the proposal to\nmove --cg-display-line and --cg-display-tracking: 26 (77 runs) genuinely carries\n1.0, 1.1 and Auto, so no single library default is right for it, and\n--cg-display-line-tight already covers the cases that want 1. Both shipped values\nstay exactly as they are.",
|
|
930
930
|
"display-3xl": {
|
|
931
931
|
"$value": "120px",
|
|
932
932
|
"$type": "dimension",
|
|
@@ -947,6 +947,16 @@
|
|
|
947
947
|
}
|
|
948
948
|
}
|
|
949
949
|
},
|
|
950
|
+
"display-64": {
|
|
951
|
+
"$value": "64px",
|
|
952
|
+
"$type": "dimension",
|
|
953
|
+
"$description": "SOURCE: home 8204:3110 (\"WHERE DOES IT LIVE?\"), thegrid 8364:4363 (\"Say what it\nneeds\"). Two nodes, two frames, Tanker, no tracking. The widest hole in the ramp:\n36px separate --cg-display-xl 56 from --cg-display-2xl 92, and both consumers\nwere clamping their own value into it.\n\nNAMED BY PIXEL VALUE, 2026-09-19 (#248). Every size added in this round falls\nSTRICTLY BETWEEN two shipped rungs, which is the second consecutive census to\nhit that wall — the tanker-title-ramp note records the first. A t-shirt suffix\nencodes ORDER, and order is what an insertion breaks; renaming a shipped rung is\nbreaking for two published consumers, and a third Tanker ramp would carry the\nsame face, leading and roles as the two that exist and therefore no information.\nSo the ramp prefix still means (face, leading, role) and the suffix becomes the\nsize. A px-named rung cannot be made wrong by a later insertion.\n\nSIZE ONLY, AND DELIBERATELY.\n\nA rung gets a --text-* utility only where the frames settled on a leading. Where\nthey did not, it ships as a custom property alone: theme.css's own rule is that\n\"a companion that invents a value is worse than an absent one\", and\ntokens.test.mjs requires every --text-* to carry a --line-height, so a utility\nhere could only exist by inventing one.\n\nThe two nodes do not agree on a leading and only one of them expresses an\nopinion: thegrid sets 70px (ratio 1.094) and home leaves lineHeightUnit at\nINTRINSIC_%, i.e. Figma's Auto, which is Tanker's own 1.35 default and not a\ndesign decision at all. A leading shipped here would be wrong for one of its two\nusers. The same reasoning is why 96px (thegrid 8364:3957, lh 56 — a ratio of\n0.583, 96px type in a 56px line box) is recorded and still has no rung.",
|
|
954
|
+
"$extensions": {
|
|
955
|
+
"io.cloudgrid": {
|
|
956
|
+
"css": "--cg-display-64"
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
},
|
|
950
960
|
"display-xl": {
|
|
951
961
|
"$value": "56px",
|
|
952
962
|
"$type": "dimension",
|
|
@@ -956,6 +966,16 @@
|
|
|
956
966
|
}
|
|
957
967
|
}
|
|
958
968
|
},
|
|
969
|
+
"display-48": {
|
|
970
|
+
"$value": "48px",
|
|
971
|
+
"$type": "dimension",
|
|
972
|
+
"$description": "SOURCE: home 8216:1868, the \"Copy this command INTO your aI tool to Plug in\"\nstatement. Tanker, explicit leading-none (lineHeightUnit FONT_SIZE_% at 100), no\ntracking.\n\nONE NODE ON ONE FRAME, stated plainly because the count in #248 reads as four:\nthe node carries four per-character colour runs and a run census counts each of\nthem. One node is not disqualifying here — --cg-display-3xl 120 is also a single\nnode on a single frame and 0.28.0 shipped it — but the evidence is one headline,\nnot four.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).\n\nIt carries display-line-tight and title-tracking rather than --cg-display-line\nand --cg-display-tracking, the same as --cg-display-2xl and --cg-display-3xl:\nthe node is explicitly 1.0 / 0, and the two legacy values were tuned for a\ncondensed face this package no longer ships (#240).",
|
|
973
|
+
"$extensions": {
|
|
974
|
+
"io.cloudgrid": {
|
|
975
|
+
"css": "--cg-display-48"
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
},
|
|
959
979
|
"display-lg": {
|
|
960
980
|
"$value": "44px",
|
|
961
981
|
"$type": "dimension",
|
|
@@ -1013,7 +1033,7 @@
|
|
|
1013
1033
|
}
|
|
1014
1034
|
},
|
|
1015
1035
|
"tanker-title-ramp": {
|
|
1016
|
-
"$description": "The display face at CARD scale, 2026-09-18.\n\nFour rungs the canonical frames use constantly and this package could spell\nnone of. They are separated from h1-display-ramp rather than wedged into it\nbecause the two ramps disagree everywhere they overlap, and the disagreement is\nnot resolvable by addition:\n\n frames 120 96 92 64 56 40 38 32 30 26 20\n package 56 44 38 30\n\n56, 38 and 30 coincide. 44 is used by nothing in any of the six frames. Every\nother size the frames ask for falls BETWEEN two shipped rungs — 40 between 44\nand 38, 26 between 30 and heading-lg's 22 — so there is no name left on the\nt-shirt ramp that is both free and in the right place, and renaming a shipped\nrung is a breaking change for two published consumers.\n\nThey are also not headings. --cg-heading-* is the Manrope ramp at 1.2 leading;\nthis is the display FACE at leading-none, and --cg-title-sm and\n--cg-heading-md are both 20px precisely because a face and a leading are what\nseparate them, not a size.\n\nSIZES, with where they were read:\n 40 home 8204:2589, enterprise 7038:1244, pricing 7785:9809, thegrid 8364:4382\n 26 teams 7297:8606, builders 6916:8648, enterprise 7038:1192,\n pricing 7788:10264, thegrid 8364:3882\n 20 teams 7172:7760, builders 6919:8783, thegrid petal labels\n\npricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker\neyebrows. Single-frame, so recorded and not given a rung.",
|
|
1036
|
+
"$description": "The display face at CARD scale, 2026-09-18.\n\nFour rungs the canonical frames use constantly and this package could spell\nnone of. They are separated from h1-display-ramp rather than wedged into it\nbecause the two ramps disagree everywhere they overlap, and the disagreement is\nnot resolvable by addition:\n\n frames 120 96 92 64 56 40 38 32 30 26 20\n package 56 44 38 30\n\n56, 38 and 30 coincide. 44 is used by nothing in any of the six frames. Every\nother size the frames ask for falls BETWEEN two shipped rungs — 40 between 44\nand 38, 26 between 30 and heading-lg's 22 — so there is no name left on the\nt-shirt ramp that is both free and in the right place, and renaming a shipped\nrung is a breaking change for two published consumers.\n\nThey are also not headings. --cg-heading-* is the Manrope ramp at 1.2 leading;\nthis is the display FACE at leading-none, and --cg-title-sm and\n--cg-heading-md are both 20px precisely because a face and a leading are what\nseparate them, not a size.\n\nSIZES, with where they were read:\n 40 home 8204:2589, enterprise 7038:1244, pricing 7785:9809, thegrid 8364:4382\n 26 teams 7297:8606, builders 6916:8648, enterprise 7038:1192,\n pricing 7788:10264, thegrid 8364:3882\n 20 teams 7172:7760, builders 6919:8783, thegrid petal labels\n\npricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker\neyebrows. Single-frame, so recorded and not given a rung.\n\n---\n\n2026-09-19 (#248). Three more rungs — title-36, title-32, title-24 — and the\nt-shirt suffix stops here. See --cg-display-64's note for why they are named by\npixel value and the group note above for why this ramp is separate from\nh1-display-ramp in the first place.\n\nRECORDED AND DELIBERATELY NOT GIVEN A RUNG:\n\n 28px. #248 proposes it on two home nodes. Both are placeholders the designer\n has not converted: 8204:3066 \"lead - SET FONT TO TANKER\" (lineHeight 92.6%,\n letterSpacing 0.24) and 8216:1784 \"txt:RIGHT HERE - SET FONT TO TANKER\"\n (106%, 0.42). They keep the previous face's metrics — a third node in the same\n set, 8216:1762, is still literally set in Anton — so there is no settled 28px\n anything in the six frames. Six home nodes and two thegrid nodes carry that\n \"SET FONT TO TANKER\" marker; every Tanker node in the frames that emits a\n letter-spacing is one of them.\n\n 32px on Inter (builders 7172:8458, the \"$ grid plug\" pill). Off-system in the\n design itself; #243 recorded it.\n\nON AUTO LEADING. Figma's lineHeightUnit distinguishes a decision from an\nabsence: FONT_SIZE_% and PIXELS mean the designer set a leading, INTRINSIC_%\nmeans Auto and the number in the file is the FACE's default, which for Tanker is\n1.35. Every 1.35 in a Tanker census is INTRINSIC_%. A rung built on one would\nencode Tanker's metrics as a brand decision.",
|
|
1017
1037
|
"title-lg": {
|
|
1018
1038
|
"$value": "40px",
|
|
1019
1039
|
"$type": "dimension",
|
|
@@ -1024,6 +1044,26 @@
|
|
|
1024
1044
|
}
|
|
1025
1045
|
}
|
|
1026
1046
|
},
|
|
1047
|
+
"title-36": {
|
|
1048
|
+
"$value": "36px",
|
|
1049
|
+
"$type": "dimension",
|
|
1050
|
+
"$description": "SOURCE: enterprise 7039:5124, 7039:5119, 7039:5129 — the three audience column\nheadings (\"Platform and security teams\", \"Innovation Leaders\", \"Business\nfunctions\"). Three nodes, one frame, all explicit leading-none, no tracking.\n\nOne frame, and 2px from --cg-display-md 38. It is a rung rather than an\napproximation of 38 because it is the card/column role, not the section-heading\nrole, and because rounding a measured 36 onto 38 is the kind of \"close enough\"\nthat #243 had to unpick when ui#239's table called this ramp's 26 a \"24px card\ntitle\".\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
|
|
1051
|
+
"$extensions": {
|
|
1052
|
+
"io.cloudgrid": {
|
|
1053
|
+
"css": "--cg-title-36"
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
"title-32": {
|
|
1058
|
+
"$value": "32px",
|
|
1059
|
+
"$type": "dimension",
|
|
1060
|
+
"$description": "SOURCE: home 8204:2590 (\"Learn how the grid can help you\"), pricing 7785:10059 /\n10068 / 10073 / 10076 / 10081 / 10084 (the six plan prices). Seven nodes, two\nframes, all explicit leading-none, no tracking.\n\nTHE DISPLAY FACE HALF OF 32. The frames also draw 32 on the UI face, ten times,\nand that is --cg-stat-size on the Manrope ramp, not this. Two rungs at one size\nis the ramp rule working as intended, exactly as --cg-title-sm and\n--cg-heading-md are both 20px: a rung is a face, a leading and a role, not a\nnumber.\n\n#243 recorded pricing's 32 as \"one 32px Tanker number, single-frame, so recorded\nand not given a rung\". The rebuilt extract finds seven nodes on two frames.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
|
|
1061
|
+
"$extensions": {
|
|
1062
|
+
"io.cloudgrid": {
|
|
1063
|
+
"css": "--cg-title-32"
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
},
|
|
1027
1067
|
"title-md": {
|
|
1028
1068
|
"$value": "26px",
|
|
1029
1069
|
"$type": "dimension",
|
|
@@ -1034,6 +1074,16 @@
|
|
|
1034
1074
|
}
|
|
1035
1075
|
}
|
|
1036
1076
|
},
|
|
1077
|
+
"title-24": {
|
|
1078
|
+
"$value": "24px",
|
|
1079
|
+
"$type": "dimension",
|
|
1080
|
+
"$description": "SOURCE: home 8204:2813, 8204:2753 / 2758 / 2763; enterprise 7039:5084, 7039:5088,\n7039:5093, 7039:5097, 7395:666. Twelve nodes, two frames. Nine set an explicit\nleading-none; two leave lineHeightUnit at INTRINSIC_% (Figma's Auto, no\ndecision); one, home 8216:1908, sets 30px. No node carries tracking.\n\nTWO PIXELS FROM --cg-title-md 26, AND BOTH ARE REAL. 26 is 77 runs on six frames\nand 24 is 12 on two; they coexist in the same design and in some cases in the\nsame section. Note that #239's type table called the 26 rung \"24px card title\"\nfrom a consumer's approximation — this rung is the actual 24, measured, and does\nnot retroactively make that table right.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
|
|
1081
|
+
"$extensions": {
|
|
1082
|
+
"io.cloudgrid": {
|
|
1083
|
+
"css": "--cg-title-24"
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1037
1087
|
"title-sm": {
|
|
1038
1088
|
"$value": "20px",
|
|
1039
1089
|
"$type": "dimension",
|
|
@@ -1124,6 +1174,16 @@
|
|
|
1124
1174
|
}
|
|
1125
1175
|
}
|
|
1126
1176
|
},
|
|
1177
|
+
"body-18": {
|
|
1178
|
+
"$value": "18px",
|
|
1179
|
+
"$type": "dimension",
|
|
1180
|
+
"$description": "SOURCE: pricing 7785:9832 / 9858 / 9872, the three plan names — Space Grotesk\nBold 18 at line-height 1.46, which is --cg-body-line exactly. Also thegrid\n8364:4352 / 4355 / 4358 and 8364:4385, Manrope 18 at 1.5.\n\nA BODY RUNG, NOT A HEADING ONE. #248 asks which ramp 18 belongs on, since it\nsits between --cg-heading-sm 17 and --cg-heading-md 20. Every node at 18 that\nexpresses a leading at all reads 1.46 or 1.5; nothing is near\n--cg-heading-line's 1.2. home 8204:3090 and 8204:3105 draw Tanker at 18 and both\nare INTRINSIC_% (Auto), so they express no opinion and do not make this a\ndisplay rung either.\n\n#243 recorded pricing's 18 as \"one 18px bold plan name. Single node, one frame,\n1px off --cg-body-lg\". It is three nodes, and the 1px is the point: a consumer\notherwise rounds it onto --cg-body-lg 19 and the plan-name row drifts by a pixel\nagainst the Figma it is being checked against.\n\nNamed by pixel value; --cg-display-64's note has the reasoning (#248).",
|
|
1181
|
+
"$extensions": {
|
|
1182
|
+
"io.cloudgrid": {
|
|
1183
|
+
"css": "--cg-body-18"
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1127
1187
|
"body-md": {
|
|
1128
1188
|
"$value": "16px",
|
|
1129
1189
|
"$type": "dimension",
|
|
@@ -1144,6 +1204,16 @@
|
|
|
1144
1204
|
}
|
|
1145
1205
|
}
|
|
1146
1206
|
},
|
|
1207
|
+
"body-14": {
|
|
1208
|
+
"$value": "14px",
|
|
1209
|
+
"$type": "dimension",
|
|
1210
|
+
"$description": "SOURCE: home 8216:1768, 8216:1769, 8216:1771 — the three lines of the prompt-and-\nreply mock. Manrope Regular 14. Three runs, one frame.\n\nREAD THIS BEFORE TREATING 14 AS A GAP. #248 counts nineteen 14px runs on four\nframes and calls it one of its two strongest cases. SIXTEEN OF THE NINETEEN ARE\nTHE EYEBROW — Manrope 600, leading 1.2, tracking 1.4px, uppercase — which 0.28.0\nalready ships whole as --cg-eyebrow-size / -line / -tracking / -weight, and\nthree of the nodes the issue cites (teams 7172:8463, builders 6916:8053,\nenterprise 7038:1184) are the very nodes in that token's own docblock. 14 was\nalready closed; the four frames are the eyebrow's four frames.\n\nSo this rung exists for the residue, and the residue is thin. It ships anyway\nbecause without it the only 14 in the package is --cg-eyebrow-size, and a\nconsumer who wants plain 14px text will reach for it and sooner or later reach\nfor text-eyebrow with it, inheriting a weight and a tracking nobody asked for.\n--cg-mono-size is also 14 and is the mono face.\n\nSIZE ONLY.\n\nA rung gets a --text-* utility only where the frames settled on a leading. Where\nthey did not, it ships as a custom property alone: theme.css's own rule is that\n\"a companion that invents a value is worse than an absent one\", and\ntokens.test.mjs requires every --text-* to carry a --line-height, so a utility\nhere could only exist by inventing one. The three runs read 1.295 and 1.55; there is nothing\nto ship.",
|
|
1211
|
+
"$extensions": {
|
|
1212
|
+
"io.cloudgrid": {
|
|
1213
|
+
"css": "--cg-body-14"
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1147
1217
|
"caption": {
|
|
1148
1218
|
"$value": "13px",
|
|
1149
1219
|
"$type": "dimension",
|
|
@@ -1244,6 +1314,35 @@
|
|
|
1244
1314
|
"css": "--cg-eyebrow-weight"
|
|
1245
1315
|
}
|
|
1246
1316
|
}
|
|
1317
|
+
},
|
|
1318
|
+
"stat-size": {
|
|
1319
|
+
"$value": "32px",
|
|
1320
|
+
"$type": "dimension",
|
|
1321
|
+
"$description": "The big figure in a stat block: the UI face at leading-none.\n\nSOURCE: home 8204:2769 / 2774 / 2779 / 2784 / 2789 and enterprise 7038:4564 /\n4569 / 4574 / 4579 / 4584 — \"2\", \"184\", \"4\", \"8\", \"$0\". Ten nodes, two frames,\nManrope Medium 32, every one of them explicit leading-none with no tracking.\n\nTHE UI-FACE HALF OF 32; --cg-title-32 is the display-face half. This is a role\nthe Manrope ramp could not spell at all: --cg-heading-* is 1.2 and --cg-body-* is\n1.46, and nothing on this face was at 1.\n\nIt is a role set rather than a bare size, on the --cg-eyebrow-* pattern, for the\nreason that block gives: the figure carries a leading and a weight as well as a\nsize, and spelling two of the three inline is how a component drifts.\n\nKNOWN DIVERGENCE, not fixed here: StatTile renders its value as\n`font-display text-display-sm` — Tanker 30 at --cg-display-line. The frames say\nManrope Medium 32 at leading-none. Repointing it changes what every StatTile\nrenders today, so it is a separate change with its own changeset.\n\nThe same five figures appear again at 18.149px inside scaled product mocks on\nhome, thegrid and enterprise. That is the per-mock scale factor #248 documents,\nnot a second rung.",
|
|
1322
|
+
"$extensions": {
|
|
1323
|
+
"io.cloudgrid": {
|
|
1324
|
+
"css": "--cg-stat-size"
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
"stat-line": {
|
|
1329
|
+
"$value": "1",
|
|
1330
|
+
"$type": "number",
|
|
1331
|
+
"$description": "leading-none, from all ten nodes. Spelled as a number rather than aliased onto\n--cg-display-line-tight: that alias exists so a retune of the DISPLAY leading\nreaches the title ramp too, and the stat figure is on the UI face and must not\nmove with it.",
|
|
1332
|
+
"$extensions": {
|
|
1333
|
+
"io.cloudgrid": {
|
|
1334
|
+
"css": "--cg-stat-line"
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
"stat-weight": {
|
|
1339
|
+
"$value": "{weights.weight-medium}",
|
|
1340
|
+
"$description": "500, from all ten nodes. Manrope Medium, not the eyebrow's 600 and not\n--cg-label-weight's 500 by coincidence — aliased onto the weight rung so it\ntracks the brand's Medium.",
|
|
1341
|
+
"$extensions": {
|
|
1342
|
+
"io.cloudgrid": {
|
|
1343
|
+
"css": "--cg-stat-weight"
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1247
1346
|
}
|
|
1248
1347
|
},
|
|
1249
1348
|
"spacing": {
|
package/dist/tokens.css
CHANGED
|
@@ -889,7 +889,18 @@
|
|
|
889
889
|
H1 display ramp: uppercase, condensed, Primary or white
|
|
890
890
|
|
|
891
891
|
Stepped down one notch 2026-08-06 (operator: main headings read too
|
|
892
|
-
dominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.
|
|
892
|
+
dominant). The SOURCE values were xl 64 / lg 48 / md 42 / sm 32.
|
|
893
|
+
|
|
894
|
+
2026-09-19 (#248): display-64 and display-48 added, named by pixel value — see
|
|
895
|
+
display-64's note. With the eight "SET FONT TO TANKER" placeholder nodes
|
|
896
|
+
excluded, ALL 68 remaining runs at 56 across all six frames are explicit
|
|
897
|
+
leading-none with zero tracking, as are all of 48, 36 and 32. #248's comment
|
|
898
|
+
reports 56 carrying two ratios and two trackings; that was one un-converted Anton
|
|
899
|
+
node (home 8216:1762) and three of its runs. This does NOT reopen the proposal to
|
|
900
|
+
move --cg-display-line and --cg-display-tracking: 26 (77 runs) genuinely carries
|
|
901
|
+
1.0, 1.1 and Auto, so no single library default is right for it, and
|
|
902
|
+
--cg-display-line-tight already covers the cases that want 1. Both shipped values
|
|
903
|
+
stay exactly as they are. */
|
|
893
904
|
/*
|
|
894
905
|
SOURCE: home 8204:2556, the hero headline, Tanker at leading-none.
|
|
895
906
|
|
|
@@ -909,7 +920,55 @@
|
|
|
909
920
|
not rungs. A value invented to complete a grid is the thing this file keeps
|
|
910
921
|
refusing to add. */
|
|
911
922
|
--cg-display-2xl: 92px;
|
|
923
|
+
/*
|
|
924
|
+
SOURCE: home 8204:3110 ("WHERE DOES IT LIVE?"), thegrid 8364:4363 ("Say what it
|
|
925
|
+
needs"). Two nodes, two frames, Tanker, no tracking. The widest hole in the ramp:
|
|
926
|
+
36px separate --cg-display-xl 56 from --cg-display-2xl 92, and both consumers
|
|
927
|
+
were clamping their own value into it.
|
|
928
|
+
|
|
929
|
+
NAMED BY PIXEL VALUE, 2026-09-19 (#248). Every size added in this round falls
|
|
930
|
+
STRICTLY BETWEEN two shipped rungs, which is the second consecutive census to
|
|
931
|
+
hit that wall — the tanker-title-ramp note records the first. A t-shirt suffix
|
|
932
|
+
encodes ORDER, and order is what an insertion breaks; renaming a shipped rung is
|
|
933
|
+
breaking for two published consumers, and a third Tanker ramp would carry the
|
|
934
|
+
same face, leading and roles as the two that exist and therefore no information.
|
|
935
|
+
So the ramp prefix still means (face, leading, role) and the suffix becomes the
|
|
936
|
+
size. A px-named rung cannot be made wrong by a later insertion.
|
|
937
|
+
|
|
938
|
+
SIZE ONLY, AND DELIBERATELY.
|
|
939
|
+
|
|
940
|
+
A rung gets a --text-* utility only where the frames settled on a leading. Where
|
|
941
|
+
they did not, it ships as a custom property alone: theme.css's own rule is that
|
|
942
|
+
"a companion that invents a value is worse than an absent one", and
|
|
943
|
+
tokens.test.mjs requires every --text-* to carry a --line-height, so a utility
|
|
944
|
+
here could only exist by inventing one.
|
|
945
|
+
|
|
946
|
+
The two nodes do not agree on a leading and only one of them expresses an
|
|
947
|
+
opinion: thegrid sets 70px (ratio 1.094) and home leaves lineHeightUnit at
|
|
948
|
+
INTRINSIC_%, i.e. Figma's Auto, which is Tanker's own 1.35 default and not a
|
|
949
|
+
design decision at all. A leading shipped here would be wrong for one of its two
|
|
950
|
+
users. The same reasoning is why 96px (thegrid 8364:3957, lh 56 — a ratio of
|
|
951
|
+
0.583, 96px type in a 56px line box) is recorded and still has no rung. */
|
|
952
|
+
--cg-display-64: 64px;
|
|
912
953
|
--cg-display-xl: 56px;
|
|
954
|
+
/*
|
|
955
|
+
SOURCE: home 8216:1868, the "Copy this command INTO your aI tool to Plug in"
|
|
956
|
+
statement. Tanker, explicit leading-none (lineHeightUnit FONT_SIZE_% at 100), no
|
|
957
|
+
tracking.
|
|
958
|
+
|
|
959
|
+
ONE NODE ON ONE FRAME, stated plainly because the count in #248 reads as four:
|
|
960
|
+
the node carries four per-character colour runs and a run census counts each of
|
|
961
|
+
them. One node is not disqualifying here — --cg-display-3xl 120 is also a single
|
|
962
|
+
node on a single frame and 0.28.0 shipped it — but the evidence is one headline,
|
|
963
|
+
not four.
|
|
964
|
+
|
|
965
|
+
Named by pixel value; --cg-display-64's note has the reasoning (#248).
|
|
966
|
+
|
|
967
|
+
It carries display-line-tight and title-tracking rather than --cg-display-line
|
|
968
|
+
and --cg-display-tracking, the same as --cg-display-2xl and --cg-display-3xl:
|
|
969
|
+
the node is explicitly 1.0 / 0, and the two legacy values were tuned for a
|
|
970
|
+
condensed face this package no longer ships (#240). */
|
|
971
|
+
--cg-display-48: 48px;
|
|
913
972
|
--cg-display-lg: 44px;
|
|
914
973
|
/* SOURCE: onboarding H1 (was 42px) */
|
|
915
974
|
--cg-display-md: 38px;
|
|
@@ -957,15 +1016,85 @@
|
|
|
957
1016
|
20 teams 7172:7760, builders 6919:8783, thegrid petal labels
|
|
958
1017
|
|
|
959
1018
|
pricing 7785:10059 draws one 32px Tanker number and thegrid draws 15px Tanker
|
|
960
|
-
eyebrows. Single-frame, so recorded and not given a rung.
|
|
1019
|
+
eyebrows. Single-frame, so recorded and not given a rung.
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
2026-09-19 (#248). Three more rungs — title-36, title-32, title-24 — and the
|
|
1024
|
+
t-shirt suffix stops here. See --cg-display-64's note for why they are named by
|
|
1025
|
+
pixel value and the group note above for why this ramp is separate from
|
|
1026
|
+
h1-display-ramp in the first place.
|
|
1027
|
+
|
|
1028
|
+
RECORDED AND DELIBERATELY NOT GIVEN A RUNG:
|
|
1029
|
+
|
|
1030
|
+
28px. #248 proposes it on two home nodes. Both are placeholders the designer
|
|
1031
|
+
has not converted: 8204:3066 "lead - SET FONT TO TANKER" (lineHeight 92.6%,
|
|
1032
|
+
letterSpacing 0.24) and 8216:1784 "txt:RIGHT HERE - SET FONT TO TANKER"
|
|
1033
|
+
(106%, 0.42). They keep the previous face's metrics — a third node in the same
|
|
1034
|
+
set, 8216:1762, is still literally set in Anton — so there is no settled 28px
|
|
1035
|
+
anything in the six frames. Six home nodes and two thegrid nodes carry that
|
|
1036
|
+
"SET FONT TO TANKER" marker; every Tanker node in the frames that emits a
|
|
1037
|
+
letter-spacing is one of them.
|
|
1038
|
+
|
|
1039
|
+
32px on Inter (builders 7172:8458, the "$ grid plug" pill). Off-system in the
|
|
1040
|
+
design itself; #243 recorded it.
|
|
1041
|
+
|
|
1042
|
+
ON AUTO LEADING. Figma's lineHeightUnit distinguishes a decision from an
|
|
1043
|
+
absence: FONT_SIZE_% and PIXELS mean the designer set a leading, INTRINSIC_%
|
|
1044
|
+
means Auto and the number in the file is the FACE's default, which for Tanker is
|
|
1045
|
+
1.35. Every 1.35 in a Tanker census is INTRINSIC_%. A rung built on one would
|
|
1046
|
+
encode Tanker's metrics as a brand decision. */
|
|
961
1047
|
/*
|
|
962
1048
|
Four frames. The governance card title, the plan price, the sub-heading under a section statement. */
|
|
963
1049
|
--cg-title-lg: 40px;
|
|
1050
|
+
/*
|
|
1051
|
+
SOURCE: enterprise 7039:5124, 7039:5119, 7039:5129 — the three audience column
|
|
1052
|
+
headings ("Platform and security teams", "Innovation Leaders", "Business
|
|
1053
|
+
functions"). Three nodes, one frame, all explicit leading-none, no tracking.
|
|
1054
|
+
|
|
1055
|
+
One frame, and 2px from --cg-display-md 38. It is a rung rather than an
|
|
1056
|
+
approximation of 38 because it is the card/column role, not the section-heading
|
|
1057
|
+
role, and because rounding a measured 36 onto 38 is the kind of "close enough"
|
|
1058
|
+
that #243 had to unpick when ui#239's table called this ramp's 26 a "24px card
|
|
1059
|
+
title".
|
|
1060
|
+
|
|
1061
|
+
Named by pixel value; --cg-display-64's note has the reasoning (#248). */
|
|
1062
|
+
--cg-title-36: 36px;
|
|
1063
|
+
/*
|
|
1064
|
+
SOURCE: home 8204:2590 ("Learn how the grid can help you"), pricing 7785:10059 /
|
|
1065
|
+
10068 / 10073 / 10076 / 10081 / 10084 (the six plan prices). Seven nodes, two
|
|
1066
|
+
frames, all explicit leading-none, no tracking.
|
|
1067
|
+
|
|
1068
|
+
THE DISPLAY FACE HALF OF 32. The frames also draw 32 on the UI face, ten times,
|
|
1069
|
+
and that is --cg-stat-size on the Manrope ramp, not this. Two rungs at one size
|
|
1070
|
+
is the ramp rule working as intended, exactly as --cg-title-sm and
|
|
1071
|
+
--cg-heading-md are both 20px: a rung is a face, a leading and a role, not a
|
|
1072
|
+
number.
|
|
1073
|
+
|
|
1074
|
+
#243 recorded pricing's 32 as "one 32px Tanker number, single-frame, so recorded
|
|
1075
|
+
and not given a rung". The rebuilt extract finds seven nodes on two frames.
|
|
1076
|
+
|
|
1077
|
+
Named by pixel value; --cg-display-64's note has the reasoning (#248). */
|
|
1078
|
+
--cg-title-32: 32px;
|
|
964
1079
|
/*
|
|
965
1080
|
Five frames. The card title and the FAQ question — the most common display size in the design after the section heading.
|
|
966
1081
|
|
|
967
1082
|
ui#239's type table calls this rung "24px card title". Measured on five frames it is 26, not 24, and nothing in any frame is 24. The table was written from a consumer's approximation rather than from the node. */
|
|
968
1083
|
--cg-title-md: 26px;
|
|
1084
|
+
/*
|
|
1085
|
+
SOURCE: home 8204:2813, 8204:2753 / 2758 / 2763; enterprise 7039:5084, 7039:5088,
|
|
1086
|
+
7039:5093, 7039:5097, 7395:666. Twelve nodes, two frames. Nine set an explicit
|
|
1087
|
+
leading-none; two leave lineHeightUnit at INTRINSIC_% (Figma's Auto, no
|
|
1088
|
+
decision); one, home 8216:1908, sets 30px. No node carries tracking.
|
|
1089
|
+
|
|
1090
|
+
TWO PIXELS FROM --cg-title-md 26, AND BOTH ARE REAL. 26 is 77 runs on six frames
|
|
1091
|
+
and 24 is 12 on two; they coexist in the same design and in some cases in the
|
|
1092
|
+
same section. Note that #239's type table called the 26 rung "24px card title"
|
|
1093
|
+
from a consumer's approximation — this rung is the actual 24, measured, and does
|
|
1094
|
+
not retroactively make that table right.
|
|
1095
|
+
|
|
1096
|
+
Named by pixel value; --cg-display-64's note has the reasoning (#248). */
|
|
1097
|
+
--cg-title-24: 24px;
|
|
969
1098
|
/*
|
|
970
1099
|
Three frames. The label inside a step circle or a diagram petal. Same size as --cg-heading-md and a different face and leading; see the group note. */
|
|
971
1100
|
--cg-title-sm: 20px;
|
|
@@ -1014,10 +1143,56 @@
|
|
|
1014
1143
|
--cg-body-xl: 20px;
|
|
1015
1144
|
/* SOURCE: H1 subline */
|
|
1016
1145
|
--cg-body-lg: 19px;
|
|
1146
|
+
/*
|
|
1147
|
+
SOURCE: pricing 7785:9832 / 9858 / 9872, the three plan names — Space Grotesk
|
|
1148
|
+
Bold 18 at line-height 1.46, which is --cg-body-line exactly. Also thegrid
|
|
1149
|
+
8364:4352 / 4355 / 4358 and 8364:4385, Manrope 18 at 1.5.
|
|
1150
|
+
|
|
1151
|
+
A BODY RUNG, NOT A HEADING ONE. #248 asks which ramp 18 belongs on, since it
|
|
1152
|
+
sits between --cg-heading-sm 17 and --cg-heading-md 20. Every node at 18 that
|
|
1153
|
+
expresses a leading at all reads 1.46 or 1.5; nothing is near
|
|
1154
|
+
--cg-heading-line's 1.2. home 8204:3090 and 8204:3105 draw Tanker at 18 and both
|
|
1155
|
+
are INTRINSIC_% (Auto), so they express no opinion and do not make this a
|
|
1156
|
+
display rung either.
|
|
1157
|
+
|
|
1158
|
+
#243 recorded pricing's 18 as "one 18px bold plan name. Single node, one frame,
|
|
1159
|
+
1px off --cg-body-lg". It is three nodes, and the 1px is the point: a consumer
|
|
1160
|
+
otherwise rounds it onto --cg-body-lg 19 and the plan-name row drifts by a pixel
|
|
1161
|
+
against the Figma it is being checked against.
|
|
1162
|
+
|
|
1163
|
+
Named by pixel value; --cg-display-64's note has the reasoning (#248). */
|
|
1164
|
+
--cg-body-18: 18px;
|
|
1017
1165
|
/* SOURCE: default UI text */
|
|
1018
1166
|
--cg-body-md: 16px;
|
|
1019
1167
|
/* SOURCE: card body, legal */
|
|
1020
1168
|
--cg-body-sm: 15px;
|
|
1169
|
+
/*
|
|
1170
|
+
SOURCE: home 8216:1768, 8216:1769, 8216:1771 — the three lines of the prompt-and-
|
|
1171
|
+
reply mock. Manrope Regular 14. Three runs, one frame.
|
|
1172
|
+
|
|
1173
|
+
READ THIS BEFORE TREATING 14 AS A GAP. #248 counts nineteen 14px runs on four
|
|
1174
|
+
frames and calls it one of its two strongest cases. SIXTEEN OF THE NINETEEN ARE
|
|
1175
|
+
THE EYEBROW — Manrope 600, leading 1.2, tracking 1.4px, uppercase — which 0.28.0
|
|
1176
|
+
already ships whole as --cg-eyebrow-size / -line / -tracking / -weight, and
|
|
1177
|
+
three of the nodes the issue cites (teams 7172:8463, builders 6916:8053,
|
|
1178
|
+
enterprise 7038:1184) are the very nodes in that token's own docblock. 14 was
|
|
1179
|
+
already closed; the four frames are the eyebrow's four frames.
|
|
1180
|
+
|
|
1181
|
+
So this rung exists for the residue, and the residue is thin. It ships anyway
|
|
1182
|
+
because without it the only 14 in the package is --cg-eyebrow-size, and a
|
|
1183
|
+
consumer who wants plain 14px text will reach for it and sooner or later reach
|
|
1184
|
+
for text-eyebrow with it, inheriting a weight and a tracking nobody asked for.
|
|
1185
|
+
--cg-mono-size is also 14 and is the mono face.
|
|
1186
|
+
|
|
1187
|
+
SIZE ONLY.
|
|
1188
|
+
|
|
1189
|
+
A rung gets a --text-* utility only where the frames settled on a leading. Where
|
|
1190
|
+
they did not, it ships as a custom property alone: theme.css's own rule is that
|
|
1191
|
+
"a companion that invents a value is worse than an absent one", and
|
|
1192
|
+
tokens.test.mjs requires every --text-* to carry a --line-height, so a utility
|
|
1193
|
+
here could only exist by inventing one. The three runs read 1.295 and 1.55; there is nothing
|
|
1194
|
+
to ship. */
|
|
1195
|
+
--cg-body-14: 14px;
|
|
1021
1196
|
/* SOURCE: footer, helper */
|
|
1022
1197
|
--cg-caption: 13px;
|
|
1023
1198
|
/*
|
|
@@ -1069,6 +1244,41 @@
|
|
|
1069
1244
|
Note this is NOT --cg-label-weight, which is 500. The eyebrow and the label are
|
|
1070
1245
|
different rungs; see the group note. */
|
|
1071
1246
|
--cg-eyebrow-weight: var(--cg-weight-bold);
|
|
1247
|
+
/*
|
|
1248
|
+
The big figure in a stat block: the UI face at leading-none.
|
|
1249
|
+
|
|
1250
|
+
SOURCE: home 8204:2769 / 2774 / 2779 / 2784 / 2789 and enterprise 7038:4564 /
|
|
1251
|
+
4569 / 4574 / 4579 / 4584 — "2", "184", "4", "8", "$0". Ten nodes, two frames,
|
|
1252
|
+
Manrope Medium 32, every one of them explicit leading-none with no tracking.
|
|
1253
|
+
|
|
1254
|
+
THE UI-FACE HALF OF 32; --cg-title-32 is the display-face half. This is a role
|
|
1255
|
+
the Manrope ramp could not spell at all: --cg-heading-* is 1.2 and --cg-body-* is
|
|
1256
|
+
1.46, and nothing on this face was at 1.
|
|
1257
|
+
|
|
1258
|
+
It is a role set rather than a bare size, on the --cg-eyebrow-* pattern, for the
|
|
1259
|
+
reason that block gives: the figure carries a leading and a weight as well as a
|
|
1260
|
+
size, and spelling two of the three inline is how a component drifts.
|
|
1261
|
+
|
|
1262
|
+
KNOWN DIVERGENCE, not fixed here: StatTile renders its value as
|
|
1263
|
+
`font-display text-display-sm` — Tanker 30 at --cg-display-line. The frames say
|
|
1264
|
+
Manrope Medium 32 at leading-none. Repointing it changes what every StatTile
|
|
1265
|
+
renders today, so it is a separate change with its own changeset.
|
|
1266
|
+
|
|
1267
|
+
The same five figures appear again at 18.149px inside scaled product mocks on
|
|
1268
|
+
home, thegrid and enterprise. That is the per-mock scale factor #248 documents,
|
|
1269
|
+
not a second rung. */
|
|
1270
|
+
--cg-stat-size: 32px;
|
|
1271
|
+
/*
|
|
1272
|
+
leading-none, from all ten nodes. Spelled as a number rather than aliased onto
|
|
1273
|
+
--cg-display-line-tight: that alias exists so a retune of the DISPLAY leading
|
|
1274
|
+
reaches the title ramp too, and the stat figure is on the UI face and must not
|
|
1275
|
+
move with it. */
|
|
1276
|
+
--cg-stat-line: 1;
|
|
1277
|
+
/*
|
|
1278
|
+
500, from all ten nodes. Manrope Medium, not the eyebrow's 600 and not
|
|
1279
|
+
--cg-label-weight's 500 by coincidence — aliased onto the weight rung so it
|
|
1280
|
+
tracks the brand's Medium. */
|
|
1281
|
+
--cg-stat-weight: var(--cg-weight-medium);
|
|
1072
1282
|
|
|
1073
1283
|
/* ---- spacing ----
|
|
1074
1284
|
spacing: 4px base */
|
package/dist/tokens.json
CHANGED
|
@@ -101,7 +101,9 @@
|
|
|
101
101
|
"--cg-weight-display": "700",
|
|
102
102
|
"--cg-display-3xl": "120px",
|
|
103
103
|
"--cg-display-2xl": "92px",
|
|
104
|
+
"--cg-display-64": "64px",
|
|
104
105
|
"--cg-display-xl": "56px",
|
|
106
|
+
"--cg-display-48": "48px",
|
|
105
107
|
"--cg-display-lg": "44px",
|
|
106
108
|
"--cg-display-md": "38px",
|
|
107
109
|
"--cg-display-sm": "30px",
|
|
@@ -109,7 +111,10 @@
|
|
|
109
111
|
"--cg-display-tracking": "0.012em",
|
|
110
112
|
"--cg-display-line-tight": "1",
|
|
111
113
|
"--cg-title-lg": "40px",
|
|
114
|
+
"--cg-title-36": "36px",
|
|
115
|
+
"--cg-title-32": "32px",
|
|
112
116
|
"--cg-title-md": "26px",
|
|
117
|
+
"--cg-title-24": "24px",
|
|
113
118
|
"--cg-title-sm": "20px",
|
|
114
119
|
"--cg-title-line": "var(--cg-display-line-tight)",
|
|
115
120
|
"--cg-title-tracking": "0em",
|
|
@@ -119,8 +124,10 @@
|
|
|
119
124
|
"--cg-heading-line": "1.2",
|
|
120
125
|
"--cg-body-xl": "20px",
|
|
121
126
|
"--cg-body-lg": "19px",
|
|
127
|
+
"--cg-body-18": "18px",
|
|
122
128
|
"--cg-body-md": "16px",
|
|
123
129
|
"--cg-body-sm": "15px",
|
|
130
|
+
"--cg-body-14": "14px",
|
|
124
131
|
"--cg-caption": "13px",
|
|
125
132
|
"--cg-body-line": "1.46",
|
|
126
133
|
"--cg-body-tracking": "0em",
|
|
@@ -132,6 +139,9 @@
|
|
|
132
139
|
"--cg-eyebrow-line": "1.2",
|
|
133
140
|
"--cg-eyebrow-tracking": "0.1em",
|
|
134
141
|
"--cg-eyebrow-weight": "var(--cg-weight-bold)",
|
|
142
|
+
"--cg-stat-size": "32px",
|
|
143
|
+
"--cg-stat-line": "1",
|
|
144
|
+
"--cg-stat-weight": "var(--cg-weight-medium)",
|
|
135
145
|
"--cg-space-1": "4px",
|
|
136
146
|
"--cg-space-2": "8px",
|
|
137
147
|
"--cg-space-3": "12px",
|
package/dist/ui/tabs.d.ts
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
2
3
|
import { type VariantProps } from "class-variance-authority";
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Three list treatments, and `pill` is the one with behaviour.
|
|
6
|
+
*
|
|
7
|
+
* `default` the segmented control: a tinted track with a raised active tab.
|
|
8
|
+
* `line` an underline under the active tab, no track at all.
|
|
9
|
+
* `pill` a row of rounded-full chips that SCROLLS rather than wraps, for
|
|
10
|
+
* choosing a sub-page on a narrow screen (ui#256). Read from the
|
|
11
|
+
* Console's mobile frames: Architecture, Versions, Scheduled jobs,
|
|
12
|
+
* Logs, Database, Configuration, Connections, seven chips on a
|
|
13
|
+
* 390px phone.
|
|
14
|
+
*
|
|
15
|
+
* Wrapping was the alternative and it is worse on the frame it is for: a
|
|
16
|
+
* second row of chips pushes the page's own heading below the fold, and the
|
|
17
|
+
* height of the strip then depends on how many sub-pages a reader's role can
|
|
18
|
+
* see. A scroller is one row whatever the count.
|
|
19
|
+
*
|
|
20
|
+
* The cost of a scroller is that the active chip can start off-screen, which is
|
|
21
|
+
* the one thing a reader needs to see. `scrollActiveTabIntoView` pays it on
|
|
22
|
+
* mount.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Bring the active chip into view inside its own scroll port, on mount.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT `element.scrollIntoView()`. That walks every ancestor scroll
|
|
28
|
+
* port, including the document, so on a phone it scrolls the whole page to
|
|
29
|
+
* wherever the chip strip happens to be — which is the page jumping under a
|
|
30
|
+
* reader who has not touched anything. Setting `scrollLeft` moves the one box
|
|
31
|
+
* that should move and nothing else.
|
|
32
|
+
*
|
|
33
|
+
* `offsetLeft` is read against the list, which is why the `pill` variant is
|
|
34
|
+
* `relative`: that makes the list its children's `offsetParent`, so the number
|
|
35
|
+
* is the chip's position inside the strip rather than its position inside
|
|
36
|
+
* whatever happened to be positioned further up the page.
|
|
37
|
+
*
|
|
38
|
+
* Exported because it is the only part of this that can be asserted without a
|
|
39
|
+
* browser. It reads four numbers and one selector, so the node suite calls it
|
|
40
|
+
* with a plain object carrying them; the real scroll is measured in
|
|
41
|
+
* `registry/e2e/tabs-pill.spec.ts`.
|
|
42
|
+
*/
|
|
43
|
+
declare function scrollActiveTabIntoView(list: HTMLElement | null | undefined): void;
|
|
44
|
+
declare function Tabs({ className, orientation, ...props }: TabsPrimitive.Root.Props): React.JSX.Element;
|
|
4
45
|
declare const tabsListVariants: (props?: ({
|
|
5
|
-
variant?: "line" | "default" | null | undefined;
|
|
46
|
+
variant?: "line" | "default" | "pill" | null | undefined;
|
|
6
47
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
|
-
declare function TabsList({ className, variant, ...props }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>):
|
|
8
|
-
declare function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props):
|
|
9
|
-
declare function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props):
|
|
10
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
48
|
+
declare function TabsList({ className, variant, ref, ...props }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>): React.JSX.Element;
|
|
49
|
+
declare function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props): React.JSX.Element;
|
|
50
|
+
declare function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props): React.JSX.Element;
|
|
51
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants, scrollActiveTabIntoView };
|
|
11
52
|
//# sourceMappingURL=tabs.d.ts.map
|
package/dist/ui/tabs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,WAA0B,EAC1B,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,iBAAS,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,QAapE;AAUD,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,WAA0B,EAC1B,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,qBAY1B;AAED,QAAA,MAAM,gBAAgB;;8EAkCrB,CAAA;AAED,iBAAS,QAAQ,CAAC,EAChB,SAAS,EACT,OAAmB,EACnB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,qBA+BlE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,KAAK,qBAqCpE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,qBAmBtE;AAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,CAAA"}
|
package/dist/ui/tabs.js
CHANGED
|
@@ -1,8 +1,70 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
5
|
import { cva } from "class-variance-authority";
|
|
5
6
|
import { cn } from "../lib/cloudgrid-cn.js";
|
|
7
|
+
/**
|
|
8
|
+
* Three list treatments, and `pill` is the one with behaviour.
|
|
9
|
+
*
|
|
10
|
+
* `default` the segmented control: a tinted track with a raised active tab.
|
|
11
|
+
* `line` an underline under the active tab, no track at all.
|
|
12
|
+
* `pill` a row of rounded-full chips that SCROLLS rather than wraps, for
|
|
13
|
+
* choosing a sub-page on a narrow screen (ui#256). Read from the
|
|
14
|
+
* Console's mobile frames: Architecture, Versions, Scheduled jobs,
|
|
15
|
+
* Logs, Database, Configuration, Connections, seven chips on a
|
|
16
|
+
* 390px phone.
|
|
17
|
+
*
|
|
18
|
+
* Wrapping was the alternative and it is worse on the frame it is for: a
|
|
19
|
+
* second row of chips pushes the page's own heading below the fold, and the
|
|
20
|
+
* height of the strip then depends on how many sub-pages a reader's role can
|
|
21
|
+
* see. A scroller is one row whatever the count.
|
|
22
|
+
*
|
|
23
|
+
* The cost of a scroller is that the active chip can start off-screen, which is
|
|
24
|
+
* the one thing a reader needs to see. `scrollActiveTabIntoView` pays it on
|
|
25
|
+
* mount.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Bring the active chip into view inside its own scroll port, on mount.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT `element.scrollIntoView()`. That walks every ancestor scroll
|
|
31
|
+
* port, including the document, so on a phone it scrolls the whole page to
|
|
32
|
+
* wherever the chip strip happens to be — which is the page jumping under a
|
|
33
|
+
* reader who has not touched anything. Setting `scrollLeft` moves the one box
|
|
34
|
+
* that should move and nothing else.
|
|
35
|
+
*
|
|
36
|
+
* `offsetLeft` is read against the list, which is why the `pill` variant is
|
|
37
|
+
* `relative`: that makes the list its children's `offsetParent`, so the number
|
|
38
|
+
* is the chip's position inside the strip rather than its position inside
|
|
39
|
+
* whatever happened to be positioned further up the page.
|
|
40
|
+
*
|
|
41
|
+
* Exported because it is the only part of this that can be asserted without a
|
|
42
|
+
* browser. It reads four numbers and one selector, so the node suite calls it
|
|
43
|
+
* with a plain object carrying them; the real scroll is measured in
|
|
44
|
+
* `registry/e2e/tabs-pill.spec.ts`.
|
|
45
|
+
*/
|
|
46
|
+
function scrollActiveTabIntoView(list) {
|
|
47
|
+
if (!list)
|
|
48
|
+
return;
|
|
49
|
+
const active = list.querySelector('[data-slot="tabs-trigger"][data-active]');
|
|
50
|
+
if (!active)
|
|
51
|
+
return;
|
|
52
|
+
const overflow = list.scrollWidth - list.clientWidth;
|
|
53
|
+
// Nothing to scroll. Without this the clamp below would still land on 0, but
|
|
54
|
+
// writing scrollLeft on a strip that does not overflow is a mutation with no
|
|
55
|
+
// meaning, and a test cannot tell it apart from the real thing.
|
|
56
|
+
if (overflow <= 0)
|
|
57
|
+
return;
|
|
58
|
+
const centred = active.offsetLeft - (list.clientWidth - active.offsetWidth) / 2;
|
|
59
|
+
list.scrollLeft = Math.max(0, Math.min(centred, overflow));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `useLayoutEffect` on the client so the strip is already scrolled on the first
|
|
63
|
+
* paint, `useEffect` on the server so React does not warn about a layout effect
|
|
64
|
+
* in a render that has no layout. Assigned once at module scope rather than
|
|
65
|
+
* chosen per render, so the hook order never changes.
|
|
66
|
+
*/
|
|
67
|
+
const useIsoLayoutEffect = typeof document === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
6
68
|
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
7
69
|
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className), ...props }));
|
|
8
70
|
}
|
|
@@ -11,17 +73,82 @@ const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center jus
|
|
|
11
73
|
variant: {
|
|
12
74
|
default: "bg-muted",
|
|
13
75
|
line: "gap-1 bg-transparent",
|
|
76
|
+
// No track, no wrap, one row. `relative` is load-bearing rather than
|
|
77
|
+
// cosmetic: it makes the list the offsetParent of its chips, which is
|
|
78
|
+
// what `scrollActiveTabIntoView` measures against.
|
|
79
|
+
//
|
|
80
|
+
// py-1 is the focus ring's room. `overflow-x: auto` computes overflow-y
|
|
81
|
+
// to auto as well, so a ring drawn outside the strip's box would either
|
|
82
|
+
// be clipped or produce a vertical scrollbar; 4px of padding is more
|
|
83
|
+
// than the 3px ring needs.
|
|
84
|
+
//
|
|
85
|
+
// `p-0` before it, and it is not redundant. tailwind-merge's padding
|
|
86
|
+
// conflicts are one-directional: a later `px-*` does NOT remove an
|
|
87
|
+
// earlier `p-*`, so the base's `p-[3px]` would have survived into the
|
|
88
|
+
// strip and left it with a 3px inline gutter that no class in this
|
|
89
|
+
// string mentions.
|
|
90
|
+
//
|
|
91
|
+
// The scrollbar is hidden in both dialects because a always-visible
|
|
92
|
+
// scrollbar under a 34px chip row is most of a second row's height.
|
|
93
|
+
// Hiding it is safe here and would not be on a vertical list: the
|
|
94
|
+
// active chip is scrolled in on mount, arrow keys move between chips,
|
|
95
|
+
// and a touch reader drags.
|
|
96
|
+
pill: "relative w-full max-w-full justify-start gap-2 overflow-x-auto rounded-none bg-transparent p-0 py-1 group-data-horizontal/tabs:h-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
14
97
|
},
|
|
15
98
|
},
|
|
16
99
|
defaultVariants: {
|
|
17
100
|
variant: "default",
|
|
18
101
|
},
|
|
19
102
|
});
|
|
20
|
-
function TabsList({ className, variant = "default", ...props }) {
|
|
21
|
-
|
|
103
|
+
function TabsList({ className, variant = "default", ref, ...props }) {
|
|
104
|
+
const list = React.useRef(null);
|
|
105
|
+
useIsoLayoutEffect(() => {
|
|
106
|
+
// Only the variant that scrolls. `default` and `line` are `w-fit` and have
|
|
107
|
+
// no scroll port, so this would be a measurement of nothing — and running
|
|
108
|
+
// it anyway is how a variant that was supposed to be untouched acquires
|
|
109
|
+
// behaviour.
|
|
110
|
+
if (variant !== "pill")
|
|
111
|
+
return;
|
|
112
|
+
scrollActiveTabIntoView(list.current);
|
|
113
|
+
}, [variant]);
|
|
114
|
+
return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant,
|
|
115
|
+
// Merged rather than replaced. Base UI forwards the ref to the real
|
|
116
|
+
// element, so taking it for the scroll would otherwise silently drop a
|
|
117
|
+
// consumer's own ref, and a dropped ref fails at THEIR call site.
|
|
118
|
+
ref: (node) => {
|
|
119
|
+
list.current = node;
|
|
120
|
+
if (typeof ref === "function") {
|
|
121
|
+
const cleanup = ref(node);
|
|
122
|
+
return typeof cleanup === "function" ? cleanup : undefined;
|
|
123
|
+
}
|
|
124
|
+
if (ref)
|
|
125
|
+
ref.current = node;
|
|
126
|
+
}, className: cn(tabsListVariants({ variant }), className), ...props }));
|
|
22
127
|
}
|
|
23
128
|
function TabsTrigger({ className, ...props }) {
|
|
24
|
-
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-body-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
129
|
+
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-body-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
130
|
+
// THE PILL CHIP (ui#256).
|
|
131
|
+
//
|
|
132
|
+
// Every rule is scoped to the list's variant rather than added to the
|
|
133
|
+
// base string, which is what keeps `default` and `line` byte-identical
|
|
134
|
+
// to what they rendered before. The prefixed selector also carries
|
|
135
|
+
// higher specificity than the unprefixed base rules it has to beat
|
|
136
|
+
// (`flex-1`, `rounded-md`, `text-foreground/60`, and the
|
|
137
|
+
// `data-active:bg-background` line above), which is the same mechanism
|
|
138
|
+
// `line` already uses. tailwind-merge cannot collapse the two, because a
|
|
139
|
+
// prefixed utility and a bare one are different groups to it.
|
|
140
|
+
//
|
|
141
|
+
// flex-none over the base's flex-1: a chip is as wide as its words. Four
|
|
142
|
+
// equal chips would compress "Scheduled jobs" and leave "Logs" swimming.
|
|
143
|
+
"group-data-[variant=pill]/tabs-list:h-auto group-data-[variant=pill]/tabs-list:flex-none group-data-[variant=pill]/tabs-list:shrink-0 group-data-[variant=pill]/tabs-list:rounded-pill group-data-[variant=pill]/tabs-list:border-hairline group-data-[variant=pill]/tabs-list:bg-card group-data-[variant=pill]/tabs-list:px-3 group-data-[variant=pill]/tabs-list:py-1.5 group-data-[variant=pill]/tabs-list:text-caption group-data-[variant=pill]/tabs-list:font-medium group-data-[variant=pill]/tabs-list:text-body",
|
|
144
|
+
// The active chip is self-contained: its own ground AND its own text,
|
|
145
|
+
// which is `SideNavItem`'s decision and its measured numbers. The tint
|
|
146
|
+
// under the brand purple is one pair of values on the page, on the panel
|
|
147
|
+
// and on ink, held at 5.02:1 by
|
|
148
|
+
// `packages/ui/test/contrast-guardrail.test.mjs`. `text-brand` alone
|
|
149
|
+
// would be purple on purple inside a panel; the tint alone would be
|
|
150
|
+
// inherited body text on lavender.
|
|
151
|
+
"group-data-[variant=pill]/tabs-list:data-active:border-brand-200 group-data-[variant=pill]/tabs-list:data-active:bg-brand-tint group-data-[variant=pill]/tabs-list:data-active:text-brand", "group-data-[variant=pill]/tabs-list:hover:text-ink group-data-[variant=pill]/tabs-list:data-active:hover:text-brand", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), ...props }));
|
|
25
152
|
}
|
|
26
153
|
function TabsContent({ className, ...props }) {
|
|
27
154
|
return (_jsx(TabsPrimitive.Panel, { "data-slot": "tabs-content", className: cn("flex-1 text-body-sm outline-none",
|
|
@@ -34,5 +161,5 @@ function TabsContent({ className, ...props }) {
|
|
|
34
161
|
// passes through.
|
|
35
162
|
"focus-visible:ring-3 focus-visible:ring-ring", className), ...props }));
|
|
36
163
|
}
|
|
37
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
164
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants, scrollActiveTabIntoView };
|
|
38
165
|
//# sourceMappingURL=tabs.js.map
|
package/dist/ui/tabs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAA;AAEjE,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC,SAAS,IAAI,CAAC,EACZ,SAAS,EACT,WAAW,GAAG,YAAY,EAC1B,GAAG,KAAK,EACiB;IACzB,OAAO,CACL,KAAC,aAAa,CAAC,IAAI,iBACP,MAAM,sBACE,WAAW,EAC7B,SAAS,EAAE,EAAE,CACX,gDAAgD,EAChD,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,MAAM,gBAAgB,GAAG,GAAG,CAC1B,yOAAyO,EACzO;IACE,QAAQ,EAAE;QACR,OAAO,EAAE;YACP,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,sBAAsB;
|
|
1
|
+
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "CloudGrid design system: React components, brand design tokens, Tailwind theme, self-hosted fonts.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"default": "./dist/index.js"
|
|
22
22
|
},
|
|
23
23
|
"./tokens.css": "./dist/tokens.css",
|
|
24
|
+
"./console.css": "./dist/console.css",
|
|
24
25
|
"./theme.css": "./dist/theme.css",
|
|
25
26
|
"./shadcn.css": "./dist/shadcn.css",
|
|
26
27
|
"./fonts.css": "./dist/fonts.css",
|