@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
@@ -0,0 +1,147 @@
1
+ import { useId, useState, forwardRef, 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 "./field.css";
6
+ import "./textarea-field.css";
7
+
8
+ const SIZES = ["comfortable", "default", "compact"];
9
+ const LABEL_POSITIONS = ["above", "side"];
10
+ const ROW_SIZES = { sm: 2, md: 4, lg: 8, xl: 12 };
11
+
12
+ function resolveRows(rows) {
13
+ if (typeof rows === "number") return rows;
14
+ return ROW_SIZES[rows] ?? 4;
15
+ }
16
+
17
+ export const TextareaField = forwardRef(function TextareaField({
18
+ label,
19
+ hint,
20
+ error,
21
+ size,
22
+ labelPosition,
23
+ required = false,
24
+ disabled = false,
25
+ readOnly = false,
26
+ id: providedId,
27
+ className = "",
28
+ rows = "md",
29
+ maxLength,
30
+ showCount = false,
31
+ value,
32
+ defaultValue,
33
+ onChange,
34
+ ...props
35
+ }, ref) {
36
+ const ctx = useContext(FieldsetContext);
37
+ const autoId = useId();
38
+ const id = providedId ?? autoId;
39
+ const hintId = `${id}-hint`;
40
+ const errorId = `${id}-error`;
41
+
42
+ const resolvedSize = SIZES.includes(size) ? size : (ctx?.size ?? "default");
43
+ const resolvedPosition = LABEL_POSITIONS.includes(labelPosition) ? labelPosition : (ctx?.labelPosition ?? "above");
44
+ const resolvedRows = resolveRows(rows);
45
+
46
+ const [internalCount, setInternalCount] = useState(() => {
47
+ if (value != null) return String(value).length;
48
+ if (defaultValue != null) return String(defaultValue).length;
49
+ return 0;
50
+ });
51
+
52
+ const count = value != null ? String(value).length : internalCount;
53
+ const showCharCount = showCount || maxLength != null;
54
+
55
+ function handleChange(e) {
56
+ if (value == null) setInternalCount(e.target.value.length);
57
+ onChange?.(e);
58
+ }
59
+
60
+ const countState = maxLength != null
61
+ ? count > maxLength ? "over"
62
+ : count / maxLength >= 0.8 ? "warning"
63
+ : "normal"
64
+ : "normal";
65
+
66
+ const classes = [
67
+ "a1-field",
68
+ `a1-field--${resolvedSize}`,
69
+ `a1-field--label-${resolvedPosition}`,
70
+ error && "a1-field--error",
71
+ required && "a1-field--required",
72
+ disabled && "a1-field--disabled",
73
+ readOnly && "a1-field--readonly",
74
+ className,
75
+ ].filter(Boolean).join(" ");
76
+
77
+ const describedBy = [error ? errorId : hint ? hintId : null]
78
+ .filter(Boolean).join(" ") || undefined;
79
+
80
+ const requiredText = useLabel("field.required", "Required");
81
+
82
+ const charCountEl = showCharCount ? (
83
+ <span
84
+ className={[
85
+ "a1-field__char-count",
86
+ countState === "warning" && "a1-field__char-count--warning",
87
+ countState === "over" && "a1-field__char-count--over",
88
+ ].filter(Boolean).join(" ")}
89
+ aria-live="polite"
90
+ aria-atomic="true"
91
+ aria-label={
92
+ maxLength != null
93
+ ? `${count} of ${maxLength} characters`
94
+ : `${count} characters`
95
+ }
96
+ >
97
+ {maxLength != null ? `${count} / ${maxLength}` : count}
98
+ </span>
99
+ ) : null;
100
+
101
+ const hasFooter = error || hint || showCharCount;
102
+
103
+ return (
104
+ <div className={classes}>
105
+ {label && (
106
+ <label className="a1-field__label" htmlFor={id}>
107
+ {label}
108
+ {required && resolvedSize === "comfortable" ? (
109
+ <MessageBadge status="info" subtle>{requiredText}</MessageBadge>
110
+ ) : required ? (
111
+ <span className="a1-field__asterisk" aria-hidden="true"> *</span>
112
+ ) : null}
113
+ </label>
114
+ )}
115
+ <div className="a1-field__control">
116
+ <textarea
117
+ ref={ref}
118
+ id={id}
119
+ className="a1-field__textarea"
120
+ rows={resolvedRows}
121
+ maxLength={maxLength}
122
+ required={required}
123
+ disabled={disabled}
124
+ readOnly={readOnly}
125
+ aria-describedby={describedBy}
126
+ aria-invalid={error ? "true" : undefined}
127
+ value={value}
128
+ defaultValue={defaultValue}
129
+ onChange={handleChange}
130
+ {...props}
131
+ />
132
+ </div>
133
+ {hasFooter && (
134
+ <div className="a1-field__footer">
135
+ <div className="a1-field__footer-message">
136
+ {error ? (
137
+ <p className="a1-field__message a1-field__message--error" id={errorId} role="alert">{error}</p>
138
+ ) : hint ? (
139
+ <p className="a1-field__message a1-field__message--hint" id={hintId}>{hint}</p>
140
+ ) : null}
141
+ </div>
142
+ {charCountEl}
143
+ </div>
144
+ )}
145
+ </div>
146
+ );
147
+ });
@@ -0,0 +1,11 @@
1
+ import { TextField } from "./TextField.jsx";
2
+
3
+ export function TimeField({ className = "", ...props }) {
4
+ return (
5
+ <TextField
6
+ type="time"
7
+ className={`a1-field--fit ${className}`.trim()}
8
+ {...props}
9
+ />
10
+ );
11
+ }
@@ -0,0 +1,114 @@
1
+ import { useState, useRef, useLayoutEffect } from "react";
2
+ import { TextField } from "./TextField.jsx";
3
+ import { buildDisplay, extractDigits, maskMaxDigits, nextSlotIndex } from "./maskUtils.js";
4
+
5
+ // Mask format uses '#' for digit, any other character is a literal separator.
6
+ export const ZIP_MASKS = {
7
+ zip5: "#####",
8
+ zip9: "#####-####",
9
+ };
10
+
11
+ const DEFAULT_MASK = ZIP_MASKS.zip5;
12
+
13
+ export function ZipField({
14
+ mask = DEFAULT_MASK,
15
+ value,
16
+ defaultValue,
17
+ onChange,
18
+ onKeyDown: externalKeyDown,
19
+ onFocus: externalFocus,
20
+ onClick: externalClick,
21
+ className = "",
22
+ ...props
23
+ }) {
24
+ const maxLen = maskMaxDigits(mask);
25
+
26
+ const [digits, setDigits] = useState(() =>
27
+ value != null
28
+ ? extractDigits(String(value)).slice(0, maxLen)
29
+ : defaultValue != null
30
+ ? extractDigits(String(defaultValue)).slice(0, maxLen)
31
+ : ""
32
+ );
33
+
34
+ const inputRef = useRef(null);
35
+ const nextCursor = useRef(null);
36
+
37
+ const currentDigits = value != null
38
+ ? extractDigits(String(value)).slice(0, maxLen)
39
+ : digits;
40
+
41
+ const display = buildDisplay(currentDigits, mask);
42
+
43
+ useLayoutEffect(() => {
44
+ if (nextCursor.current !== null && inputRef.current) {
45
+ const pos = nextCursor.current;
46
+ inputRef.current.setSelectionRange(pos, pos);
47
+ nextCursor.current = null;
48
+ }
49
+ });
50
+
51
+ function updateDigits(newDigits) {
52
+ if (value == null) setDigits(newDigits);
53
+ onChange?.({ target: { value: buildDisplay(newDigits, mask) } });
54
+ nextCursor.current = nextSlotIndex(newDigits.length, mask);
55
+ }
56
+
57
+ function handleKeyDown(e) {
58
+ if (e.key >= "0" && e.key <= "9") {
59
+ e.preventDefault();
60
+ const { selectionStart, selectionEnd } = e.target;
61
+ const base = selectionStart !== selectionEnd ? "" : currentDigits;
62
+ if (base.length < maxLen) updateDigits(base + e.key);
63
+ } else if (e.key === "Backspace" || e.key === "Delete") {
64
+ e.preventDefault();
65
+ const { selectionStart, selectionEnd } = e.target;
66
+ if (selectionStart !== selectionEnd) updateDigits("");
67
+ else if (currentDigits.length > 0) updateDigits(currentDigits.slice(0, -1));
68
+ }
69
+ externalKeyDown?.(e);
70
+ }
71
+
72
+ function handleChange(e) {
73
+ // Handles paste and browser autocomplete
74
+ const newDigits = extractDigits(e.target.value).slice(0, maxLen);
75
+ updateDigits(newDigits);
76
+ }
77
+
78
+ function handleFocus(e) {
79
+ const pos = nextSlotIndex(currentDigits.length, mask);
80
+ requestAnimationFrame(() => { inputRef.current?.setSelectionRange(pos, pos); });
81
+ externalFocus?.(e);
82
+ }
83
+
84
+ function handleClick(e) {
85
+ const pos = nextSlotIndex(currentDigits.length, mask);
86
+ requestAnimationFrame(() => { inputRef.current?.setSelectionRange(pos, pos); });
87
+ externalClick?.(e);
88
+ }
89
+
90
+ const splitAt = nextSlotIndex(currentDigits.length, mask);
91
+ const inputOverlay = (
92
+ <div className="a1-field__mask-overlay" aria-hidden="true">
93
+ <span className="a1-field__mask-typed">{display.slice(0, splitAt)}</span>
94
+ <span className="a1-field__mask-placeholder">{display.slice(splitAt)}</span>
95
+ </div>
96
+ );
97
+
98
+ return (
99
+ <TextField
100
+ ref={inputRef}
101
+ type="text"
102
+ inputMode="numeric"
103
+ autoComplete="postal-code"
104
+ value={display}
105
+ onChange={handleChange}
106
+ onKeyDown={handleKeyDown}
107
+ onFocus={handleFocus}
108
+ onClick={handleClick}
109
+ inputOverlay={inputOverlay}
110
+ className={`a1-field--fit ${className}`.trim()}
111
+ {...props}
112
+ />
113
+ );
114
+ }
@@ -0,0 +1,30 @@
1
+ /* ─── CreditCardField ────────────────────────────────────────────────────── */
2
+
3
+ .a1-credit-card__badge {
4
+ position: absolute;
5
+ right: var(--a1-field-padding-inline);
6
+ top: 50%;
7
+ transform: translateY(-50%);
8
+ pointer-events: none;
9
+ user-select: none;
10
+ font-family: var(--component-paragraph-font-family);
11
+ font-size: var(--semantic-font-size-body-xs);
12
+ font-weight: var(--component-field-credit-card-badge-font-weight);
13
+ color: var(--semantic-color-text-muted);
14
+ background: var(--semantic-color-surface-raised);
15
+ border: var(--component-field-credit-card-badge-border-width) solid var(--semantic-color-border-subtle);
16
+ border-radius: var(--base-radius-sm);
17
+ padding: var(--component-field-credit-card-badge-padding-block) var(--component-field-credit-card-badge-padding-inline);
18
+ min-width: var(--component-field-credit-card-badge-min-width);
19
+ text-align: center;
20
+ line-height: var(--component-field-credit-card-badge-line-height);
21
+ }
22
+
23
+ /* Shift input text and mask overlay left when badge is visible */
24
+ .a1-credit-card--detected .a1-field__input {
25
+ padding-right: calc(var(--a1-field-padding-inline) + var(--component-field-credit-card-detected-offset));
26
+ }
27
+
28
+ .a1-credit-card--detected .a1-field__mask-overlay {
29
+ padding-right: calc(var(--a1-field-padding-inline) + var(--component-field-credit-card-detected-offset));
30
+ }
@@ -0,0 +1,380 @@
1
+ /* ─── Field — local custom properties ───────────────────────────────────────── */
2
+
3
+ .a1-field {
4
+ /* size — defaults to "default" */
5
+ --a1-field-height: var(--component-field-default-height);
6
+ --a1-field-padding-inline: var(--component-field-default-padding-inline);
7
+ --a1-field-gap: var(--component-field-default-gap);
8
+ --a1-field-border-radius: var(--base-radius-md);
9
+ --a1-field-font-size: var(--semantic-font-size-body-md);
10
+ --a1-field-label-size: var(--semantic-font-size-body-sm);
11
+ --a1-field-label-weight: var(--component-field-label-font-weight);
12
+ --a1-field-message-size: var(--semantic-font-size-body-xs);
13
+ --a1-field-chevron-size: var(--component-field-chevron-size);
14
+ --a1-field-side-label-width: var(--component-field-side-label-width);
15
+ --a1-field-accent-border-width: var(--component-field-required-border-width);
16
+ --a1-field-accent-compensation: var(--component-field-accent-compensation);
17
+
18
+ /* interaction — hover/active/readonly values come from :root in color-scheme.css */
19
+ --a1-field-background: var(--semantic-color-surface-page);
20
+ --a1-field-border-color: var(--semantic-color-border-strong);
21
+ --a1-field-focus-ring-color: var(--component-field-focus-ring-color);
22
+ --a1-field-focus-ring-width: var(--component-field-focus-ring-width);
23
+ --a1-field-focus-ring-offset: var(--component-field-focus-ring-offset);
24
+
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 0;
28
+ }
29
+
30
+ /* ─── Sizes ────────────────────────────────────────────────────────────────── */
31
+
32
+ .a1-field--comfortable {
33
+ --a1-field-height: var(--component-field-comfortable-height);
34
+ --a1-field-padding-inline: var(--component-field-comfortable-padding-inline);
35
+ --a1-field-gap: var(--component-field-comfortable-gap);
36
+ --a1-field-border-radius: var(--base-radius-lg);
37
+ --a1-field-label-size: var(--semantic-font-size-body-md);
38
+ --a1-field-message-size: var(--semantic-font-size-body-sm);
39
+ --a1-field-chevron-size: var(--component-field-chevron-size-comfortable);
40
+ --a1-field-side-label-width: var(--component-field-side-label-width-comfortable);
41
+ }
42
+
43
+ @media (--bp-md-up) {
44
+ .a1-field--comfortable {
45
+ --a1-field-font-size: var(--semantic-font-size-body-lg);
46
+ --a1-field-label-size: var(--semantic-font-size-body-lg);
47
+ }
48
+ }
49
+
50
+ @media (--bp-lg-up) {
51
+ .a1-field--comfortable {
52
+ --a1-field-label-size: var(--semantic-font-size-body-xl);
53
+ --a1-field-message-size: var(--semantic-font-size-body-md);
54
+ }
55
+ }
56
+
57
+ @media (--bp-xl-up) {
58
+ .a1-field--comfortable {
59
+ --a1-field-label-size: var(--semantic-font-size-heading-md);
60
+ }
61
+ }
62
+
63
+ .a1-field--compact {
64
+ --a1-field-height: var(--component-field-compact-height);
65
+ --a1-field-padding-inline: var(--component-field-compact-padding-inline);
66
+ --a1-field-gap: var(--component-field-compact-gap);
67
+ --a1-field-border-radius: var(--base-radius-sm);
68
+ --a1-field-font-size: var(--semantic-font-size-body-sm);
69
+ --a1-field-label-size: var(--semantic-font-size-body-xs);
70
+ --a1-field-label-weight: var(--component-field-compact-label-font-weight);
71
+ --a1-field-message-size: var(--semantic-font-size-body-xs);
72
+ --a1-field-chevron-size: var(--component-field-chevron-size-compact);
73
+ --a1-field-accent-border-width: var(--component-field-compact-accent-border-width);
74
+ --a1-field-accent-compensation: var(--component-field-accent-compensation-compact);
75
+ }
76
+
77
+ /* Compact restores 16px input font on small screens (prevents iOS zoom) */
78
+ @media (--bp-sm-down) {
79
+ .a1-field--compact {
80
+ --a1-field-font-size: var(--semantic-font-size-body-md);
81
+ }
82
+ }
83
+
84
+ /* ─── Label ────────────────────────────────────────────────────────────────── */
85
+
86
+ .a1-field__label {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: var(--base-spacing-6);
90
+ font-family: var(--component-paragraph-font-family);
91
+ font-size: var(--a1-field-label-size);
92
+ font-weight: var(--a1-field-label-weight);
93
+ color: var(--semantic-color-text-default);
94
+ line-height: var(--semantic-font-line-height-body);
95
+ /* Extend touch area downward so clicking in the gap between label and input
96
+ still activates the associated field (label is linked via htmlFor). */
97
+ padding-bottom: var(--a1-field-gap);
98
+ }
99
+
100
+ .a1-field__asterisk {
101
+ color: var(--semantic-color-status-info-background);
102
+ }
103
+
104
+ /* ─── Control wrapper ──────────────────────────────────────────────────────── */
105
+
106
+ .a1-field__control {
107
+ position: relative;
108
+ display: flex;
109
+ }
110
+
111
+ /* ─── Shared control styles (input + select) ─────────────────────────────── */
112
+
113
+ :is(.a1-field__input, .a1-field__select) {
114
+ box-sizing: border-box;
115
+ width: 100%;
116
+ height: var(--a1-field-height);
117
+ padding-inline: var(--a1-field-padding-inline);
118
+ font-family: var(--component-paragraph-font-family);
119
+ font-size: var(--a1-field-font-size);
120
+ font-weight: var(--base-font-weight-regular);
121
+ line-height: var(--semantic-font-line-height-body);
122
+ color: var(--semantic-color-text-default);
123
+ background: var(--a1-field-background);
124
+ border: var(--component-field-border-width) solid var(--a1-field-border-color);
125
+ border-radius: var(--a1-field-border-radius);
126
+ transition:
127
+ border-color var(--semantic-motion-duration-fast),
128
+ background var(--semantic-motion-duration-fast);
129
+ -webkit-appearance: none;
130
+ appearance: none;
131
+ }
132
+
133
+ /* ─── Input-specific ────────────────────────────────────────────────────────── */
134
+
135
+ .a1-field__input::placeholder {
136
+ color: var(--semantic-color-text-muted);
137
+ font-weight: var(--base-font-weight-regular);
138
+ }
139
+
140
+ /* ─── Select-specific ───────────────────────────────────────────────────────── */
141
+
142
+ .a1-field__select {
143
+ padding-right: calc(var(--a1-field-padding-inline) + var(--a1-field-chevron-size) + var(--base-spacing-6));
144
+ cursor: pointer;
145
+ }
146
+
147
+ .a1-field__chevron {
148
+ position: absolute;
149
+ right: var(--a1-field-padding-inline);
150
+ top: 50%;
151
+ transform: translateY(-50%);
152
+ font-size: var(--a1-field-chevron-size);
153
+ color: var(--semantic-color-text-muted);
154
+ pointer-events: none;
155
+ display: flex;
156
+ align-items: center;
157
+ }
158
+
159
+ /* ─── Hover ────────────────────────────────────────────────────────────────── */
160
+
161
+ :is(.a1-field__input, .a1-field__select):hover:not(:disabled):not(:focus) {
162
+ background: var(--a1-field-hover-background);
163
+ border-color: var(--a1-field-hover-border-color);
164
+ }
165
+
166
+ .a1-field__input:read-only:hover:not(:disabled):not(:focus) {
167
+ background: var(--a1-field-read-only-background);
168
+ border-color: var(--a1-field-read-only-border-color);
169
+ }
170
+
171
+ /* ─── Active ───────────────────────────────────────────────────────────────── */
172
+
173
+ :is(.a1-field__input, .a1-field__select):active:not(:disabled) {
174
+ background: var(--a1-field-active-background);
175
+ border-color: var(--a1-field-active-border-color);
176
+ }
177
+
178
+ .a1-field__input:read-only:active:not(:disabled) {
179
+ background: var(--a1-field-read-only-background);
180
+ border-color: var(--a1-field-read-only-border-color);
181
+ }
182
+
183
+ /* ─── Focus ────────────────────────────────────────────────────────────────── */
184
+
185
+ :is(.a1-field__input, .a1-field__select):focus {
186
+ outline: var(--a1-field-focus-ring-width) solid var(--a1-field-focus-ring-color);
187
+ outline-offset: var(--a1-field-focus-ring-offset);
188
+ border-color: var(--semantic-color-action-background);
189
+ }
190
+
191
+ /* ─── Required ─────────────────────────────────────────────────────────────── */
192
+
193
+ .a1-field--required :is(.a1-field__input, .a1-field__select) {
194
+ border-color: var(--semantic-color-status-info-border);
195
+ border-left-width: var(--a1-field-accent-border-width);
196
+ border-left-color: var(--semantic-color-status-info-background);
197
+ padding-left: calc(var(--a1-field-padding-inline) - var(--a1-field-accent-compensation));
198
+ }
199
+
200
+ .a1-field--required :is(.a1-field__input, .a1-field__select):focus {
201
+ border-color: var(--semantic-color-status-info-background);
202
+ }
203
+
204
+ /* ─── Error ────────────────────────────────────────────────────────────────── */
205
+
206
+ .a1-field--error :is(.a1-field__input, .a1-field__select) {
207
+ border-width: var(--component-field-error-border-width);
208
+ border-color: var(--semantic-color-status-error-border);
209
+ border-left-width: var(--a1-field-accent-border-width);
210
+ border-left-color: var(--semantic-color-status-error-background);
211
+ padding-left: calc(var(--a1-field-padding-inline) - var(--a1-field-accent-compensation));
212
+ }
213
+
214
+ .a1-field--error :is(.a1-field__input, .a1-field__select):focus {
215
+ border-color: var(--semantic-color-status-error-background);
216
+ }
217
+
218
+ /* ─── Disabled ─────────────────────────────────────────────────────────────── */
219
+
220
+ .a1-field--disabled .a1-field__label {
221
+ color: var(--semantic-color-text-muted);
222
+ }
223
+
224
+ :is(.a1-field__input, .a1-field__select):disabled {
225
+ background: var(--semantic-color-surface-raised);
226
+ color: var(--semantic-color-text-muted);
227
+ border-color: var(--semantic-color-border-subtle);
228
+ cursor: not-allowed;
229
+ }
230
+
231
+ .a1-field--disabled .a1-field__chevron {
232
+ color: var(--semantic-color-text-muted);
233
+ opacity: 0.5;
234
+ }
235
+
236
+ /* ─── Read-only (input only) ─────────────────────────────────────────────── */
237
+
238
+ .a1-field__input:read-only:not(:disabled) {
239
+ background: var(--a1-field-read-only-background);
240
+ color: var(--a1-field-read-only-text);
241
+ border-color: var(--a1-field-read-only-border-color);
242
+ cursor: text;
243
+ user-select: text;
244
+ }
245
+
246
+ /* ─── Number input — hide native spin buttons ────────────────────────────── */
247
+
248
+ .a1-field__input[type="number"]::-webkit-inner-spin-button,
249
+ .a1-field__input[type="number"]::-webkit-outer-spin-button {
250
+ -webkit-appearance: none;
251
+ margin: 0;
252
+ }
253
+
254
+ .a1-field__input[type="number"] {
255
+ -moz-appearance: textfield;
256
+ }
257
+
258
+ /* ─── Date / time — native picker indicator ──────────────────────────────── */
259
+
260
+ .a1-field__input[type="date"]::-webkit-calendar-picker-indicator,
261
+ .a1-field__input[type="time"]::-webkit-calendar-picker-indicator {
262
+ opacity: 0.45;
263
+ cursor: pointer;
264
+ }
265
+
266
+ .a1-field--disabled .a1-field__input[type="date"]::-webkit-calendar-picker-indicator,
267
+ .a1-field--disabled .a1-field__input[type="time"]::-webkit-calendar-picker-indicator {
268
+ opacity: 0.25;
269
+ cursor: not-allowed;
270
+ }
271
+
272
+ /* ─── Fit-content width (date, zip, and other fixed-width fields) ───────────── */
273
+
274
+ .a1-field--fit {
275
+ width: fit-content;
276
+ }
277
+
278
+ .a1-field--fit .a1-field__input {
279
+ width: auto;
280
+ }
281
+
282
+ /* ─── Mask overlay (PhoneField, ZipField) ─────────────────────────────────── */
283
+
284
+ .a1-field__mask-overlay {
285
+ position: absolute;
286
+ inset: 0;
287
+ display: flex;
288
+ align-items: center;
289
+ /* Offset by border-width so text aligns with the (transparent) input text */
290
+ padding-left: calc(var(--component-field-border-width) + var(--a1-field-padding-inline));
291
+ padding-right: var(--a1-field-padding-inline);
292
+ font-family: var(--component-paragraph-font-family);
293
+ font-size: var(--a1-field-font-size);
294
+ font-weight: var(--base-font-weight-regular);
295
+ line-height: var(--semantic-font-line-height-body);
296
+ pointer-events: none;
297
+ user-select: none;
298
+ overflow: hidden;
299
+ white-space: pre;
300
+ }
301
+
302
+ .a1-field__mask-typed { color: var(--semantic-color-text-default); }
303
+ .a1-field__mask-placeholder { color: var(--semantic-color-text-muted); }
304
+
305
+ /* Hide the actual input/select text; keep the caret visible */
306
+ .a1-field__control:has(.a1-field__mask-overlay) .a1-field__input {
307
+ color: transparent;
308
+ caret-color: var(--semantic-color-text-default);
309
+ }
310
+
311
+
312
+ .a1-field__control:has(.a1-field__mask-overlay) .a1-field__select {
313
+ color: transparent;
314
+ }
315
+
316
+ /* Disabled: collapse typed portion to muted so whole mask reads uniformly */
317
+ .a1-field--disabled .a1-field__mask-typed {
318
+ color: var(--semantic-color-text-muted);
319
+ }
320
+
321
+ /* ─── Message (hint / error) ───────────────────────────────────────────────── */
322
+
323
+ .a1-field__message {
324
+ margin: 0;
325
+ margin-top: var(--a1-field-gap);
326
+ font-family: var(--component-paragraph-font-family);
327
+ font-size: var(--a1-field-message-size);
328
+ line-height: var(--semantic-font-line-height-body);
329
+ }
330
+
331
+ .a1-field__message--hint {
332
+ color: var(--semantic-color-text-muted);
333
+ }
334
+
335
+ .a1-field__message--error {
336
+ color: var(--semantic-color-status-error-background);
337
+ }
338
+
339
+ /* ─── Side label layout ────────────────────────────────────────────────────── */
340
+
341
+ .a1-field--label-side {
342
+ display: grid;
343
+ grid-template-columns: var(--a1-field-side-label-width) 1fr;
344
+ column-gap: var(--base-spacing-16);
345
+ row-gap: var(--base-spacing-4);
346
+ align-items: start;
347
+ }
348
+
349
+ .a1-field--label-side .a1-field__label {
350
+ padding-top: calc((var(--a1-field-height) - 1lh) / 2);
351
+ padding-bottom: 0;
352
+ }
353
+
354
+ .a1-field--label-side .a1-field__message {
355
+ grid-column: 1;
356
+ grid-row: 2;
357
+ /* Fine-tuned margin: overrides the base margin-top added for the above-label
358
+ layout. Net gap = row-gap + base-spacing-2 below the label text. */
359
+ margin-top: calc((var(--a1-field-label-size) * 1.5 - var(--a1-field-height)) / 2 + var(--base-spacing-2));
360
+ }
361
+
362
+ /* ─── Side label → stacked on xs/sm ───────────────────────────────────────── */
363
+
364
+ @media (--bp-sm-down) {
365
+ .a1-field--label-side {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: var(--a1-field-gap); /* restore: grid overrode row-gap to base-spacing-4 */
369
+ align-items: stretch; /* restore: grid set align-items: start */
370
+ }
371
+
372
+ .a1-field--label-side .a1-field__label {
373
+ padding-top: 0;
374
+ padding-bottom: var(--a1-field-gap);
375
+ }
376
+
377
+ .a1-field--label-side .a1-field__message {
378
+ margin-top: var(--a1-field-gap);
379
+ }
380
+ }