@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 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":"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"}
@@ -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";
@@ -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,kQAqBnB,CAAA;AAWV,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
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"}
@@ -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;CACD,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"}
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
@@ -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
- declare function Tabs({ className, orientation, ...props }: TabsPrimitive.Root.Props): import("react").JSX.Element;
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>): import("react").JSX.Element;
8
- declare function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props): import("react").JSX.Element;
9
- declare function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props): import("react").JSX.Element;
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
@@ -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,+BAY1B;AAED,QAAA,MAAM,gBAAgB;;8EAarB,CAAA;AAED,iBAAS,QAAQ,CAAC,EAChB,SAAS,EACT,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,+BASlE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,KAAK,+BAcpE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,+BAmBtE;AAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAA"}
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
- return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), ...props }));
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", "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 }));
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
@@ -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;SAC7B;KACF;IACD,eAAe,EAAE;QACf,OAAO,EAAE,SAAS;KACnB;CACF,CACF,CAAA;AAED,SAAS,QAAQ,CAAC,EAChB,SAAS,EACT,OAAO,GAAG,SAAS,EACnB,GAAG,KAAK,EACyD;IACjE,OAAO,CACL,KAAC,aAAa,CAAC,IAAI,iBACP,WAAW,kBACP,OAAO,EACrB,SAAS,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,CAAC,KACnD,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA2B;IACnE,OAAO,CACL,KAAC,aAAa,CAAC,GAAG,iBACN,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,81BAA81B,EAC91B,+PAA+P,EAC/P,mJAAmJ,EACnJ,sYAAsY,EACtY,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA6B;IACrE,OAAO,CACL,KAAC,aAAa,CAAC,KAAK,iBACR,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,kCAAkC;QAClC,wEAAwE;QACxE,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,oEAAoE;QACpE,qEAAqE;QACrE,kBAAkB;QAClB,8CAA8C,EAC9C,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAA"}
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.28.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",