@gtivr4/a1-design-system-react 0.3.0 → 0.4.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 (30) hide show
  1. package/package.json +1 -1
  2. package/src/color-scheme.css +10 -0
  3. package/src/components/choice-group/ChoiceGroup.jsx +2 -0
  4. package/src/components/choice-group/choice-group.css +11 -0
  5. package/src/components/data-table/DataTable.jsx +5 -5
  6. package/src/components/data-table/data-table-filters.css +2 -2
  7. package/src/components/data-table/data-table.css +2 -2
  8. package/src/components/field/SelectField.jsx +1 -1
  9. package/src/components/field/TextField.jsx +1 -1
  10. package/src/components/field/TextareaField.jsx +1 -1
  11. package/src/components/field/field.css +8 -8
  12. package/src/components/field-row/FieldRow.jsx +2 -2
  13. package/src/components/fieldset/Fieldset.jsx +1 -1
  14. package/src/components/grid/Grid.jsx +1 -0
  15. package/src/components/heading/Heading.jsx +1 -1
  16. package/src/components/heading/heading.css +2 -0
  17. package/src/components/notification/Notification.jsx +4 -4
  18. package/src/components/notification/notification.css +1 -1
  19. package/src/components/paragraph/Paragraph.jsx +1 -1
  20. package/src/components/paragraph/paragraph.css +2 -0
  21. package/src/components/section/Section.jsx +9 -9
  22. package/src/components/section/section.css +14 -2
  23. package/src/components/segmented-control/segmented.css +0 -1
  24. package/src/components/status-bar/StatusBar.jsx +92 -0
  25. package/src/components/status-bar/status-bar.css +146 -0
  26. package/src/components/token-select/token-select.css +1 -1
  27. package/src/components/top-header/top-header.css +4 -4
  28. package/src/index.js +1 -0
  29. package/src/themes.css +1 -0
  30. package/src/tokens.css +336 -326
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtivr4/a1-design-system-react",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React components for the A1 token-driven design system.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -9,6 +9,16 @@
9
9
  box-sizing: border-box;
10
10
  }
11
11
 
12
+ /* ─── Root typeface ──────────────────────────────────────────────────────────
13
+ Apply the design system body font to the document so unscoped elements
14
+ inherit the correct typeface without requiring any A1 component to be
15
+ rendered first. Without this, the browser default (serif) is used until a
16
+ component's own CSS loads. */
17
+
18
+ body {
19
+ font-family: var(--component-paragraph-font-family);
20
+ }
21
+
12
22
  /* ─── Form field interaction tokens — light mode defaults ────────────────────
13
23
  Single source of truth for hover, active, and read-only states across all
14
24
  form field components. Overridden in every dark-mode context below.
@@ -16,6 +16,7 @@ export function ChoiceGroup({
16
16
  size = "default",
17
17
  columns,
18
18
  multiple = false,
19
+ inlineIcon = false,
19
20
  required = false,
20
21
  name,
21
22
  options = [],
@@ -76,6 +77,7 @@ export function ChoiceGroup({
76
77
  "a1-choice-group",
77
78
  resolvedSize !== "default" && `a1-choice-group--${resolvedSize}`,
78
79
  multiple ? "a1-choice-group--multiple" : "a1-choice-group--single",
80
+ inlineIcon && "a1-choice-group--inline-icon",
79
81
  isFixedColumns && "a1-choice-group--fixed-columns",
80
82
  responsiveClass,
81
83
  error && "a1-choice-group--error",
@@ -317,6 +317,17 @@
317
317
  background-size: 80%;
318
318
  }
319
319
 
320
+ /* ─── Inline icon layout ────────────────────────────────────────────────────── */
321
+
322
+ .a1-choice-group--inline-icon .a1-choice-item {
323
+ flex-direction: row;
324
+ align-items: center;
325
+ }
326
+
327
+ .a1-choice-group--inline-icon .a1-choice-item__icon {
328
+ flex-shrink: 0;
329
+ }
330
+
320
331
  /* ─── Error state ───────────────────────────────────────────────────────────── */
321
332
 
322
333
  .a1-choice-group--error
@@ -22,8 +22,8 @@ import "./data-table.css";
22
22
  * }>
23
23
  * rows: Array<Record<string, any>>
24
24
  * getRowId?: (row: Record<string, any>, index: number) => string | number
25
- * density?: "auto" | "comfortable" | "default" | "compact"
26
- * "auto" (default) — switches between densities based on available width and column definitions
25
+ * size?: "comfortable" | "default" | "compact"
26
+ * omit (default) — switches between densities automatically based on available container width
27
27
  */
28
28
 
29
29
  // Estimated minimum content width per column type at a "neutral" padding level
@@ -193,7 +193,7 @@ function SelectionCheckbox({ checked, indeterminate = false, label, onChange })
193
193
  export function DataTable({
194
194
  columns = [],
195
195
  rows = [],
196
- density = "default",
196
+ size,
197
197
  zebra = false,
198
198
  scrollable = false,
199
199
  caption,
@@ -238,14 +238,14 @@ export function DataTable({
238
238
  const [internalSearchColumn, setInternalSearchColumn] = useState(defaultSearchColumn);
239
239
  const [internalSelectedRowIds, setInternalSelectedRowIds] = useState(() => normalizeRowIds(defaultSelectedRowIds));
240
240
 
241
- const isAuto = density === "auto";
241
+ const isAuto = size === undefined;
242
242
  const isSortControlled = sort !== undefined;
243
243
  const isPageControlled = page !== undefined;
244
244
  const isFilterControlled = filterValue !== undefined;
245
245
  const isSearchControlled = searchValue !== undefined;
246
246
  const isSearchColumnControlled = searchColumn !== undefined;
247
247
  const isSelectionControlled = selectedRowIds !== undefined;
248
- const activeDensity = isAuto ? autoDensity : density;
248
+ const activeDensity = isAuto ? autoDensity : size;
249
249
  const activeSort = isSortControlled ? normalizeSort(sort) : internalSort;
250
250
  const activePage = isPageControlled ? page : internalPage;
251
251
  const activeFilterValue = isFilterControlled
@@ -104,7 +104,7 @@
104
104
  }
105
105
 
106
106
  .a1-dt-filters__chip-icon {
107
- font-size: 18px;
107
+ font-size: var(--component-menu-item-icon-size);
108
108
  flex-shrink: 0;
109
109
  margin-inline-start: -2px;
110
110
  }
@@ -134,7 +134,7 @@
134
134
  border-radius: 9999px;
135
135
  background: var(--semantic-color-action-background);
136
136
  color: var(--semantic-color-action-foreground);
137
- font-size: 11px;
137
+ font-size: var(--component-tab-count-font-size);
138
138
  font-weight: 600;
139
139
  line-height: 1;
140
140
  margin-inline-start: var(--base-spacing-4);
@@ -263,7 +263,7 @@
263
263
  border-radius: 9999px;
264
264
  background: var(--semantic-color-action-background);
265
265
  color: var(--semantic-color-action-foreground);
266
- font-size: 10px;
266
+ font-size: var(--semantic-font-size-body-xs);
267
267
  font-weight: 600;
268
268
  letter-spacing: 0.02em;
269
269
  user-select: none;
@@ -272,7 +272,7 @@
272
272
  .a1-data-table--compact .a1-data-table__avatar {
273
273
  width: 22px;
274
274
  height: 22px;
275
- font-size: 9px;
275
+ font-size: var(--component-notification-font-size);
276
276
  }
277
277
 
278
278
  .a1-data-table__actions {
@@ -6,7 +6,7 @@ import { FieldsetContext } from "../fieldset/FieldsetContext.js";
6
6
  import "./field.css";
7
7
 
8
8
  const SIZES = ["comfortable", "default", "compact"];
9
- const LABEL_POSITIONS = ["above", "side"];
9
+ const LABEL_POSITIONS = ["above", "before"];
10
10
 
11
11
  export const SelectField = forwardRef(function SelectField({
12
12
  label,
@@ -5,7 +5,7 @@ import { FieldsetContext } from "../fieldset/FieldsetContext.js";
5
5
  import "./field.css";
6
6
 
7
7
  const SIZES = ["comfortable", "default", "compact"];
8
- const LABEL_POSITIONS = ["above", "side"];
8
+ const LABEL_POSITIONS = ["above", "before"];
9
9
 
10
10
  export const TextField = forwardRef(function TextField({
11
11
  label,
@@ -6,7 +6,7 @@ import "./field.css";
6
6
  import "./textarea-field.css";
7
7
 
8
8
  const SIZES = ["comfortable", "default", "compact"];
9
- const LABEL_POSITIONS = ["above", "side"];
9
+ const LABEL_POSITIONS = ["above", "before"];
10
10
  const ROW_SIZES = { sm: 2, md: 4, lg: 8, xl: 12 };
11
11
 
12
12
  function resolveRows(rows) {
@@ -336,9 +336,9 @@
336
336
  color: var(--semantic-color-status-error-background);
337
337
  }
338
338
 
339
- /* ─── Side label layout ────────────────────────────────────────────────────── */
339
+ /* ─── Before label layout ───────────────────────────────────────────────────── */
340
340
 
341
- .a1-field--label-side {
341
+ .a1-field--label-before {
342
342
  display: grid;
343
343
  grid-template-columns: var(--a1-field-side-label-width) 1fr;
344
344
  column-gap: var(--base-spacing-16);
@@ -346,12 +346,12 @@
346
346
  align-items: start;
347
347
  }
348
348
 
349
- .a1-field--label-side .a1-field__label {
349
+ .a1-field--label-before .a1-field__label {
350
350
  padding-top: calc((var(--a1-field-height) - 1lh) / 2);
351
351
  padding-bottom: 0;
352
352
  }
353
353
 
354
- .a1-field--label-side .a1-field__message {
354
+ .a1-field--label-before .a1-field__message {
355
355
  grid-column: 1;
356
356
  grid-row: 2;
357
357
  /* Fine-tuned margin: overrides the base margin-top added for the above-label
@@ -359,22 +359,22 @@
359
359
  margin-top: calc((var(--a1-field-label-size) * 1.5 - var(--a1-field-height)) / 2 + var(--base-spacing-2));
360
360
  }
361
361
 
362
- /* ─── Side label → stacked on xs/sm ───────────────────────────────────────── */
362
+ /* ─── Before label → stacked on xs/sm ─────────────────────────────────────── */
363
363
 
364
364
  @media (--bp-sm-down) {
365
- .a1-field--label-side {
365
+ .a1-field--label-before {
366
366
  display: flex;
367
367
  flex-direction: column;
368
368
  gap: var(--a1-field-gap); /* restore: grid overrode row-gap to base-spacing-4 */
369
369
  align-items: stretch; /* restore: grid set align-items: start */
370
370
  }
371
371
 
372
- .a1-field--label-side .a1-field__label {
372
+ .a1-field--label-before .a1-field__label {
373
373
  padding-top: 0;
374
374
  padding-bottom: var(--a1-field-gap);
375
375
  }
376
376
 
377
- .a1-field--label-side .a1-field__message {
377
+ .a1-field--label-before .a1-field__message {
378
378
  margin-top: var(--a1-field-gap);
379
379
  }
380
380
  }
@@ -5,9 +5,9 @@ import "./field-row.css";
5
5
  export function FieldRow({ children, className = "", ...props }) {
6
6
  const ctx = useContext(FieldsetContext);
7
7
 
8
- // Side-label fields already use an internal two-column grid;
8
+ // Before-label fields already use an internal two-column grid;
9
9
  // stacking the row prevents layout conflicts.
10
- const stacked = ctx?.labelPosition === "side";
10
+ const stacked = ctx?.labelPosition === "before";
11
11
 
12
12
  const classes = [
13
13
  "a1-field-row",
@@ -2,7 +2,7 @@ import { FieldsetContext } from "./FieldsetContext.js";
2
2
  import "./fieldset.css";
3
3
 
4
4
  const SIZES = ["comfortable", "default", "compact"];
5
- const LABEL_POSITIONS = ["above", "side"];
5
+ const LABEL_POSITIONS = ["above", "before"];
6
6
 
7
7
  export function Fieldset({
8
8
  legend,
@@ -2,6 +2,7 @@ import "./grid.css";
2
2
 
3
3
  const SPACING_KEYS = [1, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 64, 96, 128];
4
4
  const gapSizes = {
5
+ xs: "var(--base-spacing-4)",
5
6
  sm: "var(--base-spacing-8)",
6
7
  md: "var(--base-spacing-16)",
7
8
  lg: "var(--base-spacing-24)",
@@ -7,7 +7,7 @@ const margins = ["sm", "md", "lg"];
7
7
  const levels = ["h1", "h2", "h3", "h4", "h5", "h6"];
8
8
  const breakpoints = ["xs", "sm", "md", "lg", "xl"];
9
9
  const textWraps = ["balance"];
10
- const aligns = ["left", "center", "right"];
10
+ const aligns = ["left", "center", "right", "start", "end"];
11
11
 
12
12
  const levelDefaults = { h1: "xl", h2: "lg", h3: "md", h4: "sm", h5: "xs", h6: "xs" };
13
13
 
@@ -88,6 +88,8 @@
88
88
  .a1-heading--align-left { text-align: start; }
89
89
  .a1-heading--align-center { text-align: center; }
90
90
  .a1-heading--align-right { text-align: end; }
91
+ .a1-heading--align-start { text-align: start; }
92
+ .a1-heading--align-end { text-align: end; }
91
93
 
92
94
  /* Expressive marks */
93
95
  .a1-heading-mark {
@@ -1,6 +1,6 @@
1
1
  import "./notification.css";
2
2
 
3
- const variants = ["default", "error", "success", "warn", "info"];
3
+ const statuses = ["neutral", "error", "success", "warn", "info"];
4
4
  const positions = ["top-right", "top-left", "bottom-right", "bottom-left"];
5
5
 
6
6
  function formatCount(n, max) {
@@ -15,11 +15,11 @@ export function Notification({
15
15
  count,
16
16
  label,
17
17
  dot = false,
18
- variant = "default",
18
+ status = "neutral",
19
19
  position = "top-right",
20
20
  max = 99,
21
21
  }) {
22
- const resolvedVariant = variants.includes(variant) ? variant : "default";
22
+ const resolvedStatus = statuses.includes(status) ? status : "neutral";
23
23
  const resolvedPosition = positions.includes(position) ? position : "top-right";
24
24
 
25
25
  const isDot = dot || (count === undefined && label === undefined);
@@ -35,7 +35,7 @@ export function Notification({
35
35
 
36
36
  const classes = [
37
37
  "a1-notification",
38
- `a1-notification--${resolvedVariant}`,
38
+ `a1-notification--${resolvedStatus}`,
39
39
  `a1-notification--${resolvedPosition}`,
40
40
  isDot && "a1-notification--dot",
41
41
  ]
@@ -43,7 +43,7 @@
43
43
 
44
44
  /* ── Variants ───────────────────────────────────────────────────────────── */
45
45
 
46
- .a1-notification--default {
46
+ .a1-notification--neutral {
47
47
  --a1-notification-background: var(--base-color-neutral-600);
48
48
  --a1-notification-foreground: var(--base-color-neutral-0);
49
49
  }
@@ -4,7 +4,7 @@ const sizes = ["xs", "sm", "md", "lg", "xl"];
4
4
  const colors = ["default", "muted"];
5
5
  const breakpoints = ["xs", "sm", "md", "lg", "xl"];
6
6
  const textWraps = ["balance"];
7
- const aligns = ["left", "center", "right"];
7
+ const aligns = ["left", "center", "right", "start", "end"];
8
8
 
9
9
  function isResponsiveSize(size) {
10
10
  return size && typeof size === "object" && !Array.isArray(size);
@@ -46,6 +46,8 @@
46
46
  .a1-paragraph--align-left { text-align: start; }
47
47
  .a1-paragraph--align-center { text-align: center; }
48
48
  .a1-paragraph--align-right { text-align: end; }
49
+ .a1-paragraph--align-start { text-align: start; }
50
+ .a1-paragraph--align-end { text-align: end; }
49
51
 
50
52
  .a1-paragraph + .a1-paragraph,
51
53
  .a1-paragraph + .a1-heading {
@@ -2,7 +2,7 @@ import "../../themes.css";
2
2
  import "../../color-scheme.css";
3
3
  import "./section.css";
4
4
 
5
- const VALID_PADDING = ["lg", "md", "sm", "none"];
5
+ const VALID_PADDING = ["lg", "md", "sm", "xs", "none"];
6
6
  const VALID_SURFACES = ["page", "panel", "raised"];
7
7
  const VALID_GAPS = ["xs", "sm", "md", "lg"];
8
8
  const VALID_GRADIENTS = ["accent", "highlight", "info", "success", "warn"];
@@ -31,7 +31,7 @@ export function Section({
31
31
  inverse = false,
32
32
  contentWidth,
33
33
  height,
34
- alignment,
34
+ align,
35
35
  className = "",
36
36
  children,
37
37
  ...props
@@ -72,14 +72,14 @@ export function Section({
72
72
  classes.push(`a1-section--height-${height}`);
73
73
  }
74
74
 
75
- if (typeof alignment === "string") {
76
- if (VALID_ALIGNMENTS.includes(alignment)) {
77
- classes.push(`a1-section--align-${alignment}`);
75
+ if (typeof align === "string") {
76
+ if (VALID_ALIGNMENTS.includes(align)) {
77
+ classes.push(`a1-section--align-${align}`);
78
78
  }
79
- } else if (alignment && typeof alignment === "object") {
80
- for (const [bp, align] of Object.entries(alignment)) {
81
- if (VALID_ALIGNMENTS.includes(align)) {
82
- classes.push(`a1-section--${bp}-align-${align}`);
79
+ } else if (align && typeof align === "object") {
80
+ for (const [bp, alignVal] of Object.entries(align)) {
81
+ if (VALID_ALIGNMENTS.includes(alignVal)) {
82
+ classes.push(`a1-section--${bp}-align-${alignVal}`);
83
83
  }
84
84
  }
85
85
  }
@@ -111,7 +111,7 @@
111
111
  /* ── Alignment ─────────────────────────────────────────────────────────────── */
112
112
  /*
113
113
  * Aligns direct children as layout items.
114
- * Use the object prop for responsive changes: alignment={{ xs: "center", lg: "left" }}
114
+ * Use the object prop for responsive changes: align={{ xs: "center", lg: "left" }}
115
115
  */
116
116
 
117
117
  .a1-section[class*="-align-"] {
@@ -206,10 +206,11 @@
206
206
 
207
207
  /* ── Padding — static (with built-in responsive scaling) ───────────────────── */
208
208
  /*
209
- * Three tiers scale across three breakpoints:
209
+ * Four tiers scale across three breakpoints:
210
210
  * lg 96/64 → 96/40 at ≤1024 → 64/24 at ≤640
211
211
  * md 64/40 → 40/24 at ≤1024 → 32/16 at ≤640
212
212
  * sm 32/24 → 24/16 at ≤1024 → 16/12 at ≤640
213
+ * xs 16/16 → 12/12 at ≤1024 → 8/8 at ≤640
213
214
  * Block (top/bottom) / Inline (left/right)
214
215
  */
215
216
 
@@ -217,6 +218,11 @@
217
218
  padding: 0;
218
219
  }
219
220
 
221
+ .a1-section--padding-xs {
222
+ padding-block: var(--base-spacing-16);
223
+ padding-inline: var(--base-spacing-16);
224
+ }
225
+
220
226
  .a1-section--padding-sm {
221
227
  padding-block: var(--base-spacing-32);
222
228
  padding-inline: var(--base-spacing-24);
@@ -233,12 +239,14 @@
233
239
  }
234
240
 
235
241
  @media (--bp-md-down) {
242
+ .a1-section--padding-xs { padding-block: var(--base-spacing-12); padding-inline: var(--base-spacing-12); }
236
243
  .a1-section--padding-sm { padding-block: var(--base-spacing-24); padding-inline: var(--base-spacing-16); }
237
244
  .a1-section--padding-md { padding-block: var(--base-spacing-40); padding-inline: var(--base-spacing-24); }
238
245
  .a1-section--padding-lg { padding-block: var(--base-spacing-64); padding-inline: var(--base-spacing-40); }
239
246
  }
240
247
 
241
248
  @media (--bp-sm-down) {
249
+ .a1-section--padding-xs { padding-block: var(--base-spacing-8); padding-inline: var(--base-spacing-8); }
242
250
  .a1-section--padding-sm { padding-block: var(--base-spacing-16); padding-inline: var(--base-spacing-12); }
243
251
  .a1-section--padding-md { padding-block: var(--base-spacing-24); padding-inline: var(--base-spacing-16); }
244
252
  .a1-section--padding-lg { padding-block: var(--base-spacing-40); padding-inline: var(--base-spacing-24); }
@@ -251,6 +259,7 @@
251
259
 
252
260
  /* xs: base, no media query */
253
261
  .a1-section--xs-padding-none { padding: 0; }
262
+ .a1-section--xs-padding-xs { padding-block: var(--base-spacing-16); padding-inline: var(--base-spacing-16); }
254
263
  .a1-section--xs-padding-sm { padding-block: var(--base-spacing-32); padding-inline: var(--base-spacing-24); }
255
264
  .a1-section--xs-padding-md { padding-block: var(--base-spacing-64); padding-inline: var(--base-spacing-40); }
256
265
  .a1-section--xs-padding-lg { padding-block: var(--base-spacing-96); padding-inline: var(--base-spacing-64); }
@@ -258,6 +267,7 @@
258
267
  /* sm: ≥481px */
259
268
  @media (--bp-sm-up) {
260
269
  .a1-section--sm-padding-none { padding: 0; }
270
+ .a1-section--sm-padding-xs { padding-block: var(--base-spacing-16); padding-inline: var(--base-spacing-16); }
261
271
  .a1-section--sm-padding-sm { padding-block: var(--base-spacing-32); padding-inline: var(--base-spacing-24); }
262
272
  .a1-section--sm-padding-md { padding-block: var(--base-spacing-64); padding-inline: var(--base-spacing-40); }
263
273
  .a1-section--sm-padding-lg { padding-block: var(--base-spacing-96); padding-inline: var(--base-spacing-64); }
@@ -266,6 +276,7 @@
266
276
  /* md: ≥769px */
267
277
  @media (min-width: 769px) {
268
278
  .a1-section--md-padding-none { padding: 0; }
279
+ .a1-section--md-padding-xs { padding-block: var(--base-spacing-12); padding-inline: var(--base-spacing-12); }
269
280
  .a1-section--md-padding-sm { padding-block: var(--base-spacing-24); padding-inline: var(--base-spacing-16); }
270
281
  .a1-section--md-padding-md { padding-block: var(--base-spacing-40); padding-inline: var(--base-spacing-24); }
271
282
  .a1-section--md-padding-lg { padding-block: var(--base-spacing-64); padding-inline: var(--base-spacing-40); }
@@ -274,6 +285,7 @@
274
285
  /* lg: ≥1025px */
275
286
  @media (--bp-lg-up) {
276
287
  .a1-section--lg-padding-none { padding: 0; }
288
+ .a1-section--lg-padding-xs { padding-block: var(--base-spacing-16); padding-inline: var(--base-spacing-16); }
277
289
  .a1-section--lg-padding-sm { padding-block: var(--base-spacing-32); padding-inline: var(--base-spacing-24); }
278
290
  .a1-section--lg-padding-md { padding-block: var(--base-spacing-64); padding-inline: var(--base-spacing-40); }
279
291
  .a1-section--lg-padding-lg { padding-block: var(--base-spacing-96); padding-inline: var(--base-spacing-64); }
@@ -42,7 +42,6 @@
42
42
  .a1-segment[aria-checked="true"] {
43
43
  background: var(--semantic-color-surface-page);
44
44
  color: var(--semantic-color-text-default);
45
- font-weight: var(--component-segmented-font-weight-active);
46
45
  box-shadow: var(--semantic-shadow-xs);
47
46
  }
48
47
 
@@ -0,0 +1,92 @@
1
+ import { useId, useState, useEffect } from "react";
2
+ import { Button } from "../button/Button.jsx";
3
+ import { useLabel } from "../labels/Labels.jsx";
4
+ import "./status-bar.css";
5
+
6
+ const SIZES = ["sm", "md", "lg"];
7
+ const POSITIONS = ["above", "below", "before", "after"];
8
+
9
+ export function StatusBar({
10
+ value = 0,
11
+ max = 100,
12
+ label,
13
+ labelPosition = "above",
14
+ size = "md",
15
+ indeterminate = false,
16
+ className = "",
17
+ ...props
18
+ }) {
19
+ const labelId = useId();
20
+
21
+ const pauseLabel = useLabel("statusBar.pause", "Pause");
22
+ const playLabel = useLabel("statusBar.play", "Play");
23
+
24
+ const [paused, setPaused] = useState(false);
25
+ const [showPause, setShowPause] = useState(false);
26
+
27
+ useEffect(() => {
28
+ if (!indeterminate) {
29
+ setShowPause(false);
30
+ setPaused(false);
31
+ return;
32
+ }
33
+ const t = setTimeout(() => setShowPause(true), 3000);
34
+ return () => clearTimeout(t);
35
+ }, [indeterminate]);
36
+
37
+ const resolvedSize = SIZES.includes(size) ? size : "md";
38
+ const resolvedPosition = POSITIONS.includes(labelPosition) ? labelPosition : "above";
39
+
40
+ const pct = Math.min(100, Math.max(0, max > 0 ? (value / max) * 100 : 0));
41
+ const isLabelFirst = resolvedPosition === "above" || resolvedPosition === "before";
42
+
43
+ const classes = [
44
+ "a1-status-bar",
45
+ resolvedSize !== "md" && `a1-status-bar--${resolvedSize}`,
46
+ `a1-status-bar--${resolvedPosition}`,
47
+ indeterminate && "a1-status-bar--indeterminate",
48
+ indeterminate && paused && "a1-status-bar--paused",
49
+ className,
50
+ ].filter(Boolean).join(" ");
51
+
52
+ const labelEl = label ? (
53
+ <span id={labelId} className="a1-status-bar__label">{label}</span>
54
+ ) : null;
55
+
56
+ return (
57
+ <div className={classes}>
58
+ {isLabelFirst && labelEl}
59
+ <div className="a1-status-bar__bar-row">
60
+ <div
61
+ className="a1-status-bar__track"
62
+ role="progressbar"
63
+ aria-valuenow={indeterminate ? undefined : value}
64
+ aria-valuemin={0}
65
+ aria-valuemax={max}
66
+ aria-labelledby={label ? labelId : undefined}
67
+ {...props}
68
+ >
69
+ <div
70
+ className={[
71
+ "a1-status-bar__fill",
72
+ indeterminate && "a1-status-bar__fill--indeterminate",
73
+ ].filter(Boolean).join(" ")}
74
+ style={indeterminate ? undefined : { inlineSize: `${pct}%` }}
75
+ />
76
+ </div>
77
+ {showPause && (
78
+ <Button
79
+ size="sm"
80
+ variant="secondary"
81
+ icon={paused ? "play_arrow" : "pause"}
82
+ className="a1-status-bar__pause"
83
+ onClick={() => setPaused(p => !p)}
84
+ >
85
+ {paused ? playLabel : pauseLabel}
86
+ </Button>
87
+ )}
88
+ </div>
89
+ {!isLabelFirst && labelEl}
90
+ </div>
91
+ );
92
+ }