@gtivr4/a1-design-system-react 0.14.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +3 -2
  2. package/src/color-scheme.css +2 -0
  3. package/src/components/accordion/Accordion.d.ts +8 -0
  4. package/src/components/accordion/Accordion.jsx +9 -1
  5. package/src/components/accordion/accordion.css +46 -6
  6. package/src/components/autocomplete/Autocomplete.d.ts +53 -0
  7. package/src/components/autocomplete/Autocomplete.jsx +380 -0
  8. package/src/components/autocomplete/autocomplete.css +346 -0
  9. package/src/components/banner/Banner.d.ts +9 -2
  10. package/src/components/banner/Banner.jsx +32 -6
  11. package/src/components/banner/banner.css +81 -0
  12. package/src/components/bottom-sheet/BottomSheet.d.ts +22 -0
  13. package/src/components/bottom-sheet/BottomSheet.jsx +154 -0
  14. package/src/components/bottom-sheet/bottom-sheet.css +113 -0
  15. package/src/components/button/button.css +7 -3
  16. package/src/components/code/Code.jsx +6 -1
  17. package/src/components/data-table/DataTable.jsx +11 -1
  18. package/src/components/data-table/data-table.css +19 -0
  19. package/src/components/figure/Figure.d.ts +37 -4
  20. package/src/components/figure/Figure.jsx +78 -9
  21. package/src/components/figure/figure.css +105 -8
  22. package/src/components/grid/Grid.d.ts +1 -1
  23. package/src/components/grid/Grid.jsx +2 -0
  24. package/src/components/grid/grid.css +5 -0
  25. package/src/components/icon-button/IconButton.d.ts +2 -2
  26. package/src/components/icon-button/IconButton.jsx +3 -2
  27. package/src/components/icon-button/icon-button.css +11 -1
  28. package/src/components/menu/Menu.jsx +12 -0
  29. package/src/components/menu/menu.css +17 -6
  30. package/src/components/page-layout/page-layout.css +10 -4
  31. package/src/components/page-nav/PageNav.jsx +29 -8
  32. package/src/components/page-nav/page-nav.css +13 -0
  33. package/src/components/paragraph/Paragraph.d.ts +2 -0
  34. package/src/components/paragraph/Paragraph.jsx +4 -0
  35. package/src/components/paragraph/paragraph.css +6 -6
  36. package/src/components/section/Section.d.ts +6 -0
  37. package/src/components/section/Section.jsx +19 -0
  38. package/src/components/section/section.css +33 -10
  39. package/src/components/segmented-control/SegmentedControl.d.ts +8 -0
  40. package/src/components/segmented-control/SegmentedControl.jsx +16 -3
  41. package/src/components/segmented-control/segmented.css +31 -1
  42. package/src/components/slider/Slider.d.ts +71 -0
  43. package/src/components/slider/Slider.jsx +243 -0
  44. package/src/components/slider/slider.css +238 -0
  45. package/src/components/split-button/SplitButton.d.ts +39 -0
  46. package/src/components/split-button/SplitButton.jsx +94 -0
  47. package/src/components/split-button/split-button.css +40 -0
  48. package/src/components/tabs/tabs.css +3 -0
  49. package/src/components/toolbar/Toolbar.d.ts +131 -0
  50. package/src/components/toolbar/Toolbar.jsx +335 -0
  51. package/src/components/toolbar/toolbar.css +229 -0
  52. package/src/components/top-header/top-header.css +2 -0
  53. package/src/components/tree-menu/TreeMenu.jsx +11 -7
  54. package/src/index.d.ts +71 -0
  55. package/src/index.js +15 -1
  56. package/src/themes.css +293 -0
  57. package/src/tokens.css +26 -3
@@ -0,0 +1,131 @@
1
+ import * as React from "react";
2
+
3
+ /** Standard icon name for a "none" selection. */
4
+ export declare const TOOLBAR_NONE_ICON: string;
5
+
6
+ /**
7
+ * Whether a tool's label is shown. Pass a boolean, or a breakpoint object to
8
+ * vary it responsively (cascades xs → xl, e.g. `{ xs: false, lg: true }` shows
9
+ * labels only from the `lg` breakpoint up). When labels can be hidden at any
10
+ * breakpoint the tool keeps an `aria-label`, so the accessible name is stable.
11
+ */
12
+ export type ToolbarShowLabel =
13
+ | boolean
14
+ | { xs?: boolean; sm?: boolean; md?: boolean; lg?: boolean; xl?: boolean };
15
+
16
+ export interface ToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
17
+ /** Accessible name for the toolbar (ignored when `label` is set). */
18
+ "aria-label"?: string;
19
+ /**
20
+ * Optional visible caption rendered above the bar, styled to match the compact
21
+ * ChoiceGroup label. When set it also provides the toolbar's accessible name.
22
+ */
23
+ label?: React.ReactNode;
24
+ /**
25
+ * Lift the bar into a floating, elevated surface (shadow + border) for a
26
+ * toolbar that hovers over page content (e.g. a selection formatting bar).
27
+ * The consumer is responsible for positioning. Default: false
28
+ */
29
+ overlay?: boolean;
30
+ /**
31
+ * Stretch the bar to fill its container, with the tools growing to share the
32
+ * available width (dividers keep their natural size). When false the bar is
33
+ * `fit-content` wide. Default: false
34
+ */
35
+ fullWidth?: boolean;
36
+ children?: React.ReactNode;
37
+ }
38
+ /** A compact container grouping related editing controls on one subtle surface. */
39
+ export declare function Toolbar(props: ToolbarProps): React.ReactElement;
40
+
41
+ /** Visual separator between tools within a Toolbar. */
42
+ export declare function ToolbarDivider(): React.ReactElement;
43
+
44
+ export interface ToolbarToggleProps
45
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
46
+ /** Material Symbols icon name. */
47
+ icon?: string;
48
+ /** A colour swatch (any CSS color) shown inside the tool. */
49
+ swatch?: string;
50
+ /** Accessible name (and tooltip); shown as text when `showLabel`. */
51
+ label?: string;
52
+ /** Pressed (on) state. */
53
+ pressed?: boolean;
54
+ /** Called with the next pressed state when clicked. */
55
+ onChange?: (pressed: boolean) => void;
56
+ /** Show the label as visible text; boolean or a responsive breakpoint object. Default: false */
57
+ showLabel?: ToolbarShowLabel;
58
+ disabled?: boolean;
59
+ }
60
+ /** A two-state toggle button (e.g. bold on/off). */
61
+ export declare function ToolbarToggle(props: ToolbarToggleProps): React.ReactElement;
62
+
63
+ export interface ToolbarButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
64
+ icon?: string;
65
+ /** A colour swatch (any CSS color) shown inside the tool. */
66
+ swatch?: string;
67
+ /** Accessible name (and tooltip); shown as text when `showLabel`. */
68
+ label?: string;
69
+ /** Show the label as visible text; boolean or a responsive breakpoint object. Default: false */
70
+ showLabel?: ToolbarShowLabel;
71
+ }
72
+ /** A plain action button inside a Toolbar (no pressed state). */
73
+ export declare function ToolbarButton(props: ToolbarButtonProps): React.ReactElement;
74
+
75
+ export interface ToolbarMenuItem {
76
+ value: string | number;
77
+ label?: React.ReactNode;
78
+ /** Material Symbols icon name shown beside the menu item. */
79
+ icon?: string;
80
+ disabled?: boolean;
81
+ }
82
+ export interface ToolbarMenuProps {
83
+ /** Button icon. Defaults to the active item's icon when `value` matches one. */
84
+ icon?: string;
85
+ /** Accessible name (and tooltip); shown as text when `showLabel`. */
86
+ label?: string;
87
+ /** Currently selected value — marks the matching item active. */
88
+ value?: string | number;
89
+ /** Called with the chosen item value. */
90
+ onChange?: (value: string | number) => void;
91
+ items?: ToolbarMenuItem[];
92
+ /** Show the label as visible text; boolean or a responsive breakpoint object. Default: false */
93
+ showLabel?: ToolbarShowLabel;
94
+ disabled?: boolean;
95
+ "aria-label"?: string;
96
+ className?: string;
97
+ }
98
+ /** A toolbar button that opens a dropdown Menu of choices (e.g. list types). */
99
+ export declare function ToolbarMenu(props: ToolbarMenuProps): React.ReactElement;
100
+
101
+ export interface ToolbarGroupOption {
102
+ value: string | number;
103
+ label?: React.ReactNode;
104
+ /** Material Symbols icon name (rendered instead of the label in icon-only mode). */
105
+ icon?: string;
106
+ /** A colour swatch (any CSS color) shown inside the option. */
107
+ swatch?: string;
108
+ disabled?: boolean;
109
+ }
110
+ export interface ToolbarGroupProps {
111
+ value?: string | number;
112
+ /** Called with the newly selected option value. */
113
+ onChange?: (value: string | number) => void;
114
+ options?: ToolbarGroupOption[];
115
+ /** Lay the options out as a `columns`-wide grid (e.g. 3 for a 3×3 picker). */
116
+ columns?: number;
117
+ /** Show option labels as text; boolean or a responsive breakpoint object. Default: false (icon-only) */
118
+ showLabels?: ToolbarShowLabel;
119
+ /**
120
+ * `"all"` (default) honours `showLabels` for every option. `"selected"` shows
121
+ * the label only on the currently selected option; the rest render icon/swatch-only
122
+ * and a `"none"`/empty value falls back to the standard none icon. Use it for a
123
+ * swatch picker where only the chosen swatch is named (e.g. Section surface/gradient).
124
+ */
125
+ labelMode?: "all" | "selected";
126
+ "aria-label"?: string;
127
+ disabled?: boolean;
128
+ className?: string;
129
+ }
130
+ /** A single-select button group (radio semantics); set `columns` for a grid. */
131
+ export declare function ToolbarGroup(props: ToolbarGroupProps): React.ReactElement;
@@ -0,0 +1,335 @@
1
+ import "./toolbar.css";
2
+ import { useId, useRef, useState } from "react";
3
+ import { Icon } from "../icon/Icon.jsx";
4
+ import { Menu, MenuItem } from "../menu/Menu.jsx";
5
+
6
+ /** Standard icon for a "none" selection (no alignment, no size, …). */
7
+ export const TOOLBAR_NONE_ICON = "block";
8
+
9
+ const BREAKPOINTS = ["xs", "sm", "md", "lg", "xl"];
10
+
11
+ function cx(...parts) {
12
+ return parts.filter(Boolean).join(" ");
13
+ }
14
+
15
+ function isNoneValue(v) {
16
+ return v === "none" || v === null || v === "";
17
+ }
18
+
19
+ /** A responsive `showLabel`/`showLabels` value: `{ xs?, sm?, … }` of booleans. */
20
+ function isResponsive(v) {
21
+ return v !== null && typeof v === "object" && !Array.isArray(v);
22
+ }
23
+
24
+ /** Is the label shown for any breakpoint? (decides whether the span renders). */
25
+ function labelEverShown(showLabel) {
26
+ if (isResponsive(showLabel)) return BREAKPOINTS.some((bp) => showLabel[bp]);
27
+ return !!showLabel;
28
+ }
29
+
30
+ /** Are labels shown at every breakpoint? (decides icon-only fallbacks/aria). */
31
+ function labelAlwaysShown(showLabel) {
32
+ return showLabel === true;
33
+ }
34
+
35
+ /** Cascade per-breakpoint visibility classes for the label span (xs → xl). */
36
+ function labelVisibilityClasses(showLabel) {
37
+ if (!isResponsive(showLabel)) return [];
38
+ let last = false;
39
+ return BREAKPOINTS.map((bp) => {
40
+ if (typeof showLabel[bp] === "boolean") last = showLabel[bp];
41
+ return `a1-toolbar__label--${last ? "show" : "hide"}-${bp}`;
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Accessible name for a tool button. When labels are always visible the visible
47
+ * text is the name (no aria-label). When they're hidden — or responsively
48
+ * toggled — fall back to an explicit `aria-label` so the name is stable.
49
+ */
50
+ function toolAriaLabel(showLabel, label) {
51
+ return labelAlwaysShown(showLabel) ? undefined : label;
52
+ }
53
+
54
+ /**
55
+ * Toolbar — a compact container that groups related editing controls (toggles,
56
+ * single-select button groups, action buttons, selects, menus) on one subtle
57
+ * surface. Think a text-editor toolbar, or the controls in the component
58
+ * configurator. Compose it from the `Toolbar*` sub-components, separating tools
59
+ * with `<ToolbarDivider />`.
60
+ *
61
+ * Pass `label` to render an optional caption above the bar — styled to match the
62
+ * compact ChoiceGroup label so toolbars sit naturally alongside form controls.
63
+ * Pass `overlay` to lift the bar into a floating, elevated surface (shadow +
64
+ * border) for a toolbar that hovers over page content — e.g. a selection
65
+ * formatting bar. The consumer positions the overlay (the bar itself does not
66
+ * choose a location).
67
+ *
68
+ * By default the bar is `fit-content` wide. Pass `fullWidth` to stretch it to
69
+ * fill its container, with the tools growing to share the available space
70
+ * (dividers keep their natural size).
71
+ */
72
+ export function Toolbar({
73
+ label,
74
+ overlay = false,
75
+ fullWidth = false,
76
+ "aria-label": ariaLabel,
77
+ className = "",
78
+ children,
79
+ ...rest
80
+ }) {
81
+ const labelId = useId();
82
+ const bar = (
83
+ <div
84
+ role="toolbar"
85
+ aria-label={label ? undefined : ariaLabel}
86
+ aria-labelledby={label ? labelId : undefined}
87
+ aria-orientation="horizontal"
88
+ className={cx(
89
+ "a1-toolbar",
90
+ overlay && "a1-toolbar--overlay",
91
+ fullWidth && "a1-toolbar--full-width",
92
+ className,
93
+ )}
94
+ {...rest}
95
+ >
96
+ {children}
97
+ </div>
98
+ );
99
+
100
+ if (!label) return bar;
101
+
102
+ return (
103
+ <div className="a1-toolbar-field">
104
+ <span className="a1-toolbar-field__label" id={labelId}>{label}</span>
105
+ {bar}
106
+ </div>
107
+ );
108
+ }
109
+
110
+ /** Visual separator between tools within a Toolbar. */
111
+ export function ToolbarDivider({ className = "", ...rest }) {
112
+ return <span className={cx("a1-toolbar__divider", className)} role="separator" aria-orientation="vertical" {...rest} />;
113
+ }
114
+
115
+ function ToolButtonContent({ icon, label, showLabel, swatch }) {
116
+ const renderLabel = label != null && labelEverShown(showLabel);
117
+ const labelClass = cx("a1-toolbar__label", ...labelVisibilityClasses(showLabel));
118
+ return (
119
+ <>
120
+ {swatch ? <span className="a1-toolbar__swatch" style={{ background: swatch }} aria-hidden="true" /> : null}
121
+ {icon ? <Icon name={icon} size="sm" /> : null}
122
+ {renderLabel ? <span className={labelClass}>{label}</span> : null}
123
+ </>
124
+ );
125
+ }
126
+
127
+ /** A two-state toggle button (e.g. bold on/off). */
128
+ export function ToolbarToggle({
129
+ icon,
130
+ label,
131
+ swatch,
132
+ pressed = false,
133
+ onChange,
134
+ showLabel = false,
135
+ disabled = false,
136
+ className = "",
137
+ ...rest
138
+ }) {
139
+ return (
140
+ <button
141
+ type="button"
142
+ className={cx("a1-toolbar__button", className)}
143
+ aria-pressed={pressed}
144
+ aria-label={toolAriaLabel(showLabel, label)}
145
+ title={label}
146
+ disabled={disabled}
147
+ onClick={() => onChange?.(!pressed)}
148
+ {...rest}
149
+ >
150
+ <ToolButtonContent icon={icon} label={label} showLabel={showLabel} swatch={swatch} />
151
+ </button>
152
+ );
153
+ }
154
+
155
+ /** A plain action button inside a Toolbar (no pressed state). */
156
+ export function ToolbarButton({
157
+ icon,
158
+ label,
159
+ swatch,
160
+ onClick,
161
+ showLabel = false,
162
+ disabled = false,
163
+ className = "",
164
+ ...rest
165
+ }) {
166
+ return (
167
+ <button
168
+ type="button"
169
+ className={cx("a1-toolbar__button", className)}
170
+ aria-label={toolAriaLabel(showLabel, label)}
171
+ title={label}
172
+ disabled={disabled}
173
+ onClick={onClick}
174
+ {...rest}
175
+ >
176
+ <ToolButtonContent icon={icon} label={label} showLabel={showLabel} swatch={swatch} />
177
+ </button>
178
+ );
179
+ }
180
+
181
+ /**
182
+ * A toolbar button that opens a dropdown `Menu` of choices (e.g. a list-type
183
+ * picker: none / bulleted / numbered / checklist, or a text-size picker). The
184
+ * button carries a small
185
+ * caret to signal that it opens a menu. When `value` is set, the matching item
186
+ * is marked active and — unless an explicit `icon` is given — the button shows
187
+ * that item's icon so the toolbar reflects the current selection.
188
+ */
189
+ export function ToolbarMenu({
190
+ icon,
191
+ label,
192
+ value,
193
+ onChange,
194
+ items = [],
195
+ showLabel = false,
196
+ disabled = false,
197
+ "aria-label": ariaLabel,
198
+ className = "",
199
+ ...rest
200
+ }) {
201
+ const [open, setOpen] = useState(false);
202
+ const btnRef = useRef(null);
203
+ const active = items.find((it) => it.value === value);
204
+ const buttonIcon = icon ?? active?.icon;
205
+ const name = ariaLabel ?? label;
206
+
207
+ return (
208
+ <>
209
+ <button
210
+ ref={btnRef}
211
+ type="button"
212
+ className={cx("a1-toolbar__button", "a1-toolbar__menu-button", className)}
213
+ aria-haspopup="menu"
214
+ aria-expanded={open}
215
+ aria-label={ariaLabel ?? toolAriaLabel(showLabel, label)}
216
+ title={label}
217
+ disabled={disabled}
218
+ onClick={() => setOpen((o) => !o)}
219
+ {...rest}
220
+ >
221
+ <ToolButtonContent icon={buttonIcon} label={label} showLabel={showLabel} />
222
+ <Icon name="arrow_drop_down" size="sm" className="a1-toolbar__caret" />
223
+ </button>
224
+ <Menu open={open} onClose={() => setOpen(false)} anchorRef={btnRef} aria-label={name}>
225
+ {items.map((it) => (
226
+ <MenuItem
227
+ key={String(it.value)}
228
+ icon={it.icon}
229
+ active={it.value === value}
230
+ disabled={it.disabled}
231
+ onClick={() => { onChange?.(it.value); setOpen(false); }}
232
+ >
233
+ {it.label ?? String(it.value)}
234
+ </MenuItem>
235
+ ))}
236
+ </Menu>
237
+ </>
238
+ );
239
+ }
240
+
241
+ /**
242
+ * A single-select button group (radio semantics) — e.g. text alignment, or a
243
+ * compact `columns`-wide grid like a 3×3 crop-direction picker. Mutually
244
+ * exclusive options with clearly highlighted selection, in far less space than a
245
+ * ChoiceGroup.
246
+ */
247
+ export function ToolbarGroup({
248
+ value,
249
+ onChange,
250
+ options = [],
251
+ columns,
252
+ showLabels = false,
253
+ labelMode = "all",
254
+ "aria-label": ariaLabel,
255
+ disabled = false,
256
+ className = "",
257
+ ...rest
258
+ }) {
259
+ const btnRefs = useRef([]);
260
+ const grid = typeof columns === "number" && columns > 0;
261
+ const selectedIndex = options.findIndex((o) => o.value === value);
262
+
263
+ function move(index, select = true) {
264
+ const n = options.length;
265
+ const idx = ((index % n) + n) % n;
266
+ btnRefs.current[idx]?.focus();
267
+ if (select) onChange?.(options[idx].value);
268
+ }
269
+
270
+ function handleKeyDown(e, i) {
271
+ const n = options.length;
272
+ let next = null;
273
+ let clampVertical = false;
274
+ switch (e.key) {
275
+ case "ArrowRight": next = i + 1; break;
276
+ case "ArrowLeft": next = i - 1; break;
277
+ case "ArrowDown": next = grid ? i + columns : i + 1; clampVertical = grid; break;
278
+ case "ArrowUp": next = grid ? i - columns : i - 1; clampVertical = grid; break;
279
+ case "Home": next = 0; break;
280
+ case "End": next = n - 1; break;
281
+ default: return;
282
+ }
283
+ e.preventDefault();
284
+ // Vertical moves in a grid clamp (don't wrap to a wrong column); everything
285
+ // else wraps around the linear order.
286
+ if (clampVertical) {
287
+ if (next < 0 || next >= n) return;
288
+ move(next);
289
+ } else {
290
+ move(next);
291
+ }
292
+ }
293
+
294
+ const style = grid ? { "--a1-toolbar-grid-columns": columns } : undefined;
295
+
296
+ return (
297
+ <div
298
+ role="radiogroup"
299
+ aria-label={ariaLabel}
300
+ className={cx("a1-toolbar__group", grid && "a1-toolbar__group--grid", className)}
301
+ style={style}
302
+ {...rest}
303
+ >
304
+ {options.map((opt, i) => {
305
+ const selected = i === selectedIndex;
306
+ // Roving tabindex: the selected option (or the first, when none is
307
+ // selected) is the single tab stop for the group.
308
+ const tabIndex = selected || (selectedIndex === -1 && i === 0) ? 0 : -1;
309
+ const optLabel = opt.label ?? String(opt.value);
310
+ // `labelMode="selected"` shows the label only on the selected option;
311
+ // the rest fall back to icon/swatch-only (and "none" to its icon).
312
+ const optShowLabel = labelMode === "selected" ? selected : showLabels;
313
+ const icon = opt.icon ?? (!labelAlwaysShown(optShowLabel) && isNoneValue(opt.value) ? TOOLBAR_NONE_ICON : undefined);
314
+ return (
315
+ <button
316
+ key={String(opt.value)}
317
+ ref={(el) => { btnRefs.current[i] = el; }}
318
+ type="button"
319
+ role="radio"
320
+ aria-checked={selected}
321
+ aria-label={toolAriaLabel(optShowLabel, optLabel)}
322
+ title={optLabel}
323
+ tabIndex={tabIndex}
324
+ disabled={disabled || opt.disabled}
325
+ className="a1-toolbar__button"
326
+ onClick={() => onChange?.(opt.value)}
327
+ onKeyDown={(e) => handleKeyDown(e, i)}
328
+ >
329
+ <ToolButtonContent icon={icon} label={opt.label} showLabel={optShowLabel} swatch={opt.swatch} />
330
+ </button>
331
+ );
332
+ })}
333
+ </div>
334
+ );
335
+ }
@@ -0,0 +1,229 @@
1
+ /* ══════════════════════════════════════════════════════════════════════════
2
+ Toolbar
3
+ ══════════════════════════════════════════════════════════════════════════ */
4
+
5
+ /* ── Labelled field wrapper ────────────────────────────────────────────────── */
6
+ /* Optional caption above the bar — matches the compact ChoiceGroup label so a
7
+ Toolbar sits naturally next to form controls. */
8
+ .a1-toolbar-field {
9
+ display: flex;
10
+ flex-direction: column;
11
+ /* Keep the label and bar at their natural width so a stretching flex parent
12
+ (e.g. a column Stack with the default `align-items: stretch`) doesn't force
13
+ the bar to full width. A `fullWidth` bar still fills (it sets its own
14
+ `inline-size: 100%`). */
15
+ align-items: flex-start;
16
+ gap: var(--component-choice-group-compact-group-gap);
17
+ }
18
+
19
+ .a1-toolbar-field__label {
20
+ font-family: var(--component-paragraph-font-family);
21
+ /* The smallest body step. */
22
+ font-size: var(--semantic-font-size-body-xs);
23
+ font-weight: var(--component-field-label-font-weight);
24
+ color: var(--semantic-color-text-default);
25
+ line-height: var(--semantic-font-line-height-body);
26
+ }
27
+
28
+ .a1-toolbar {
29
+ /* Variant layer — every value resolves to a Style Dictionary token. */
30
+ --a1-toolbar-item-size: var(--base-spacing-32);
31
+ --a1-toolbar-bg: var(--semantic-color-surface-raised);
32
+ /* Unselected tools sit back in muted text; the selected tool steps forward
33
+ with default text on a darker neutral background — low-contrast, neutral. */
34
+ --a1-toolbar-fg: var(--semantic-color-text-muted);
35
+ --a1-toolbar-hover-bg: color-mix(in srgb, var(--semantic-color-text-default) 8%, transparent);
36
+ --a1-toolbar-selected-bg: color-mix(in srgb, var(--semantic-color-text-default) 16%, transparent);
37
+ --a1-toolbar-selected-fg: var(--semantic-color-text-default);
38
+ /* No boundary by default; the accessible theme and high-contrast modes raise
39
+ it so the bar is clearly delimited against the page surface. */
40
+ --a1-toolbar-border-width: 0;
41
+ --a1-toolbar-border-color: var(--semantic-color-border-default);
42
+
43
+ display: inline-flex;
44
+ align-items: center;
45
+ /* Tools wrap onto new rows when they run out of width — never overflow the
46
+ container and never scroll horizontally. */
47
+ flex-wrap: wrap;
48
+ max-inline-size: 100%;
49
+ gap: var(--base-spacing-2);
50
+ padding: var(--base-spacing-2);
51
+ background: var(--a1-toolbar-bg);
52
+ border: var(--a1-toolbar-border-width) solid var(--a1-toolbar-border-color);
53
+ border-radius: var(--base-radius-md);
54
+ }
55
+
56
+ /* Accessible theme: add a clear boundary at high contrast. */
57
+ [data-theme='accessible'] .a1-toolbar {
58
+ --a1-toolbar-border-width: var(--component-divider-size-sm);
59
+ }
60
+
61
+ /* OS high-contrast / forced-colors: add the same boundary. */
62
+ @media (prefers-contrast: more) {
63
+ .a1-toolbar {
64
+ --a1-toolbar-border-width: var(--component-divider-size-sm);
65
+ }
66
+ }
67
+
68
+ @media (forced-colors: active) {
69
+ .a1-toolbar {
70
+ --a1-toolbar-border-width: var(--component-divider-size-sm);
71
+ }
72
+ }
73
+
74
+ /* ── Full width (fill container, tools share the space) ────────────────────── */
75
+ .a1-toolbar--full-width {
76
+ display: flex;
77
+ inline-size: 100%;
78
+ }
79
+
80
+ /* Tools grow to share the available space; dividers keep their natural size. */
81
+ .a1-toolbar--full-width > :not(.a1-toolbar__divider) {
82
+ flex: 1 1 0;
83
+ }
84
+
85
+ /* A stretched (non-grid) group distributes its own buttons across its share. */
86
+ .a1-toolbar--full-width .a1-toolbar__group:not(.a1-toolbar__group--grid) > .a1-toolbar__button {
87
+ flex: 1 1 0;
88
+ }
89
+
90
+ /* ── Overlay (floating, elevated bar over page content) ────────────────────── */
91
+ .a1-toolbar--overlay {
92
+ background: var(--semantic-color-surface-card);
93
+ border: var(--component-divider-size-xs) solid var(--semantic-color-border-default);
94
+ box-shadow: var(--semantic-shadow-lg);
95
+ }
96
+
97
+ /* ── Buttons (toggle / action / group items) ───────────────────────────────── */
98
+
99
+ .a1-toolbar__button {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ gap: var(--base-spacing-4);
104
+ min-inline-size: var(--a1-toolbar-item-size);
105
+ block-size: var(--a1-toolbar-item-size);
106
+ padding-inline: var(--base-spacing-6);
107
+ margin: 0;
108
+ border: none;
109
+ border-radius: var(--base-radius-sm);
110
+ background: transparent;
111
+ color: var(--a1-toolbar-fg);
112
+ font-family: var(--component-paragraph-font-family);
113
+ font-size: var(--semantic-font-size-body-xs);
114
+ line-height: 1;
115
+ cursor: pointer;
116
+ }
117
+
118
+ .a1-toolbar__button:hover {
119
+ background: var(--a1-toolbar-hover-bg);
120
+ }
121
+
122
+ .a1-toolbar__button:focus-visible {
123
+ outline: none;
124
+ box-shadow: 0 0 0 var(--component-button-focus-ring-width) var(--component-field-focus-ring-color);
125
+ }
126
+
127
+ /* Selected state for toggles (aria-pressed) and group items (aria-checked) —
128
+ default text on a darker neutral fill. */
129
+ .a1-toolbar__button[aria-pressed="true"],
130
+ .a1-toolbar__button[aria-checked="true"] {
131
+ background: var(--a1-toolbar-selected-bg);
132
+ color: var(--a1-toolbar-selected-fg);
133
+ }
134
+
135
+ /* A toggle (aria-pressed) can be turned off, so it keeps hover feedback when on.
136
+ A radio group item (aria-checked) can't be deselected, so it shows no hover or
137
+ active change — clicking it does nothing. */
138
+ .a1-toolbar__button[aria-pressed="true"]:hover {
139
+ background: color-mix(in srgb, var(--semantic-color-text-default) 22%, transparent);
140
+ }
141
+
142
+ .a1-toolbar__button[aria-checked="true"]:hover,
143
+ .a1-toolbar__button[aria-checked="true"]:active {
144
+ background: var(--a1-toolbar-selected-bg);
145
+ }
146
+
147
+ .a1-toolbar__button:disabled {
148
+ opacity: 0.5;
149
+ cursor: not-allowed;
150
+ }
151
+
152
+ .a1-toolbar__label {
153
+ white-space: nowrap;
154
+ }
155
+
156
+ /* Colour swatch shown inside a tool to preview a specific colour. The border
157
+ keeps a light swatch from bleeding into the toolbar surface. */
158
+ .a1-toolbar__swatch {
159
+ flex: none;
160
+ inline-size: var(--base-spacing-16);
161
+ block-size: var(--base-spacing-16);
162
+ border-radius: var(--base-radius-sm);
163
+ border: var(--component-divider-size-xs) solid var(--semantic-color-border-default);
164
+ }
165
+
166
+ /* Responsive label visibility (showLabel/showLabels as a breakpoint object).
167
+ The component emits one cascading class per breakpoint (xs → xl); the last
168
+ matching media query wins, mirroring Divider's responsive pattern. */
169
+ .a1-toolbar__label--hide-xs { display: none; }
170
+ .a1-toolbar__label--show-xs { display: inline; }
171
+
172
+ @media (--bp-sm-up) {
173
+ .a1-toolbar__label--hide-sm { display: none; }
174
+ .a1-toolbar__label--show-sm { display: inline; }
175
+ }
176
+
177
+ @media (--bp-md-up) {
178
+ .a1-toolbar__label--hide-md { display: none; }
179
+ .a1-toolbar__label--show-md { display: inline; }
180
+ }
181
+
182
+ @media (--bp-lg-up) {
183
+ .a1-toolbar__label--hide-lg { display: none; }
184
+ .a1-toolbar__label--show-lg { display: inline; }
185
+ }
186
+
187
+ @media (--bp-xl) {
188
+ .a1-toolbar__label--hide-xl { display: none; }
189
+ .a1-toolbar__label--show-xl { display: inline; }
190
+ }
191
+
192
+ /* ── Menu button (opens a dropdown Menu) ───────────────────────────────────── */
193
+
194
+ .a1-toolbar__menu-button {
195
+ inline-size: auto;
196
+ }
197
+
198
+ .a1-toolbar__caret {
199
+ /* Pull the caret tight to the icon/label so the menu button stays compact. */
200
+ margin-inline-start: calc(-1 * var(--base-spacing-2));
201
+ }
202
+
203
+ /* ── Group (segmented) + grid ──────────────────────────────────────────────── */
204
+
205
+ .a1-toolbar__group {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ /* A wide single group wraps its own buttons rather than overflowing the bar. */
209
+ flex-wrap: wrap;
210
+ gap: var(--base-spacing-2);
211
+ }
212
+
213
+ .a1-toolbar__group--grid {
214
+ display: grid;
215
+ grid-template-columns: repeat(var(--a1-toolbar-grid-columns, 3), var(--a1-toolbar-item-size));
216
+ gap: var(--base-spacing-2);
217
+ }
218
+
219
+ /* ── Divider ───────────────────────────────────────────────────────────────── */
220
+
221
+ .a1-toolbar__divider {
222
+ align-self: stretch;
223
+ inline-size: var(--component-divider-size-xs);
224
+ min-block-size: var(--a1-toolbar-item-size);
225
+ margin-inline: var(--base-spacing-2);
226
+ background: var(--semantic-color-border-default);
227
+ border-radius: var(--base-radius-pill);
228
+ }
229
+
@@ -144,6 +144,8 @@
144
144
  }
145
145
 
146
146
  .a1-top-header__nav-submenu-trigger {
147
+ min-inline-size: 24px;
148
+ min-block-size: 24px;
147
149
  padding-inline: var(--base-spacing-8);
148
150
  }
149
151