@cloudgrid-io/ui 0.29.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.
@@ -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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"}
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"}
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.29.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/",