@gtivr4/a1-design-system-react 0.1.0 → 0.2.3

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 (108) hide show
  1. package/package.json +1 -1
  2. package/src/color-scheme.css +586 -24
  3. package/src/components/accordion/Accordion.jsx +80 -0
  4. package/src/components/accordion/accordion.css +118 -0
  5. package/src/components/banner/Banner.jsx +66 -0
  6. package/src/components/banner/banner.css +205 -0
  7. package/src/components/bleed/Bleed.jsx +27 -0
  8. package/src/components/bleed/bleed.css +5 -0
  9. package/src/components/blockquote/Blockquote.jsx +40 -0
  10. package/src/components/blockquote/blockquote.css +166 -0
  11. package/src/components/breadcrumb/Breadcrumb.jsx +82 -0
  12. package/src/components/breadcrumb/breadcrumb.css +133 -0
  13. package/src/components/button/button.css +42 -12
  14. package/src/components/button-container/ButtonContainer.jsx +20 -1
  15. package/src/components/button-container/button-container.css +19 -1
  16. package/src/components/calendar/Calendar.jsx +383 -0
  17. package/src/components/calendar/calendar.css +225 -0
  18. package/src/components/card/Card.jsx +50 -12
  19. package/src/components/card/card.css +178 -14
  20. package/src/components/checkbox-group/CheckboxGroup.jsx +120 -0
  21. package/src/components/checkbox-group/checkbox-group.css +304 -0
  22. package/src/components/cluster/Cluster.jsx +52 -0
  23. package/src/components/cluster/cluster.css +9 -0
  24. package/src/components/code/Code.jsx +135 -0
  25. package/src/components/code/code.css +60 -0
  26. package/src/components/data-table/DataTable.jsx +721 -0
  27. package/src/components/data-table/DataTableFilters.jsx +339 -0
  28. package/src/components/data-table/data-table-filters.css +259 -0
  29. package/src/components/data-table/data-table.css +425 -0
  30. package/src/components/dialog/Dialog.jsx +45 -2
  31. package/src/components/dialog/dialog.css +13 -4
  32. package/src/components/divider/Divider.jsx +64 -0
  33. package/src/components/divider/divider.css +170 -0
  34. package/src/components/field/CreditCardField.jsx +131 -0
  35. package/src/components/field/DateField.jsx +11 -0
  36. package/src/components/field/NumberField.jsx +11 -0
  37. package/src/components/field/PhoneField.jsx +107 -0
  38. package/src/components/field/SelectField.jsx +86 -0
  39. package/src/components/field/TextField.jsx +83 -0
  40. package/src/components/field/TextareaField.jsx +147 -0
  41. package/src/components/field/TimeField.jsx +11 -0
  42. package/src/components/field/ZipField.jsx +114 -0
  43. package/src/components/field/credit-card.css +30 -0
  44. package/src/components/field/field.css +380 -0
  45. package/src/components/field/textarea-field.css +185 -0
  46. package/src/components/field-row/FieldRow.jsx +23 -0
  47. package/src/components/field-row/field-row.css +51 -0
  48. package/src/components/fieldset/Fieldset.jsx +49 -0
  49. package/src/components/fieldset/fieldset.css +75 -0
  50. package/src/components/figure/Figure.jsx +63 -0
  51. package/src/components/figure/figure.css +97 -0
  52. package/src/components/grid/Grid.jsx +36 -2
  53. package/src/components/grid/grid.css +129 -4
  54. package/src/components/heading/Heading.jsx +41 -1
  55. package/src/components/heading/heading.css +65 -4
  56. package/src/components/icon/icon.css +1 -0
  57. package/src/components/icon-button/icon-button.css +1 -0
  58. package/src/components/inline/inline.css +51 -0
  59. package/src/components/inline-editable/InlineEditable.jsx +77 -0
  60. package/src/components/inline-editable/inline-editable.css +47 -0
  61. package/src/components/inset/Inset.jsx +27 -0
  62. package/src/components/inset/inset.css +6 -0
  63. package/src/components/labels/Labels.jsx +5 -5
  64. package/src/components/link/Link.jsx +2 -3
  65. package/src/components/link/link.css +30 -1
  66. package/src/components/list/List.jsx +92 -0
  67. package/src/components/list/list.css +178 -0
  68. package/src/components/menu/Menu.jsx +243 -10
  69. package/src/components/menu/menu.css +157 -17
  70. package/src/components/message/Message.jsx +25 -50
  71. package/src/components/message/message.css +50 -33
  72. package/src/components/notification/Notification.jsx +1 -1
  73. package/src/components/page-layout/PageLayout.jsx +16 -1
  74. package/src/components/page-layout/page-layout.css +97 -4
  75. package/src/components/page-nav/PageNav.jsx +110 -0
  76. package/src/components/page-nav/page-nav.css +167 -0
  77. package/src/components/paragraph/Paragraph.jsx +35 -2
  78. package/src/components/paragraph/paragraph.css +38 -1
  79. package/src/components/radio-group/RadioGroup.jsx +121 -0
  80. package/src/components/radio-group/radio-group.css +268 -0
  81. package/src/components/section/Section.jsx +108 -0
  82. package/src/components/section/section.css +280 -0
  83. package/src/components/segmented-control/SegmentedControl.jsx +4 -0
  84. package/src/components/segmented-control/segmented.css +13 -0
  85. package/src/components/side-nav/SideNav.jsx +29 -9
  86. package/src/components/side-nav/scrim.css +1 -1
  87. package/src/components/side-nav/side-nav.css +70 -32
  88. package/src/components/snackbar/Snackbar.jsx +56 -0
  89. package/src/components/snackbar/snackbar.css +113 -0
  90. package/src/components/spacer/Spacer.jsx +36 -0
  91. package/src/components/spacer/spacer.css +44 -0
  92. package/src/components/stack/Stack.jsx +100 -0
  93. package/src/components/stack/stack.css +37 -0
  94. package/src/components/switch/Switch.jsx +114 -0
  95. package/src/components/switch/switch.css +276 -0
  96. package/src/components/system-banner/SystemBanner.jsx +57 -0
  97. package/src/components/system-banner/system-banner.css +118 -0
  98. package/src/components/tabs/Tabs.jsx +96 -28
  99. package/src/components/tabs/tabs.css +352 -15
  100. package/src/components/token-select/TokenSelect.jsx +159 -0
  101. package/src/components/token-select/token-select.css +110 -0
  102. package/src/components/top-header/TopHeader.jsx +641 -0
  103. package/src/components/top-header/top-header.css +337 -0
  104. package/src/illustrations/ComponentThumbnails.jsx +227 -0
  105. package/src/index.js +41 -5
  106. package/src/themes.css +256 -5
  107. package/src/utilities/spacing.css +8 -0
  108. package/src/utilities/sr-only.css +16 -0
@@ -1,37 +1,201 @@
1
1
  .a1-card {
2
+ container: a1-card / inline-size;
2
3
  box-sizing: border-box;
3
4
  background: var(--semantic-color-surface-page);
4
- border: var(--component-card-border-width) solid var(--semantic-color-border-strong);
5
+ border: var(--component-card-border-width) solid var(--semantic-color-border-subtle);
5
6
  border-radius: var(--component-card-border-radius);
6
7
  padding: var(--component-card-padding);
7
- box-shadow: var(--a1-card-shadow);
8
+ box-shadow: var(--semantic-shadow-sm);
8
9
  color: var(--semantic-color-text-default);
9
10
  }
10
11
 
12
+ /* ─── Navigation variant — the whole card is the link/button ─────────────── */
13
+
14
+ .a1-card--navigation {
15
+ display: block;
16
+ inline-size: 100%;
17
+ text-align: start;
18
+ text-decoration: none;
19
+ border-color: var(--semantic-color-text-accent);
20
+ cursor: pointer;
21
+ transition:
22
+ background var(--semantic-motion-duration-fast) var(--semantic-motion-easing-standard),
23
+ border-color var(--semantic-motion-duration-fast) var(--semantic-motion-easing-standard),
24
+ box-shadow var(--semantic-motion-duration-fast) var(--semantic-motion-easing-standard);
25
+ }
26
+
27
+ button.a1-card--navigation {
28
+ appearance: none;
29
+ font: inherit;
30
+ }
31
+
32
+ .a1-card--navigation:hover {
33
+ background: var(--semantic-color-action-surface);
34
+ border-color: var(--semantic-color-action-background);
35
+ }
36
+
37
+ .a1-card--navigation:active {
38
+ background: var(--semantic-color-surface-panel);
39
+ border-color: var(--semantic-color-action-background);
40
+ box-shadow: var(--semantic-shadow-xs);
41
+ }
42
+
43
+ .a1-card--navigation:focus-visible {
44
+ outline: var(--component-button-focus-ring-width) solid var(--semantic-color-action-background);
45
+ outline-offset: var(--base-spacing-2);
46
+ }
47
+
48
+ /* ─── Bare variant — no chrome, no padding ─────────────────────────────────── */
49
+
50
+ .a1-card--bare {
51
+ background: transparent;
52
+ border: none;
53
+ border-radius: 0;
54
+ box-shadow: none;
55
+ padding: 0;
56
+ }
57
+
58
+ /* ─── Content wrapper ─────────────────────────────────────────────────────── */
59
+
60
+ .a1-card__content {
61
+ min-width: 0;
62
+ }
63
+
64
+ /* ─── Hero icon — full-bleed header area (or left strip at lg) ─────────────
65
+ Container query breakpoints: sm=320px, md=480px, lg=640px
66
+ base/sm/md : coloured band spanning the full card width at the top
67
+ lg ≥ 640px : coloured strip spanning the full card height on the left
68
+ ─────────────────────────────────────────────────────────────────────────── */
69
+
70
+ .a1-card--has-hero {
71
+ overflow: hidden;
72
+ }
73
+
74
+ .a1-card--has-hero .a1-card__layout {
75
+ display: flex;
76
+ flex-direction: column;
77
+ }
78
+
79
+ .a1-card__hero {
80
+ /* Bleed out to the card edges on all four sides then add inner padding */
81
+ margin-top: calc(-1 * var(--component-card-padding));
82
+ margin-inline: calc(-1 * var(--component-card-padding));
83
+ margin-bottom: var(--component-card-padding);
84
+ padding: var(--base-spacing-32) 0;
85
+ background: var(--a1-card-hero-bg, var(--semantic-color-action-background));
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ flex-shrink: 0;
90
+ --a1-icon-opsz: 48;
91
+ }
92
+
93
+ /* Higher specificity (0,2,0) beats .a1-icon (0,1,0) so font-size is not overridden by inherit */
94
+ .a1-card__hero .a1-icon {
95
+ font-size: var(--base-spacing-64);
96
+ color: var(--semantic-color-text-inverse);
97
+ }
98
+
99
+ /* lg ≥ 640px — hero becomes a left strip, full card height; icon 3 steps larger (128px) */
100
+ @container a1-card (min-width: 640px) {
101
+ .a1-card--has-hero > .a1-card__layout {
102
+ flex-direction: row;
103
+ align-items: stretch;
104
+ }
105
+
106
+ .a1-card__hero {
107
+ /* Reset top-strip margins; bleed left/top/bottom only */
108
+ margin-top: calc(-1 * var(--component-card-padding));
109
+ margin-bottom: calc(-1 * var(--component-card-padding));
110
+ margin-inline-start: calc(-1 * var(--component-card-padding));
111
+ margin-inline-end: 0;
112
+ margin-block: calc(-1 * var(--component-card-padding));
113
+ /* Vertical strip padding */
114
+ padding: var(--base-spacing-32) var(--base-spacing-40);
115
+ /* Round left corners only — right side butts flush against content */
116
+ border-start-start-radius: calc(var(--component-card-border-radius) - var(--component-card-border-width));
117
+ border-end-start-radius: calc(var(--component-card-border-radius) - var(--component-card-border-width));
118
+ border-start-end-radius: 0;
119
+ border-end-end-radius: 0;
120
+ }
121
+
122
+ .a1-card__hero .a1-icon {
123
+ font-size: var(--base-spacing-128);
124
+ }
125
+
126
+ .a1-card--has-hero .a1-card__content {
127
+ flex: 1 1 auto;
128
+ padding-inline-start: var(--component-card-padding);
129
+ }
130
+ }
131
+
132
+ /* ─── Default icon — small tokenised icon block ─────────────────────────────
133
+ Container query breakpoints (standard: sm=320px, md=480px, lg=640px):
134
+ base : 40×40 box, 20px icon, icon above content (column)
135
+ sm ≥ 320px : 40×40 box, 24px icon
136
+ md ≥ 480px : 64×64 box, 32px icon
137
+ lg ≥ 640px : 96×96 box, 40px icon, shifted left (row layout)
138
+ ─────────────────────────────────────────────────────────────────────────── */
139
+
140
+ .a1-card--has-icon .a1-card__layout {
141
+ display: flex;
142
+ flex-direction: column;
143
+ }
144
+
11
145
  .a1-card__icon {
146
+ --a1-card-icon-box: var(--base-spacing-40);
147
+ --a1-card-icon-size: var(--component-card-icon-size);
148
+
12
149
  display: flex;
13
150
  align-items: center;
14
151
  justify-content: center;
15
- width: var(--base-spacing-40);
16
- height: var(--base-spacing-40);
152
+ width: var(--a1-card-icon-box);
153
+ height: var(--a1-card-icon-box);
17
154
  border-radius: var(--base-radius-control);
18
155
  background: var(--semantic-color-action-surface);
19
156
  color: var(--semantic-color-action-background);
20
157
  margin-bottom: var(--base-spacing-12);
21
158
  flex-shrink: 0;
22
- }
23
-
24
- .a1-card__icon {
25
159
  --a1-icon-opsz: var(--component-card-icon-optical-size);
26
160
  }
27
161
 
28
162
  .a1-card__icon .a1-icon {
29
- font-size: var(--component-card-icon-size);
163
+ font-size: var(--a1-card-icon-size);
30
164
  }
31
165
 
32
- .a1-card--shadow-none { --a1-card-shadow: none; }
33
- .a1-card--shadow-xs { --a1-card-shadow: var(--semantic-shadow-xs); }
34
- .a1-card--shadow-sm { --a1-card-shadow: var(--semantic-shadow-sm); }
35
- .a1-card--shadow-md { --a1-card-shadow: var(--semantic-shadow-md); }
36
- .a1-card--shadow-lg { --a1-card-shadow: var(--semantic-shadow-lg); }
37
- .a1-card--shadow-xl { --a1-card-shadow: var(--semantic-shadow-xl); }
166
+ /* sm 320px — icon one step larger (24px), box unchanged */
167
+ @container a1-card (min-width: 320px) {
168
+ .a1-card__icon {
169
+ --a1-card-icon-size: var(--base-spacing-24);
170
+ --a1-icon-opsz: 24;
171
+ }
172
+ }
173
+
174
+ /* md ≥ 480px — icon two steps larger (32px), box one step larger (64px) */
175
+ @container a1-card (min-width: 480px) {
176
+ .a1-card__icon {
177
+ --a1-card-icon-box: var(--base-spacing-64);
178
+ --a1-card-icon-size: var(--base-spacing-32);
179
+ --a1-icon-opsz: 40;
180
+ }
181
+ }
182
+
183
+ /* lg ≥ 640px — icon three steps larger (40px), box two steps larger (96px), shifted left */
184
+ @container a1-card (min-width: 640px) {
185
+ .a1-card--has-icon > .a1-card__layout {
186
+ flex-direction: row;
187
+ align-items: flex-start;
188
+ gap: var(--base-spacing-16);
189
+ }
190
+
191
+ .a1-card__icon {
192
+ --a1-card-icon-box: var(--base-spacing-96);
193
+ --a1-card-icon-size: var(--base-spacing-40);
194
+ --a1-icon-opsz: 48;
195
+ margin-bottom: 0;
196
+ }
197
+
198
+ .a1-card--has-icon .a1-card__content {
199
+ flex: 1 1 auto;
200
+ }
201
+ }
@@ -0,0 +1,120 @@
1
+ import { useId, useState, useContext } from "react";
2
+ import { useLabel } from "../labels/Labels.jsx";
3
+ import { MessageBadge } from "../message/Message.jsx";
4
+ import { FieldsetContext } from "../fieldset/FieldsetContext.js";
5
+ import "./checkbox-group.css";
6
+
7
+ const SIZES = ["comfortable", "default", "compact"];
8
+
9
+ export function CheckboxGroup({
10
+ label,
11
+ hint,
12
+ error,
13
+ size,
14
+ required = false,
15
+ disabled = false,
16
+ inline = false,
17
+ name,
18
+ options = [],
19
+ value,
20
+ defaultValue,
21
+ onChange,
22
+ id: providedId,
23
+ className = "",
24
+ ...props
25
+ }) {
26
+ const ctx = useContext(FieldsetContext);
27
+ const autoId = useId();
28
+ const id = providedId ?? autoId;
29
+ const hintId = `${id}-hint`;
30
+ const errorId = `${id}-error`;
31
+
32
+ const resolvedSize = SIZES.includes(size) ? size : (ctx?.size ?? "default");
33
+
34
+ const [internalValue, setInternalValue] = useState(defaultValue ?? []);
35
+ const currentValue = value ?? internalValue;
36
+
37
+ function handleChange(optionValue, checked) {
38
+ const next = checked
39
+ ? [...currentValue, optionValue]
40
+ : currentValue.filter(v => v !== optionValue);
41
+ if (value == null) setInternalValue(next);
42
+ onChange?.(next);
43
+ }
44
+
45
+ const classes = [
46
+ "a1-checkbox-group",
47
+ `a1-checkbox-group--${resolvedSize}`,
48
+ inline && "a1-checkbox-group--inline",
49
+ error && "a1-checkbox-group--error",
50
+ required && "a1-checkbox-group--required",
51
+ disabled && "a1-checkbox-group--disabled",
52
+ className,
53
+ ].filter(Boolean).join(" ");
54
+
55
+ const describedBy = [error ? errorId : hint ? hintId : null]
56
+ .filter(Boolean).join(" ") || undefined;
57
+
58
+ const requiredText = useLabel("field.required", "Required");
59
+
60
+ return (
61
+ <fieldset className={classes} aria-describedby={describedBy} {...props}>
62
+ {label && (
63
+ <legend className="a1-checkbox-group__legend">
64
+ <span className="a1-checkbox-group__legend-inner">
65
+ {label}
66
+ {required && resolvedSize === "comfortable" ? (
67
+ <MessageBadge status="info" subtle>{requiredText}</MessageBadge>
68
+ ) : required ? (
69
+ <span className="a1-field__asterisk" aria-hidden="true"> *</span>
70
+ ) : null}
71
+ </span>
72
+ </legend>
73
+ )}
74
+ {hint && !error && (
75
+ <p className="a1-checkbox-group__message a1-checkbox-group__message--hint" id={hintId}>
76
+ {hint}
77
+ </p>
78
+ )}
79
+ <div className="a1-checkbox-group__items">
80
+ {options.map((option) => {
81
+ const isChecked = currentValue.includes(option.value);
82
+ const isDisabled = disabled || option.disabled;
83
+ const itemId = `${id}-${option.value}`;
84
+
85
+ return (
86
+ <label
87
+ key={option.value}
88
+ className={[
89
+ "a1-checkbox-item",
90
+ isDisabled && "a1-checkbox-item--disabled",
91
+ ].filter(Boolean).join(" ")}
92
+ >
93
+ <input
94
+ type="checkbox"
95
+ id={itemId}
96
+ className="a1-checkbox-item__input"
97
+ name={name}
98
+ value={option.value}
99
+ checked={isChecked}
100
+ disabled={isDisabled}
101
+ onChange={(e) => handleChange(option.value, e.target.checked)}
102
+ />
103
+ <span className="a1-checkbox-item__content">
104
+ <span className="a1-checkbox-item__label">{option.label}</span>
105
+ {option.hint && (
106
+ <span className="a1-checkbox-item__hint">{option.hint}</span>
107
+ )}
108
+ </span>
109
+ </label>
110
+ );
111
+ })}
112
+ </div>
113
+ {error && (
114
+ <p className="a1-checkbox-group__message a1-checkbox-group__message--error" id={errorId} role="alert">
115
+ {error}
116
+ </p>
117
+ )}
118
+ </fieldset>
119
+ );
120
+ }
@@ -0,0 +1,304 @@
1
+ /* ─── CheckboxGroup ───────────────────────────────────────────────────────── */
2
+
3
+ .a1-checkbox-group {
4
+ border: none;
5
+ margin: 0;
6
+ padding: 0;
7
+ min-width: 0;
8
+
9
+ /* Size tokens — default */
10
+ --a1-cb-size: var(--component-checkbox-group-box-size);
11
+ --a1-cb-radius: var(--base-radius-sm);
12
+ --a1-cb-gap: var(--component-checkbox-group-gap); /* horizontal gap: box ↔ text */
13
+ --a1-cb-item-gap: var(--component-checkbox-group-item-gap); /* gap between items (row padding provides visual breathing room) */
14
+ --a1-cb-group-gap: var(--component-checkbox-group-group-gap); /* vertical gap between legend/hint and message */
15
+ --a1-cb-items-top-gap: var(--component-checkbox-group-items-top-gap); /* extra space between legend/hint and the items stack */
16
+ --a1-cb-input-nudge: var(--component-checkbox-group-input-nudge); /* top margin aligning box center with label cap-height */
17
+ --a1-cb-row-py: var(--component-checkbox-group-row-padding-block); /* vertical padding on each item row */
18
+ --a1-cb-row-px: var(--component-checkbox-group-row-padding-inline); /* horizontal padding on each item row */
19
+ --a1-cb-legend-size: var(--semantic-font-size-body-sm);
20
+ --a1-cb-label-size: var(--semantic-font-size-body-md);
21
+ --a1-cb-hint-size: var(--semantic-font-size-body-xs);
22
+ --a1-cb-msg-size: var(--semantic-font-size-body-xs);
23
+
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--a1-cb-group-gap);
27
+ }
28
+
29
+ /* ─── Sizes ───────────────────────────────────────────────────────────────── */
30
+
31
+ .a1-checkbox-group--comfortable {
32
+ --a1-cb-size: var(--component-checkbox-group-comfortable-box-size);
33
+ --a1-cb-gap: var(--component-checkbox-group-comfortable-gap);
34
+ --a1-cb-item-gap: var(--component-checkbox-group-comfortable-item-gap);
35
+ --a1-cb-group-gap: var(--component-checkbox-group-comfortable-group-gap);
36
+ --a1-cb-items-top-gap: var(--component-checkbox-group-comfortable-items-top-gap);
37
+ --a1-cb-input-nudge: var(--component-checkbox-group-comfortable-input-nudge);
38
+ --a1-cb-row-py: var(--component-checkbox-group-comfortable-row-padding-block);
39
+ --a1-cb-row-px: var(--component-checkbox-group-comfortable-row-padding-inline);
40
+ --a1-cb-legend-size: var(--semantic-font-size-body-md);
41
+ --a1-cb-label-size: var(--semantic-font-size-body-md);
42
+ --a1-cb-hint-size: var(--semantic-font-size-body-sm);
43
+ --a1-cb-msg-size: var(--semantic-font-size-body-sm);
44
+ }
45
+
46
+ .a1-checkbox-group--comfortable .a1-checkbox-item__label {
47
+ font-weight: var(--base-font-weight-medium);
48
+ }
49
+
50
+ @media (--bp-md-up) {
51
+ .a1-checkbox-group--comfortable {
52
+ --a1-cb-legend-size: var(--semantic-font-size-body-lg);
53
+ --a1-cb-label-size: var(--semantic-font-size-body-lg);
54
+ --a1-cb-size: var(--component-checkbox-group-comfortable-box-size-md);
55
+ --a1-cb-input-nudge: var(--component-checkbox-group-comfortable-input-nudge);
56
+ }
57
+ }
58
+
59
+ .a1-checkbox-group--compact {
60
+ --a1-cb-size: var(--component-checkbox-group-compact-box-size);
61
+ --a1-cb-gap: var(--component-checkbox-group-compact-gap);
62
+ --a1-cb-item-gap: var(--component-checkbox-group-compact-item-gap);
63
+ --a1-cb-group-gap: var(--component-checkbox-group-compact-group-gap);
64
+ --a1-cb-items-top-gap: var(--component-checkbox-group-compact-items-top-gap);
65
+ --a1-cb-input-nudge: var(--component-checkbox-group-compact-input-nudge);
66
+ --a1-cb-row-py: var(--component-checkbox-group-compact-row-padding-block);
67
+ --a1-cb-row-px: var(--component-checkbox-group-compact-row-padding-inline);
68
+ --a1-cb-legend-size: var(--semantic-font-size-body-xs);
69
+ --a1-cb-label-size: var(--semantic-font-size-body-sm);
70
+ --a1-cb-hint-size: var(--semantic-font-size-body-xs);
71
+ --a1-cb-msg-size: var(--semantic-font-size-body-xs);
72
+ }
73
+
74
+ /* ─── Legend ──────────────────────────────────────────────────────────────── */
75
+
76
+ .a1-checkbox-group__legend {
77
+ display: block;
78
+ width: 100%;
79
+ padding: 0;
80
+ font-family: var(--component-paragraph-font-family);
81
+ font-size: var(--a1-cb-legend-size);
82
+ font-weight: var(--component-field-label-font-weight);
83
+ color: var(--semantic-color-text-default);
84
+ line-height: var(--semantic-font-line-height-body);
85
+ }
86
+
87
+ .a1-checkbox-group--disabled .a1-checkbox-group__legend {
88
+ color: var(--semantic-color-text-muted);
89
+ }
90
+
91
+ /* Inline-flex wrapper handles the gap between legend text and required badge/asterisk */
92
+ .a1-checkbox-group__legend-inner {
93
+ display: inline-flex;
94
+ align-items: center;
95
+ gap: var(--base-spacing-6);
96
+ }
97
+
98
+ /* ─── Group hint / error message ──────────────────────────────────────────── */
99
+
100
+ .a1-checkbox-group__message {
101
+ margin: 0;
102
+ font-family: var(--component-paragraph-font-family);
103
+ font-size: var(--a1-cb-msg-size);
104
+ line-height: var(--semantic-font-line-height-body);
105
+ }
106
+
107
+ .a1-checkbox-group__message--hint { color: var(--semantic-color-text-muted); }
108
+ .a1-checkbox-group__message--error { color: var(--semantic-color-status-error-background); }
109
+
110
+ /* ─── Items stack ─────────────────────────────────────────────────────────── */
111
+
112
+ .a1-checkbox-group__items {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: var(--a1-cb-item-gap);
116
+ margin-top: var(--a1-cb-items-top-gap);
117
+ }
118
+
119
+ /* ─── Individual item ─────────────────────────────────────────────────────── */
120
+
121
+ .a1-checkbox-item {
122
+ display: flex;
123
+ align-items: flex-start;
124
+ gap: var(--a1-cb-gap);
125
+ padding: var(--a1-cb-row-py) var(--a1-cb-row-px);
126
+ margin-inline: calc(-1 * var(--a1-cb-row-px));
127
+ border-radius: var(--base-radius-md);
128
+ cursor: pointer;
129
+ transition: background-color var(--semantic-motion-duration-fast);
130
+ }
131
+
132
+ .a1-checkbox-item--disabled {
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ .a1-checkbox-item__content {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: var(--component-checkbox-group-content-gap);
140
+ }
141
+
142
+ .a1-checkbox-item__label {
143
+ font-family: var(--component-paragraph-font-family);
144
+ font-size: var(--a1-cb-label-size);
145
+ font-weight: var(--base-font-weight-regular);
146
+ color: var(--semantic-color-text-muted);
147
+ line-height: var(--semantic-font-line-height-body);
148
+ }
149
+
150
+ /* Restore default color when the item is checked */
151
+ .a1-checkbox-item:has(.a1-checkbox-item__input:checked) .a1-checkbox-item__label {
152
+ color: var(--semantic-color-text-default);
153
+ }
154
+
155
+ /* Disabled always stays muted regardless of checked state */
156
+ .a1-checkbox-item--disabled .a1-checkbox-item__label {
157
+ color: var(--semantic-color-text-muted);
158
+ }
159
+
160
+ .a1-checkbox-item__hint {
161
+ font-family: var(--component-paragraph-font-family);
162
+ font-size: var(--a1-cb-hint-size);
163
+ font-weight: var(--base-font-weight-regular);
164
+ color: var(--semantic-color-text-muted);
165
+ line-height: var(--semantic-font-line-height-body);
166
+ }
167
+
168
+ /* ─── Checkbox input ──────────────────────────────────────────────────────── */
169
+
170
+ .a1-checkbox-item__input {
171
+ -webkit-appearance: none;
172
+ appearance: none;
173
+ flex-shrink: 0;
174
+ width: var(--a1-cb-size);
175
+ height: var(--a1-cb-size);
176
+ /* Nudge aligns the checkbox's visual center with the label's cap-height */
177
+ margin-top: var(--a1-cb-input-nudge);
178
+ border: var(--component-field-border-width) solid var(--semantic-color-border-strong);
179
+ border-radius: var(--a1-cb-radius);
180
+ background-color: var(--semantic-color-surface-page);
181
+ cursor: pointer;
182
+ transition:
183
+ border-color var(--semantic-motion-duration-fast),
184
+ background-color var(--semantic-motion-duration-fast);
185
+ }
186
+
187
+ /* Checked row — always show a tinted background */
188
+ .a1-checkbox-item:not(.a1-checkbox-item--disabled):has(.a1-checkbox-item__input:checked) {
189
+ background-color: var(--a1-field-hover-background);
190
+ }
191
+
192
+ /* Hover — entire row background, triggered anywhere on the label */
193
+ .a1-checkbox-item:not(.a1-checkbox-item--disabled):hover {
194
+ background-color: var(--a1-field-hover-background);
195
+ }
196
+
197
+ /* Checked row on hover — step one shade deeper/lighter */
198
+ .a1-checkbox-item:not(.a1-checkbox-item--disabled):hover:has(.a1-checkbox-item__input:checked) {
199
+ background-color: var(--a1-field-active-background);
200
+ }
201
+
202
+ /* Also darken the input border on row hover (unchecked/non-indeterminate only) */
203
+ .a1-checkbox-item:not(.a1-checkbox-item--disabled):hover
204
+ .a1-checkbox-item__input:not(:checked):not(:indeterminate) {
205
+ border-color: var(--a1-field-hover-border-color);
206
+ }
207
+
208
+ /* Focus */
209
+ .a1-checkbox-item__input:focus-visible {
210
+ outline: var(--component-field-focus-ring-width) solid var(--component-field-focus-ring-color);
211
+ outline-offset: var(--component-field-focus-ring-offset);
212
+ }
213
+
214
+ /* Checked */
215
+ .a1-checkbox-item__input:checked {
216
+ background-color: var(--semantic-color-status-info-background);
217
+ border-color: var(--semantic-color-status-info-background);
218
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpolyline points='2,6 5,9.5 10,3' fill='none' stroke='%23fafcff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
219
+ background-repeat: no-repeat;
220
+ background-position: center;
221
+ background-size: var(--component-checkbox-group-icon-size);
222
+ }
223
+
224
+ /* Indeterminate */
225
+ .a1-checkbox-item__input:indeterminate {
226
+ background-color: var(--semantic-color-status-info-background);
227
+ border-color: var(--semantic-color-status-info-background);
228
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cline x1='3' y1='6' x2='9' y2='6' stroke='%23fafcff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
229
+ background-repeat: no-repeat;
230
+ background-position: center;
231
+ background-size: var(--component-checkbox-group-icon-size);
232
+ }
233
+
234
+ /* Disabled (unchecked) */
235
+ .a1-checkbox-item__input:disabled {
236
+ background-color: var(--semantic-color-surface-raised);
237
+ border-color: var(--semantic-color-border-subtle);
238
+ cursor: not-allowed;
239
+ }
240
+
241
+ /* Disabled + checked */
242
+ .a1-checkbox-item__input:checked:disabled {
243
+ background-color: var(--semantic-color-border-default);
244
+ border-color: var(--semantic-color-border-default);
245
+ }
246
+
247
+ /* ─── Inline layout ───────────────────────────────────────────────────────── */
248
+
249
+ .a1-checkbox-group--inline .a1-checkbox-group__items {
250
+ flex-direction: row;
251
+ flex-wrap: wrap;
252
+ gap: var(--a1-cb-gap);
253
+ }
254
+
255
+ /* Each inline item becomes a bordered pill — provides visual separation */
256
+ .a1-checkbox-group--inline .a1-checkbox-item {
257
+ margin-inline: 0;
258
+ border: var(--component-field-border-width) solid var(--semantic-color-border-subtle);
259
+ }
260
+
261
+ /* Unchecked inline hover — darken border */
262
+ .a1-checkbox-group--inline .a1-checkbox-item:not(.a1-checkbox-item--disabled):hover {
263
+ border-color: var(--a1-field-hover-border-color);
264
+ }
265
+
266
+ /* Checked inline item — accent border + background tint */
267
+ .a1-checkbox-group--inline .a1-checkbox-item:not(.a1-checkbox-item--disabled):has(.a1-checkbox-item__input:checked) {
268
+ border-color: var(--semantic-color-status-info-background);
269
+ }
270
+
271
+ /* Checked inline hover — keep accent border, deepen background */
272
+ .a1-checkbox-group--inline .a1-checkbox-item:not(.a1-checkbox-item--disabled):hover:has(.a1-checkbox-item__input:checked) {
273
+ border-color: var(--semantic-color-status-info-background);
274
+ }
275
+
276
+ /* Disabled inline item */
277
+ .a1-checkbox-group--inline .a1-checkbox-item--disabled {
278
+ border-color: var(--semantic-color-border-subtle);
279
+ opacity: var(--component-checkbox-group-disabled-opacity);
280
+ }
281
+
282
+ /* Inline error — left accent doesn't suit a row layout; just use input borders */
283
+ .a1-checkbox-group--inline.a1-checkbox-group--error .a1-checkbox-group__items {
284
+ border-left: none;
285
+ padding-left: 0;
286
+ }
287
+
288
+ /* ─── Error state ─────────────────────────────────────────────────────────── */
289
+
290
+ /* Left accent border on the items block makes the error scope obvious at a glance */
291
+ .a1-checkbox-group--error .a1-checkbox-group__items {
292
+ border-left: var(--component-checkbox-group-error-accent-width) solid var(--semantic-color-status-error-background);
293
+ padding-left: var(--base-spacing-12);
294
+ }
295
+
296
+ /* Unchecked items get a red border too */
297
+ .a1-checkbox-group--error .a1-checkbox-item__input:not(:checked):not(:disabled) {
298
+ border-color: var(--semantic-color-status-error-border);
299
+ }
300
+
301
+ /* Bolder error message */
302
+ .a1-checkbox-group__message--error {
303
+ font-weight: var(--component-checkbox-group-error-message-font-weight);
304
+ }
@@ -0,0 +1,52 @@
1
+ import "./cluster.css";
2
+ import { resolveSpacing } from "../structure-utils.js";
3
+
4
+ const alignments = ["start", "center", "end", "stretch", "baseline"];
5
+ const justifications = ["start", "center", "end", "between", "around", "evenly"];
6
+
7
+ const alignMap = {
8
+ start: "flex-start",
9
+ center: "center",
10
+ end: "flex-end",
11
+ stretch: "stretch",
12
+ baseline: "baseline",
13
+ };
14
+
15
+ const justifyMap = {
16
+ start: "flex-start",
17
+ center: "center",
18
+ end: "flex-end",
19
+ between: "space-between",
20
+ around: "space-around",
21
+ evenly: "space-evenly",
22
+ };
23
+
24
+ export function Cluster({
25
+ as: Component = "div",
26
+ gap = 8,
27
+ rowGap,
28
+ columnGap,
29
+ align = "center",
30
+ justify = "start",
31
+ className = "",
32
+ children,
33
+ ...props
34
+ }) {
35
+ const resolvedAlign = alignments.includes(align) ? align : "center";
36
+ const resolvedJustify = justifications.includes(justify) ? justify : "start";
37
+ const gapValue = resolveSpacing(gap);
38
+
39
+ const style = {
40
+ "--a1-cluster-row-gap": resolveSpacing(rowGap) ?? gapValue,
41
+ "--a1-cluster-column-gap": resolveSpacing(columnGap) ?? gapValue,
42
+ "--a1-cluster-align": alignMap[resolvedAlign],
43
+ "--a1-cluster-justify": justifyMap[resolvedJustify],
44
+ ...props.style,
45
+ };
46
+
47
+ return (
48
+ <Component className={["a1-cluster", className].filter(Boolean).join(" ")} style={style} {...props}>
49
+ {children}
50
+ </Component>
51
+ );
52
+ }