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

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 (111) hide show
  1. package/guidelines/Guidelines.md +228 -0
  2. package/package.json +4 -1
  3. package/src/breakpoints.css +29 -0
  4. package/src/color-scheme.css +586 -24
  5. package/src/components/accordion/Accordion.jsx +80 -0
  6. package/src/components/accordion/accordion.css +118 -0
  7. package/src/components/banner/Banner.jsx +66 -0
  8. package/src/components/banner/banner.css +205 -0
  9. package/src/components/bleed/Bleed.jsx +27 -0
  10. package/src/components/bleed/bleed.css +5 -0
  11. package/src/components/blockquote/Blockquote.jsx +40 -0
  12. package/src/components/blockquote/blockquote.css +166 -0
  13. package/src/components/breadcrumb/Breadcrumb.jsx +82 -0
  14. package/src/components/breadcrumb/breadcrumb.css +133 -0
  15. package/src/components/button/button.css +42 -12
  16. package/src/components/button-container/ButtonContainer.jsx +20 -1
  17. package/src/components/button-container/button-container.css +19 -1
  18. package/src/components/calendar/Calendar.jsx +383 -0
  19. package/src/components/calendar/calendar.css +225 -0
  20. package/src/components/card/Card.jsx +50 -12
  21. package/src/components/card/card.css +178 -14
  22. package/src/components/checkbox-group/CheckboxGroup.jsx +120 -0
  23. package/src/components/checkbox-group/checkbox-group.css +304 -0
  24. package/src/components/cluster/Cluster.jsx +52 -0
  25. package/src/components/cluster/cluster.css +9 -0
  26. package/src/components/code/Code.jsx +135 -0
  27. package/src/components/code/code.css +60 -0
  28. package/src/components/data-table/DataTable.jsx +721 -0
  29. package/src/components/data-table/DataTableFilters.jsx +339 -0
  30. package/src/components/data-table/data-table-filters.css +259 -0
  31. package/src/components/data-table/data-table.css +425 -0
  32. package/src/components/dialog/Dialog.jsx +45 -2
  33. package/src/components/dialog/dialog.css +13 -4
  34. package/src/components/divider/Divider.jsx +64 -0
  35. package/src/components/divider/divider.css +170 -0
  36. package/src/components/field/CreditCardField.jsx +131 -0
  37. package/src/components/field/DateField.jsx +11 -0
  38. package/src/components/field/NumberField.jsx +11 -0
  39. package/src/components/field/PhoneField.jsx +107 -0
  40. package/src/components/field/SelectField.jsx +86 -0
  41. package/src/components/field/TextField.jsx +83 -0
  42. package/src/components/field/TextareaField.jsx +147 -0
  43. package/src/components/field/TimeField.jsx +11 -0
  44. package/src/components/field/ZipField.jsx +114 -0
  45. package/src/components/field/credit-card.css +30 -0
  46. package/src/components/field/field.css +380 -0
  47. package/src/components/field/textarea-field.css +185 -0
  48. package/src/components/field-row/FieldRow.jsx +23 -0
  49. package/src/components/field-row/field-row.css +51 -0
  50. package/src/components/fieldset/Fieldset.jsx +49 -0
  51. package/src/components/fieldset/fieldset.css +75 -0
  52. package/src/components/figure/Figure.jsx +63 -0
  53. package/src/components/figure/figure.css +97 -0
  54. package/src/components/grid/Grid.jsx +36 -2
  55. package/src/components/grid/grid.css +129 -4
  56. package/src/components/heading/Heading.jsx +41 -1
  57. package/src/components/heading/heading.css +65 -4
  58. package/src/components/icon/icon.css +1 -0
  59. package/src/components/icon-button/icon-button.css +1 -0
  60. package/src/components/inline/inline.css +51 -0
  61. package/src/components/inline-editable/InlineEditable.jsx +77 -0
  62. package/src/components/inline-editable/inline-editable.css +47 -0
  63. package/src/components/inset/Inset.jsx +27 -0
  64. package/src/components/inset/inset.css +6 -0
  65. package/src/components/labels/Labels.jsx +5 -5
  66. package/src/components/link/Link.jsx +2 -3
  67. package/src/components/link/link.css +30 -1
  68. package/src/components/list/List.jsx +92 -0
  69. package/src/components/list/list.css +178 -0
  70. package/src/components/menu/Menu.jsx +243 -10
  71. package/src/components/menu/menu.css +157 -17
  72. package/src/components/message/Message.jsx +25 -50
  73. package/src/components/message/message.css +50 -33
  74. package/src/components/notification/Notification.jsx +1 -1
  75. package/src/components/page-layout/PageLayout.jsx +16 -1
  76. package/src/components/page-layout/page-layout.css +97 -4
  77. package/src/components/page-nav/PageNav.jsx +110 -0
  78. package/src/components/page-nav/page-nav.css +167 -0
  79. package/src/components/paragraph/Paragraph.jsx +35 -2
  80. package/src/components/paragraph/paragraph.css +38 -1
  81. package/src/components/radio-group/RadioGroup.jsx +121 -0
  82. package/src/components/radio-group/radio-group.css +268 -0
  83. package/src/components/section/Section.jsx +108 -0
  84. package/src/components/section/section.css +280 -0
  85. package/src/components/segmented-control/SegmentedControl.jsx +4 -0
  86. package/src/components/segmented-control/segmented.css +13 -0
  87. package/src/components/side-nav/SideNav.jsx +29 -9
  88. package/src/components/side-nav/scrim.css +1 -1
  89. package/src/components/side-nav/side-nav.css +70 -32
  90. package/src/components/snackbar/Snackbar.jsx +56 -0
  91. package/src/components/snackbar/snackbar.css +113 -0
  92. package/src/components/spacer/Spacer.jsx +36 -0
  93. package/src/components/spacer/spacer.css +44 -0
  94. package/src/components/stack/Stack.jsx +100 -0
  95. package/src/components/stack/stack.css +37 -0
  96. package/src/components/switch/Switch.jsx +114 -0
  97. package/src/components/switch/switch.css +276 -0
  98. package/src/components/system-banner/SystemBanner.jsx +57 -0
  99. package/src/components/system-banner/system-banner.css +118 -0
  100. package/src/components/tabs/Tabs.jsx +96 -28
  101. package/src/components/tabs/tabs.css +352 -15
  102. package/src/components/token-select/TokenSelect.jsx +159 -0
  103. package/src/components/token-select/token-select.css +110 -0
  104. package/src/components/top-header/TopHeader.jsx +641 -0
  105. package/src/components/top-header/top-header.css +337 -0
  106. package/src/illustrations/ComponentThumbnails.jsx +227 -0
  107. package/src/index.js +41 -5
  108. package/src/themes.css +256 -5
  109. package/src/tokens.css +919 -0
  110. package/src/utilities/spacing.css +8 -0
  111. package/src/utilities/sr-only.css +16 -0
@@ -1,6 +1,7 @@
1
1
  /* ─── Segmented control ───────────────────────────────────────────────────── */
2
2
 
3
3
  .a1-segmented {
4
+ box-sizing: border-box;
4
5
  display: inline-flex;
5
6
  align-items: stretch;
6
7
  background: var(--semantic-color-surface-raised);
@@ -69,6 +70,18 @@
69
70
  padding-inline: var(--component-segmented-segment-padding-block);
70
71
  }
71
72
 
73
+ /* ─── Dark mode ───────────────────────────────────────────────────────────── */
74
+
75
+ .a1-theme-dark .a1-segment:not([aria-checked="true"]),
76
+ .a1-inverse .a1-segment:not([aria-checked="true"]) {
77
+ color: var(--base-color-neutral-300);
78
+ }
79
+
80
+ .a1-theme-dark .a1-segment:hover:not([aria-checked="true"]),
81
+ .a1-inverse .a1-segment:hover:not([aria-checked="true"]) {
82
+ color: var(--base-color-neutral-100);
83
+ }
84
+
72
85
  /* ─── Full width ──────────────────────────────────────────────────────────── */
73
86
 
74
87
  .a1-segmented--full-width .a1-segment {
@@ -17,7 +17,7 @@ const SideNavCtx = createContext({ collapsed: false, onExpand: null });
17
17
  * @param {boolean} [props.active] - Marks this item as the current page
18
18
  * @param {string} [props.className]
19
19
  */
20
- export function SideNavItem({ as: Component = "a", icon, label, active, className = "", ...props }) {
20
+ export function SideNavItem({ as: Component = "a", icon, label, badge, active, className = "", ...props }) {
21
21
  const depth = useContext(DepthCtx);
22
22
  const { collapsed } = useContext(SideNavCtx);
23
23
 
@@ -37,6 +37,7 @@ export function SideNavItem({ as: Component = "a", icon, label, active, classNam
37
37
  >
38
38
  {icon && <Icon name={icon} className="a1-side-nav-item__icon" />}
39
39
  <span className="a1-side-nav-item__label">{label}</span>
40
+ {badge && !collapsed && <span className="a1-side-nav-item__badge">{badge}</span>}
40
41
  </Component>
41
42
  );
42
43
  }
@@ -112,7 +113,8 @@ export function SideNavGroup({ icon, label, defaultOpen = false, open: controlle
112
113
  * - sm/md (481–1024px): fixed-width overlay with scrim; built-in close (✕) button
113
114
  * - lg/xl (≥1025px): persistent in the document flow; built-in collapse (‹/›) toggle
114
115
  *
115
- * The close/collapse button is rendered inline with the header content.
116
+ * The close button is rendered inline with the header content. The desktop
117
+ * collapse button can be rendered in the header or footer.
116
118
  *
117
119
  * @param {object} props
118
120
  * @param {React.ReactNode | ((collapsed: boolean) => React.ReactNode)} [props.header]
@@ -125,6 +127,7 @@ export function SideNavGroup({ icon, label, defaultOpen = false, open: controlle
125
127
  * @param {boolean} [props.defaultCollapsed=false] - Initial collapsed state for lg/xl (uncontrolled)
126
128
  * @param {boolean} [props.collapsed] - Controlled collapsed state for lg/xl
127
129
  * @param {function} [props.onCollapsedChange] - Called with next boolean when collapsed state changes
130
+ * @param {"header"|"footer"} [props.collapseButtonPlacement="header"] - Where the desktop collapse button appears
128
131
  * @param {"start"|"end"} [props.placement="start"] - Side of the viewport/layout where the nav appears
129
132
  * @param {string} [props.className]
130
133
  */
@@ -132,6 +135,7 @@ export function SideNav({
132
135
  header, footer, children,
133
136
  open = false, onClose,
134
137
  collapsed: controlledCollapsed, defaultCollapsed = false, onCollapsedChange,
138
+ collapseButtonPlacement = "header",
135
139
  placement = "start",
136
140
  className = "", ...props
137
141
  }) {
@@ -157,10 +161,12 @@ export function SideNav({
157
161
  }, [open, onClose]);
158
162
 
159
163
  const resolvedHeader = typeof header === "function" ? header(isCollapsed) : header;
164
+ const resolvedFooter = typeof footer === "function" ? footer(isCollapsed) : footer;
160
165
 
161
166
  const navClasses = [
162
167
  "a1-side-nav",
163
168
  `a1-side-nav--placement-${placement}`,
169
+ collapseButtonPlacement === "footer" && "a1-side-nav--collapse-footer",
164
170
  open && "a1-side-nav--open",
165
171
  isCollapsed && "a1-side-nav--collapsed",
166
172
  className,
@@ -189,19 +195,33 @@ export function SideNav({
189
195
  className="a1-side-nav__close-btn"
190
196
  onClick={onClose}
191
197
  />
192
- <IconButton
193
- icon={collapseIcon}
194
- label={isCollapsed ? "Expand navigation" : "Collapse navigation"}
195
- className="a1-side-nav__collapse-btn"
196
- onClick={toggleCollapse}
197
- />
198
+ {collapseButtonPlacement === "header" && (
199
+ <IconButton
200
+ icon={collapseIcon}
201
+ label={isCollapsed ? "Expand navigation" : "Collapse navigation"}
202
+ className="a1-side-nav__collapse-btn"
203
+ onClick={toggleCollapse}
204
+ />
205
+ )}
198
206
  </div>
199
207
 
200
208
  <SideNavCtx.Provider value={{ collapsed: isCollapsed, onExpand: handleExpand }}>
201
209
  <div className="a1-side-nav__nav">{children}</div>
202
210
  </SideNavCtx.Provider>
203
211
 
204
- {footer && <div className="a1-side-nav__footer">{footer}</div>}
212
+ {(resolvedFooter || collapseButtonPlacement === "footer") && (
213
+ <div className="a1-side-nav__footer">
214
+ {resolvedFooter && <div className="a1-side-nav__footer-content">{resolvedFooter}</div>}
215
+ {collapseButtonPlacement === "footer" && (
216
+ <IconButton
217
+ icon={collapseIcon}
218
+ label={isCollapsed ? "Expand navigation" : "Collapse navigation"}
219
+ className="a1-side-nav__collapse-btn"
220
+ onClick={toggleCollapse}
221
+ />
222
+ )}
223
+ </div>
224
+ )}
205
225
  </nav>
206
226
  </>
207
227
  );
@@ -8,7 +8,7 @@
8
8
  backdrop-filter: blur(var(--component-scrim-blur));
9
9
  opacity: 0;
10
10
  pointer-events: none;
11
- transition: opacity 250ms ease;
11
+ transition: opacity var(--semantic-motion-duration-slow) var(--semantic-motion-easing-standard);
12
12
  }
13
13
 
14
14
  .a1-scrim--visible {
@@ -4,16 +4,16 @@
4
4
  box-sizing: border-box;
5
5
  display: flex;
6
6
  flex-direction: column;
7
- width: var(--component-side-nav-width, 280px);
7
+ width: var(--component-side-nav-width);
8
8
  height: 100%;
9
9
  background: var(--semantic-color-surface-panel);
10
- border-inline-end: 1px solid var(--semantic-color-border-subtle);
10
+ border-inline-end: var(--component-side-nav-border-width) solid var(--semantic-color-border-subtle);
11
11
  overflow: hidden;
12
12
  }
13
13
 
14
14
  .a1-side-nav--placement-end {
15
15
  border-inline-end: none;
16
- border-inline-start: 1px solid var(--semantic-color-border-subtle);
16
+ border-inline-start: var(--component-side-nav-border-width) solid var(--semantic-color-border-subtle);
17
17
  }
18
18
 
19
19
  /* ── Header row — logo/content inline with close or collapse button ─────── */
@@ -25,8 +25,8 @@
25
25
  align-items: center;
26
26
  gap: var(--base-spacing-4);
27
27
  padding: var(--base-spacing-8) var(--base-spacing-8) var(--base-spacing-8) var(--base-spacing-12);
28
- border-block-end: 1px solid var(--semantic-color-border-subtle);
29
- min-height: 52px;
28
+ border-block-end: var(--component-side-nav-border-width) solid var(--semantic-color-border-subtle);
29
+ min-height: var(--component-side-nav-header-min-height);
30
30
  }
31
31
 
32
32
  .a1-side-nav__header-content {
@@ -40,7 +40,7 @@
40
40
  flex-shrink: 0;
41
41
  }
42
42
 
43
- @media (max-width: 1024px) {
43
+ @media (--bp-md-down) {
44
44
  .a1-side-nav .a1-side-nav__close-btn {
45
45
  display: inline-flex;
46
46
  }
@@ -52,7 +52,7 @@
52
52
  flex-shrink: 0;
53
53
  }
54
54
 
55
- @media (min-width: 1025px) {
55
+ @media (--bp-lg-up) {
56
56
  .a1-side-nav .a1-side-nav__collapse-btn {
57
57
  display: inline-flex;
58
58
  }
@@ -77,7 +77,18 @@
77
77
  flex-shrink: 0;
78
78
  padding: var(--component-side-nav-padding-block, var(--base-spacing-8))
79
79
  var(--component-side-nav-padding-inline, var(--base-spacing-8));
80
- border-block-start: 1px solid var(--semantic-color-border-subtle);
80
+ border-block-start: var(--component-side-nav-border-width) solid var(--semantic-color-border-subtle);
81
+ }
82
+
83
+ .a1-side-nav--collapse-footer .a1-side-nav__footer {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: var(--base-spacing-4);
87
+ }
88
+
89
+ .a1-side-nav__footer-content {
90
+ flex: 1 1 auto;
91
+ min-width: 0;
81
92
  }
82
93
 
83
94
  /* ── Item — shared by SideNavItem and SideNavGroup trigger ─────────────── */
@@ -88,7 +99,7 @@
88
99
  align-items: center;
89
100
  gap: var(--component-side-nav-item-gap, var(--base-spacing-8));
90
101
  width: 100%;
91
- min-height: var(--component-side-nav-item-height, 36px);
102
+ min-height: var(--component-side-nav-item-height);
92
103
  padding-block: var(--base-spacing-4);
93
104
  padding-inline-start: calc(
94
105
  var(--component-side-nav-item-padding-inline, var(--base-spacing-8)) +
@@ -96,10 +107,10 @@
96
107
  );
97
108
  padding-inline-end: var(--component-side-nav-item-padding-inline, var(--base-spacing-8));
98
109
  border: none;
99
- border-radius: var(--component-side-nav-item-border-radius, 6px);
110
+ border-radius: var(--component-side-nav-item-border-radius);
100
111
  font-family: var(--semantic-font-family-body);
101
- font-size: var(--semantic-font-size-body-sm, 14px);
102
- line-height: 1.5;
112
+ font-size: var(--semantic-font-size-body-sm);
113
+ line-height: var(--component-side-nav-item-font-line-height);
103
114
  color: var(--semantic-color-text-default);
104
115
  text-decoration: none;
105
116
  text-align: start;
@@ -107,7 +118,9 @@
107
118
  overflow: hidden;
108
119
  cursor: pointer;
109
120
  background: transparent;
110
- transition: background 150ms ease, color 150ms ease;
121
+ transition:
122
+ background var(--semantic-motion-duration-fast) var(--semantic-motion-easing-standard),
123
+ color var(--semantic-motion-duration-fast) var(--semantic-motion-easing-standard);
111
124
  }
112
125
 
113
126
  .a1-side-nav-item:hover {
@@ -119,8 +132,8 @@
119
132
  }
120
133
 
121
134
  .a1-side-nav-item:focus-visible {
122
- outline: var(--component-side-nav-item-focus-ring-width, 3px) solid var(--semantic-color-text-accent);
123
- outline-offset: var(--component-side-nav-item-focus-ring-offset, -2px);
135
+ outline: var(--component-side-nav-item-focus-ring-width) solid var(--semantic-color-text-accent);
136
+ outline-offset: var(--component-side-nav-item-focus-ring-offset);
124
137
  }
125
138
 
126
139
  /* ── Active state ──────────────────────────────────────────────────────── */
@@ -128,7 +141,7 @@
128
141
  .a1-side-nav-item--active {
129
142
  background: color-mix(in srgb, transparent, var(--semantic-color-text-accent) 10%);
130
143
  color: var(--semantic-color-text-accent);
131
- font-weight: 500;
144
+ font-weight: var(--component-side-nav-item-active-font-weight);
132
145
  }
133
146
 
134
147
  .a1-side-nav-item--active:hover {
@@ -149,6 +162,11 @@
149
162
  text-overflow: ellipsis;
150
163
  }
151
164
 
165
+ .a1-side-nav-item__badge {
166
+ flex-shrink: 0;
167
+ line-height: 1;
168
+ }
169
+
152
170
  /* ── Group ─────────────────────────────────────────────────────────────── */
153
171
 
154
172
  .a1-side-nav-group {
@@ -158,10 +176,10 @@
158
176
  }
159
177
 
160
178
  .a1-side-nav-group__chevron {
161
- font-size: 18px;
179
+ font-size: var(--component-side-nav-item-chevron-size);
162
180
  flex-shrink: 0;
163
181
  color: var(--semantic-color-text-muted);
164
- transition: transform 200ms ease;
182
+ transition: transform var(--semantic-motion-duration-normal) var(--semantic-motion-easing-standard);
165
183
  }
166
184
 
167
185
  .a1-side-nav-group__trigger--open .a1-side-nav-group__chevron {
@@ -173,7 +191,7 @@
173
191
  .a1-side-nav-group__children {
174
192
  display: grid;
175
193
  grid-template-rows: 0fr;
176
- transition: grid-template-rows 200ms ease;
194
+ transition: grid-template-rows var(--semantic-motion-duration-normal) var(--semantic-motion-easing-standard);
177
195
  }
178
196
 
179
197
  .a1-side-nav-group__children--open {
@@ -191,27 +209,27 @@
191
209
  /* ── Responsive overlay behavior ───────────────────────────────────────── */
192
210
 
193
211
  /* Scrim — sm/md only (xs nav is full-width, lg+ nav is persistent) */
194
- @media (max-width: 480px) {
212
+ @media (--bp-xs) {
195
213
  .a1-side-nav__scrim {
196
214
  display: none;
197
215
  }
198
216
  }
199
217
 
200
- @media (min-width: 1025px) {
218
+ @media (--bp-lg-up) {
201
219
  .a1-side-nav__scrim {
202
220
  display: none;
203
221
  }
204
222
  }
205
223
 
206
224
  /* xs + sm + md (≤1024px): fixed overlay, slides in from the start edge */
207
- @media (max-width: 1024px) {
225
+ @media (--bp-md-down) {
208
226
  .a1-side-nav {
209
227
  position: fixed;
210
228
  inset-block: 0;
211
229
  inset-inline-start: 0;
212
- z-index: 200;
230
+ z-index: var(--component-side-nav-overlay-z-index);
213
231
  transform: translateX(-100%);
214
- transition: transform 250ms ease;
232
+ transition: transform var(--semantic-motion-duration-slow) var(--semantic-motion-easing-standard);
215
233
  will-change: transform;
216
234
  }
217
235
 
@@ -223,16 +241,16 @@
223
241
 
224
242
  .a1-side-nav--open {
225
243
  transform: translateX(0);
226
- box-shadow: 8px 0 40px rgba(0, 0, 0, 0.18);
244
+ box-shadow: var(--component-side-nav-overlay-shadow-start);
227
245
  }
228
246
 
229
247
  .a1-side-nav--placement-end.a1-side-nav--open {
230
- box-shadow: -8px 0 40px rgba(0, 0, 0, 0.18);
248
+ box-shadow: var(--component-side-nav-overlay-shadow-end);
231
249
  }
232
250
  }
233
251
 
234
252
  /* xs only (≤480px): full viewport width */
235
- @media (max-width: 480px) {
253
+ @media (--bp-xs) {
236
254
  .a1-side-nav {
237
255
  width: 100%;
238
256
  max-width: 100vw;
@@ -246,13 +264,21 @@
246
264
 
247
265
  /* ── Collapsed state (lg/xl only) ──────────────────────────────────────── */
248
266
 
249
- @media (min-width: 1025px) {
267
+ @media (--bp-lg-up) {
268
+ /*
269
+ * Sticky sidebar: the nav sticks to the viewport top and never scrolls with
270
+ * the page. Internal nav content scrolls via .a1-side-nav__nav (overflow-y:
271
+ * auto). The header row and footer remain anchored at all times.
272
+ */
250
273
  .a1-side-nav {
251
- transition: width 250ms ease;
274
+ position: sticky;
275
+ top: 0;
276
+ height: 100vh;
277
+ transition: width var(--semantic-motion-duration-slow) var(--semantic-motion-easing-standard);
252
278
  }
253
279
 
254
280
  .a1-side-nav--collapsed {
255
- width: var(--component-side-nav-collapsed-width, 52px);
281
+ width: var(--component-side-nav-collapsed-width);
256
282
  }
257
283
 
258
284
  /* Stack header content above the expand button when collapsed */
@@ -263,11 +289,23 @@
263
289
  gap: var(--base-spacing-4);
264
290
  }
265
291
 
266
- /* Hide footer in collapsed state */
267
- .a1-side-nav--collapsed .a1-side-nav__footer {
292
+ /* Hide footer in collapsed state unless it owns the collapse button */
293
+ .a1-side-nav--collapsed:not(.a1-side-nav--collapse-footer) .a1-side-nav__footer {
268
294
  display: none;
269
295
  }
270
296
 
297
+ .a1-side-nav--collapsed.a1-side-nav--collapse-footer .a1-side-nav__footer {
298
+ flex-direction: column;
299
+ align-items: center;
300
+ justify-content: center;
301
+ padding-inline: var(--base-spacing-4);
302
+ gap: var(--base-spacing-2);
303
+ }
304
+
305
+ .a1-side-nav--collapsed.a1-side-nav--collapse-footer .a1-side-nav__footer-content {
306
+ flex: none;
307
+ }
308
+
271
309
  /* Center items and remove indentation in collapsed state */
272
310
  .a1-side-nav--collapsed .a1-side-nav-item {
273
311
  justify-content: center;
@@ -0,0 +1,56 @@
1
+ import "./snackbar.css";
2
+ import { Button } from "../button/Button.jsx";
3
+ import { IconButton } from "../icon-button/IconButton.jsx";
4
+
5
+ const variants = ["default", "success", "info", "warn", "error"];
6
+ const positions = ["bottom", "bottom-left", "bottom-right", "top", "top-left", "top-right"];
7
+
8
+ export function Snackbar({
9
+ open = false,
10
+ children,
11
+ actionLabel,
12
+ onAction,
13
+ onClose,
14
+ variant = "default",
15
+ position = "bottom",
16
+ inverse = true,
17
+ role,
18
+ className = "",
19
+ ...props
20
+ }) {
21
+ if (!open) return null;
22
+
23
+ const resolvedVariant = variants.includes(variant) ? variant : "default";
24
+ const resolvedPosition = positions.includes(position) ? position : "bottom";
25
+ const classes = [
26
+ "a1-snackbar",
27
+ inverse && "a1-inverse",
28
+ `a1-snackbar--${resolvedVariant}`,
29
+ `a1-snackbar--${resolvedPosition}`,
30
+ className,
31
+ ].filter(Boolean).join(" ");
32
+
33
+ return (
34
+ <div
35
+ className={classes}
36
+ role={role ?? (resolvedVariant === "error" ? "alert" : "status")}
37
+ aria-live={resolvedVariant === "error" ? "assertive" : "polite"}
38
+ {...props}
39
+ >
40
+ <div className="a1-snackbar__content">{children}</div>
41
+ {(actionLabel && onAction) && (
42
+ <Button size="sm" variant="tertiary" onClick={onAction}>
43
+ {actionLabel}
44
+ </Button>
45
+ )}
46
+ {onClose && (
47
+ <IconButton
48
+ icon="close"
49
+ label="Dismiss"
50
+ variant="tertiary"
51
+ onClick={onClose}
52
+ />
53
+ )}
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,113 @@
1
+ @keyframes a1-snackbar-in {
2
+ from {
3
+ opacity: 0;
4
+ translate: 0 8px;
5
+ }
6
+ to {
7
+ opacity: 1;
8
+ translate: 0 0;
9
+ }
10
+ }
11
+
12
+ .a1-snackbar {
13
+ position: fixed;
14
+ z-index: var(--component-snackbar-z-index, 1100);
15
+ box-sizing: border-box;
16
+ width: min(420px, calc(100vw - var(--base-spacing-32)));
17
+ min-height: 56px;
18
+ padding: var(--base-spacing-12) var(--base-spacing-24);
19
+ border: 1px solid var(--a1-snackbar-border);
20
+ border-radius: var(--component-card-border-radius);
21
+ background: var(--a1-snackbar-background);
22
+ color: var(--a1-snackbar-foreground);
23
+ box-shadow: var(--semantic-shadow-lg, 0 16px 40px rgba(0, 0, 0, 0.18));
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--base-spacing-8);
27
+ animation: a1-snackbar-in var(--semantic-motion-duration-standard, 200ms) var(--semantic-motion-easing-standard, ease-out) both;
28
+ }
29
+
30
+ @media (prefers-reduced-motion: reduce) {
31
+ .a1-snackbar {
32
+ animation: none;
33
+ }
34
+ }
35
+
36
+ .a1-snackbar--bottom,
37
+ .a1-snackbar--top {
38
+ left: 50%;
39
+ transform: translateX(-50%);
40
+ }
41
+
42
+ .a1-snackbar--bottom,
43
+ .a1-snackbar--bottom-left,
44
+ .a1-snackbar--bottom-right {
45
+ bottom: var(--base-spacing-24);
46
+ }
47
+
48
+ .a1-snackbar--top,
49
+ .a1-snackbar--top-left,
50
+ .a1-snackbar--top-right {
51
+ top: var(--base-spacing-24);
52
+ }
53
+
54
+ .a1-snackbar--bottom-left,
55
+ .a1-snackbar--top-left {
56
+ left: var(--base-spacing-24);
57
+ }
58
+
59
+ .a1-snackbar--bottom-right,
60
+ .a1-snackbar--top-right {
61
+ right: var(--base-spacing-24);
62
+ }
63
+
64
+ .a1-snackbar__content {
65
+ min-width: 0;
66
+ flex: 1;
67
+ }
68
+
69
+ .a1-snackbar--default,
70
+ .a1-snackbar--info {
71
+ --a1-snackbar-background: var(--semantic-color-surface-inverse, var(--base-color-neutral-900));
72
+ --a1-snackbar-border: var(--semantic-color-surface-inverse, var(--base-color-neutral-900));
73
+ --a1-snackbar-foreground: var(--semantic-color-text-default, var(--base-color-neutral-0));
74
+ }
75
+
76
+ .a1-snackbar--success {
77
+ --a1-snackbar-background: var(--semantic-color-status-success-background);
78
+ --a1-snackbar-border: var(--semantic-color-status-success-border, var(--semantic-color-status-success-background));
79
+ --a1-snackbar-foreground: var(--semantic-color-status-success-foreground);
80
+ }
81
+
82
+ .a1-snackbar--warn {
83
+ --a1-snackbar-background: var(--semantic-color-status-warn-background);
84
+ --a1-snackbar-border: var(--semantic-color-status-warn-border, var(--semantic-color-status-warn-background));
85
+ --a1-snackbar-foreground: var(--semantic-color-status-warn-foreground);
86
+ }
87
+
88
+ .a1-snackbar--error {
89
+ --a1-snackbar-background: var(--semantic-color-status-error-background);
90
+ --a1-snackbar-border: var(--semantic-color-status-error-border, var(--semantic-color-status-error-background));
91
+ --a1-snackbar-foreground: var(--semantic-color-status-error-foreground);
92
+ }
93
+
94
+ @media (max-width: 720px) {
95
+ .a1-snackbar {
96
+ right: var(--base-spacing-16);
97
+ left: var(--base-spacing-16);
98
+ width: auto;
99
+ transform: none;
100
+ }
101
+
102
+ .a1-snackbar--bottom,
103
+ .a1-snackbar--bottom-left,
104
+ .a1-snackbar--bottom-right {
105
+ bottom: var(--base-spacing-16);
106
+ }
107
+
108
+ .a1-snackbar--top,
109
+ .a1-snackbar--top-left,
110
+ .a1-snackbar--top-right {
111
+ top: var(--base-spacing-16);
112
+ }
113
+ }
@@ -0,0 +1,36 @@
1
+ import "./spacer.css";
2
+
3
+ const sizes = ["xs", "sm", "md", "lg", "xl", "xxl"];
4
+ const breakpoints = ["xs", "sm", "md", "lg", "xl"];
5
+
6
+ const sizeMap = {
7
+ xs: "var(--base-spacing-8)",
8
+ sm: "var(--base-spacing-16)",
9
+ md: "var(--base-spacing-24)",
10
+ lg: "var(--base-spacing-40)",
11
+ xl: "var(--base-spacing-64)",
12
+ xxl: "var(--base-spacing-96)",
13
+ };
14
+
15
+ function isResponsive(value) {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+
19
+ function resolveBase(size) {
20
+ if (!isResponsive(size)) return sizeMap[size] ?? sizeMap.md;
21
+ return sizeMap[size.xs] ?? sizeMap.md;
22
+ }
23
+
24
+ export function Spacer({ size = "md", ...props }) {
25
+ const style = { "--a1-spacer-size": resolveBase(size) };
26
+
27
+ if (isResponsive(size)) {
28
+ let lastKnown = resolveBase(size);
29
+ breakpoints.slice(1).forEach((bp) => {
30
+ if (sizes.includes(size[bp])) lastKnown = sizeMap[size[bp]];
31
+ style[`--a1-spacer-size-${bp}`] = lastKnown;
32
+ });
33
+ }
34
+
35
+ return <div className="a1-spacer" role="none" aria-hidden="true" style={style} {...props} />;
36
+ }
@@ -0,0 +1,44 @@
1
+ /* ══════════════════════════════════════════════════════════════════════════
2
+ Spacer
3
+ ══════════════════════════════════════════════════════════════════════════ */
4
+
5
+ .a1-spacer {
6
+ display: block;
7
+ height: var(--a1-spacer-responsive-size, var(--a1-spacer-size, 0px));
8
+ }
9
+
10
+ @media (--bp-sm-up) {
11
+ .a1-spacer {
12
+ --a1-spacer-responsive-size: var(--a1-spacer-size-sm, var(--a1-spacer-size));
13
+ }
14
+ }
15
+
16
+ @media (--bp-md-up) {
17
+ .a1-spacer {
18
+ --a1-spacer-responsive-size: var(
19
+ --a1-spacer-size-md,
20
+ var(--a1-spacer-size-sm, var(--a1-spacer-size))
21
+ );
22
+ }
23
+ }
24
+
25
+ @media (--bp-lg-up) {
26
+ .a1-spacer {
27
+ --a1-spacer-responsive-size: var(
28
+ --a1-spacer-size-lg,
29
+ var(--a1-spacer-size-md, var(--a1-spacer-size-sm, var(--a1-spacer-size)))
30
+ );
31
+ }
32
+ }
33
+
34
+ @media (--bp-xl) {
35
+ .a1-spacer {
36
+ --a1-spacer-responsive-size: var(
37
+ --a1-spacer-size-xl,
38
+ var(
39
+ --a1-spacer-size-lg,
40
+ var(--a1-spacer-size-md, var(--a1-spacer-size-sm, var(--a1-spacer-size)))
41
+ )
42
+ );
43
+ }
44
+ }