@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
@@ -0,0 +1,185 @@
1
+ /* ─── TextareaField — size-specific padding ──────────────────────────────── */
2
+
3
+ .a1-field {
4
+ --a1-textarea-padding-block: var(--component-field-textarea-padding-block);
5
+ }
6
+
7
+ .a1-field--comfortable {
8
+ --a1-textarea-padding-block: var(--component-field-textarea-padding-block-comfortable);
9
+ }
10
+
11
+ .a1-field--compact {
12
+ --a1-textarea-padding-block: var(--component-field-textarea-padding-block-compact);
13
+ }
14
+
15
+ /* ─── Textarea control ───────────────────────────────────────────────────── */
16
+
17
+ .a1-field__textarea {
18
+ box-sizing: border-box;
19
+ display: block; /* removes inline gap below element */
20
+ width: 100%;
21
+ padding-inline: var(--a1-field-padding-inline);
22
+ padding-block: var(--a1-textarea-padding-block);
23
+ font-family: var(--component-paragraph-font-family);
24
+ font-size: var(--a1-field-font-size);
25
+ font-weight: var(--base-font-weight-regular);
26
+ line-height: var(--semantic-font-line-height-body);
27
+ color: var(--semantic-color-text-default);
28
+ background: var(--a1-field-background);
29
+ border: var(--component-field-border-width) solid var(--a1-field-border-color);
30
+ border-radius: var(--a1-field-border-radius);
31
+ transition:
32
+ border-color var(--semantic-motion-duration-fast),
33
+ background var(--semantic-motion-duration-fast);
34
+ -webkit-appearance: none;
35
+ appearance: none;
36
+ resize: vertical;
37
+ }
38
+
39
+ .a1-field__textarea::placeholder {
40
+ color: var(--semantic-color-text-muted);
41
+ font-weight: var(--base-font-weight-regular);
42
+ }
43
+
44
+ /* ─── Hover ──────────────────────────────────────────────────────────────── */
45
+
46
+ .a1-field__textarea:hover:not(:disabled):not(:focus) {
47
+ background: var(--a1-field-hover-background);
48
+ border-color: var(--a1-field-hover-border-color);
49
+ }
50
+
51
+ .a1-field__textarea:read-only:hover:not(:disabled):not(:focus) {
52
+ background: var(--a1-field-read-only-background);
53
+ border-color: var(--a1-field-read-only-border-color);
54
+ }
55
+
56
+ /* ─── Active ─────────────────────────────────────────────────────────────── */
57
+
58
+ .a1-field__textarea:active:not(:disabled) {
59
+ background: var(--a1-field-active-background);
60
+ border-color: var(--a1-field-active-border-color);
61
+ }
62
+
63
+ .a1-field__textarea:read-only:active:not(:disabled) {
64
+ background: var(--a1-field-read-only-background);
65
+ border-color: var(--a1-field-read-only-border-color);
66
+ }
67
+
68
+ /* ─── Focus ──────────────────────────────────────────────────────────────── */
69
+
70
+ .a1-field__textarea:focus {
71
+ outline: var(--a1-field-focus-ring-width) solid var(--a1-field-focus-ring-color);
72
+ outline-offset: var(--a1-field-focus-ring-offset);
73
+ border-color: var(--semantic-color-action-background);
74
+ }
75
+
76
+ /* ─── Required ───────────────────────────────────────────────────────────── */
77
+
78
+ .a1-field--required .a1-field__textarea {
79
+ border-color: var(--semantic-color-status-info-border);
80
+ border-left-width: var(--a1-field-accent-border-width);
81
+ border-left-color: var(--semantic-color-status-info-background);
82
+ padding-left: calc(var(--a1-field-padding-inline) - var(--a1-field-accent-compensation));
83
+ }
84
+
85
+ .a1-field--required .a1-field__textarea:focus {
86
+ border-color: var(--semantic-color-status-info-background);
87
+ }
88
+
89
+ /* ─── Error ──────────────────────────────────────────────────────────────── */
90
+
91
+ .a1-field--error .a1-field__textarea {
92
+ border-width: var(--component-field-error-border-width);
93
+ border-color: var(--semantic-color-status-error-border);
94
+ border-left-width: var(--a1-field-accent-border-width);
95
+ border-left-color: var(--semantic-color-status-error-background);
96
+ padding-left: calc(var(--a1-field-padding-inline) - var(--a1-field-accent-compensation));
97
+ }
98
+
99
+ .a1-field--error .a1-field__textarea:focus {
100
+ border-color: var(--semantic-color-status-error-background);
101
+ }
102
+
103
+ /* ─── Disabled ───────────────────────────────────────────────────────────── */
104
+
105
+ .a1-field__textarea:disabled {
106
+ background: var(--semantic-color-surface-raised);
107
+ color: var(--semantic-color-text-muted);
108
+ border-color: var(--semantic-color-border-subtle);
109
+ cursor: not-allowed;
110
+ resize: none;
111
+ }
112
+
113
+ /* ─── Read-only ──────────────────────────────────────────────────────────── */
114
+
115
+ .a1-field__textarea:read-only:not(:disabled) {
116
+ background: var(--a1-field-read-only-background);
117
+ color: var(--a1-field-read-only-text);
118
+ border-color: var(--a1-field-read-only-border-color);
119
+ cursor: text;
120
+ user-select: text;
121
+ resize: none;
122
+ }
123
+
124
+ /* ─── Footer (hint / error + char count row) ─────────────────────────────── */
125
+
126
+ .a1-field__footer {
127
+ display: flex;
128
+ align-items: baseline;
129
+ gap: var(--base-spacing-8);
130
+ margin-top: var(--a1-field-gap);
131
+ }
132
+
133
+ .a1-field__footer-message {
134
+ flex: 1;
135
+ min-width: 0;
136
+ }
137
+
138
+ .a1-field__footer .a1-field__message {
139
+ margin: 0;
140
+ }
141
+
142
+ /* ─── Character count ────────────────────────────────────────────────────── */
143
+
144
+ .a1-field__char-count {
145
+ font-family: var(--component-paragraph-font-family);
146
+ font-size: var(--a1-field-message-size);
147
+ line-height: var(--semantic-font-line-height-body);
148
+ color: var(--semantic-color-text-muted);
149
+ white-space: nowrap;
150
+ flex-shrink: 0;
151
+ font-variant-numeric: tabular-nums;
152
+ }
153
+
154
+ .a1-field__char-count--warning {
155
+ color: var(--semantic-color-status-warn-text);
156
+ }
157
+
158
+ .a1-field__char-count--over {
159
+ color: var(--semantic-color-status-error-text);
160
+ }
161
+
162
+ /* ─── Side label layout adjustments ─────────────────────────────────────── */
163
+
164
+ /*
165
+ * Align label to the top of the textarea's first text line rather than
166
+ * centering it vertically as with single-line inputs.
167
+ */
168
+ .a1-field--label-side:has(.a1-field__textarea) .a1-field__label {
169
+ padding-top: var(--a1-textarea-padding-block);
170
+ padding-bottom: 0;
171
+ }
172
+
173
+ /* Place the footer under the textarea (column 2) instead of under the label */
174
+ .a1-field--label-side .a1-field__footer {
175
+ grid-column: 2;
176
+ grid-row: 2;
177
+ margin-top: 0;
178
+ }
179
+
180
+ @media (--bp-sm-down) {
181
+ .a1-field--label-side:has(.a1-field__textarea) .a1-field__label {
182
+ padding-top: 0;
183
+ padding-bottom: var(--a1-field-gap);
184
+ }
185
+ }
@@ -0,0 +1,23 @@
1
+ import { useContext } from "react";
2
+ import { FieldsetContext } from "../fieldset/FieldsetContext.js";
3
+ import "./field-row.css";
4
+
5
+ export function FieldRow({ children, className = "", ...props }) {
6
+ const ctx = useContext(FieldsetContext);
7
+
8
+ // Side-label fields already use an internal two-column grid;
9
+ // stacking the row prevents layout conflicts.
10
+ const stacked = ctx?.labelPosition === "side";
11
+
12
+ const classes = [
13
+ "a1-field-row",
14
+ stacked && "a1-field-row--stacked",
15
+ className,
16
+ ].filter(Boolean).join(" ");
17
+
18
+ return (
19
+ <div className={classes} {...props}>
20
+ {children}
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,51 @@
1
+ /* ─── FieldRow ───────────────────────────────────────────────────────────── */
2
+
3
+ .a1-field-row {
4
+ display: flex;
5
+ align-items: flex-start;
6
+ gap: var(--base-spacing-12);
7
+ }
8
+
9
+ /* Every direct child fills an equal share of the available width */
10
+ .a1-field-row > * {
11
+ flex: 1 1 0;
12
+ min-width: 0;
13
+ }
14
+
15
+ /* Fixed-width fields (ZipField, DateField) keep their natural fit-content width */
16
+ .a1-field-row > .a1-field--fit {
17
+ flex: none;
18
+ }
19
+
20
+ /* ─── Stacked — used when side labels are active ─────────────────────────── */
21
+
22
+ .a1-field-row--stacked {
23
+ flex-direction: column;
24
+ }
25
+
26
+ .a1-field-row--stacked > * {
27
+ flex: none;
28
+ width: 100%;
29
+ }
30
+
31
+ /* Fit-content fields don't stretch to full width even when stacked */
32
+ .a1-field-row--stacked > .a1-field--fit {
33
+ width: fit-content;
34
+ }
35
+
36
+ /* ─── Responsive — stack on xs / sm viewports ─────────────────────────────── */
37
+
38
+ @media (--bp-sm-down) {
39
+ .a1-field-row {
40
+ flex-direction: column;
41
+ }
42
+
43
+ .a1-field-row > * {
44
+ flex: none;
45
+ width: 100%;
46
+ }
47
+
48
+ .a1-field-row > .a1-field--fit {
49
+ width: fit-content;
50
+ }
51
+ }
@@ -0,0 +1,49 @@
1
+ import { FieldsetContext } from "./FieldsetContext.js";
2
+ import "./fieldset.css";
3
+
4
+ const SIZES = ["comfortable", "default", "compact"];
5
+ const LABEL_POSITIONS = ["above", "side"];
6
+
7
+ export function Fieldset({
8
+ legend,
9
+ size,
10
+ labelPosition,
11
+ markRequired = false,
12
+ surface = false,
13
+ children,
14
+ className = "",
15
+ ...props
16
+ }) {
17
+ const resolvedSize = SIZES.includes(size) ? size : undefined;
18
+ const resolvedPosition = LABEL_POSITIONS.includes(labelPosition) ? labelPosition : undefined;
19
+
20
+ // Note appears for compact/default; comfortable fields already show a "Required" badge.
21
+ const showRequiredNote = markRequired && resolvedSize !== "comfortable";
22
+
23
+ const classes = [
24
+ "a1-fieldset",
25
+ surface && "a1-fieldset--surface",
26
+ className,
27
+ ].filter(Boolean).join(" ");
28
+
29
+ return (
30
+ <FieldsetContext.Provider value={{ size: resolvedSize, labelPosition: resolvedPosition }}>
31
+ <fieldset className={classes} {...props}>
32
+ {legend && (
33
+ <legend className={`a1-fieldset__legend${showRequiredNote ? " a1-fieldset__legend--has-note" : ""}`}>
34
+ {legend}
35
+ </legend>
36
+ )}
37
+ {showRequiredNote && (
38
+ <p className="a1-fieldset__required-note">
39
+ <span className="a1-fieldset__required-note__asterisk">*</span>
40
+ {" "}Required field
41
+ </p>
42
+ )}
43
+ <div className="a1-fieldset__body">
44
+ {children}
45
+ </div>
46
+ </fieldset>
47
+ </FieldsetContext.Provider>
48
+ );
49
+ }
@@ -0,0 +1,75 @@
1
+ /* ─── Fieldset — reset browser defaults ──────────────────────────────────── */
2
+
3
+ .a1-fieldset {
4
+ border: none;
5
+ margin: 0;
6
+ padding: 0;
7
+ min-width: 0; /* prevents default overflow on narrow containers */
8
+ }
9
+
10
+ /* ─── Legend ─────────────────────────────────────────────────────────────── */
11
+
12
+ .a1-fieldset__legend {
13
+ display: block;
14
+ width: 100%;
15
+ padding: 0;
16
+ margin-bottom: var(--base-spacing-16);
17
+ font-family: var(--component-paragraph-font-family);
18
+ font-size: var(--semantic-font-size-body-lg);
19
+ font-weight: var(--component-field-label-font-weight);
20
+ color: var(--semantic-color-text-default);
21
+ line-height: var(--semantic-font-line-height-body);
22
+ }
23
+
24
+ /* Tighten bottom margin when the required note follows */
25
+ .a1-fieldset__legend--has-note {
26
+ margin-bottom: var(--base-spacing-4);
27
+ }
28
+
29
+ /* ─── Required note (* Required field) ───────────────────────────────────── */
30
+
31
+ .a1-fieldset__required-note {
32
+ margin: 0 0 var(--base-spacing-12);
33
+ font-family: var(--component-paragraph-font-family);
34
+ font-size: var(--semantic-font-size-body-xs);
35
+ color: var(--semantic-color-text-muted);
36
+ line-height: var(--semantic-font-line-height-body);
37
+ }
38
+
39
+ .a1-fieldset__required-note__asterisk {
40
+ color: var(--semantic-color-status-info-background);
41
+ }
42
+
43
+ /* ─── Body ───────────────────────────────────────────────────────────────── */
44
+
45
+ .a1-fieldset__body {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: var(--base-spacing-16);
49
+ }
50
+
51
+ /* ─── Surface variant ────────────────────────────────────────────────────── */
52
+
53
+ .a1-fieldset--surface {
54
+ background: var(--semantic-color-surface-page);
55
+ border: var(--component-card-border-width) solid var(--semantic-color-border-subtle);
56
+ border-radius: var(--component-card-border-radius);
57
+ box-shadow: var(--component-card-shadow);
58
+ padding: var(--component-card-padding);
59
+ }
60
+
61
+ /*
62
+ * float: left is the standard hack to pull <legend> out of its special
63
+ * browser rendering (where it would otherwise sit on top of the border).
64
+ * With float the legend becomes a normal block at the top of the card.
65
+ */
66
+ .a1-fieldset--surface > .a1-fieldset__legend {
67
+ float: left;
68
+ width: 100%;
69
+ }
70
+
71
+ /* Clear the legend float so subsequent content flows below it */
72
+ .a1-fieldset--surface > .a1-fieldset__required-note,
73
+ .a1-fieldset--surface > .a1-fieldset__body {
74
+ clear: left;
75
+ }
@@ -0,0 +1,63 @@
1
+ import "./figure.css";
2
+ import { Bleed } from "../bleed/Bleed.jsx";
3
+
4
+ const rounded = ["none", "sm", "md", "lg"];
5
+ const captionPositions = ["start", "center"];
6
+ const spacings = ["sm", "md", "lg"];
7
+ const sizes = ["xs", "sm", "md", "lg"];
8
+ const alignments = ["start", "center", "end"];
9
+
10
+ export function Figure({
11
+ src,
12
+ alt = "",
13
+ caption,
14
+ captionSrOnly = false,
15
+ captionPosition = "start",
16
+ radius,
17
+ size,
18
+ align,
19
+ marginTop,
20
+ marginBottom,
21
+ bleed,
22
+ className = "",
23
+ imgClassName = "",
24
+ style,
25
+ imgStyle,
26
+ ...props
27
+ }) {
28
+ const classes = [
29
+ "a1-figure",
30
+ radius != null && rounded.includes(radius) && `a1-figure--rounded-${radius}`,
31
+ captionPositions.includes(captionPosition) && captionPosition !== "start" && `a1-figure--caption-${captionPosition}`,
32
+ sizes.includes(size) && `a1-figure--${size}`,
33
+ alignments.includes(align) && align !== "start" && `a1-figure--align-${align}`,
34
+ spacings.includes(marginTop) && `a1-figure--mt-${marginTop}`,
35
+ spacings.includes(marginBottom) && `a1-figure--mb-${marginBottom}`,
36
+ className,
37
+ ].filter(Boolean).join(" ");
38
+
39
+ const captionClasses = [
40
+ captionSrOnly ? "a1-sr-only" : "a1-figure__caption",
41
+ ].join(" ");
42
+
43
+ const figure = (
44
+ <figure className={classes} style={style} {...props}>
45
+ <img
46
+ src={src}
47
+ alt={alt}
48
+ className={["a1-figure__img", imgClassName].filter(Boolean).join(" ")}
49
+ style={imgStyle}
50
+ />
51
+ {caption && (
52
+ <figcaption className={captionClasses}>{caption}</figcaption>
53
+ )}
54
+ </figure>
55
+ );
56
+
57
+ if (bleed) {
58
+ const inlineValue = bleed === true ? undefined : bleed;
59
+ return <Bleed inline={inlineValue}>{figure}</Bleed>;
60
+ }
61
+
62
+ return figure;
63
+ }
@@ -0,0 +1,97 @@
1
+ /* ══════════════════════════════════════════════════════════════════════════
2
+ Figure
3
+ ══════════════════════════════════════════════════════════════════════════ */
4
+
5
+ @import "../../utilities/sr-only.css";
6
+
7
+ .a1-figure {
8
+ margin: 0;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: var(--base-spacing-8);
12
+ }
13
+
14
+ .a1-figure__img {
15
+ display: block;
16
+ width: 100%;
17
+ height: auto;
18
+ border-radius: var(--base-radius-container);
19
+ }
20
+
21
+ .a1-figure__caption {
22
+ font-family: var(--component-paragraph-font-family);
23
+ font-size: var(--semantic-font-size-body-sm);
24
+ font-weight: var(--semantic-font-weight-body);
25
+ line-height: var(--semantic-font-line-height-body);
26
+ color: var(--semantic-color-text-muted);
27
+ font-style: italic;
28
+ }
29
+
30
+ /* ── Rounded modifier ──────────────────────────────────────────────────────── */
31
+
32
+ .a1-figure--rounded .a1-figure__img {
33
+ border-radius: var(--base-radius-container);
34
+ }
35
+
36
+ .a1-figure--rounded-none .a1-figure__img {
37
+ border-radius: 0;
38
+ }
39
+
40
+ .a1-figure--rounded-sm .a1-figure__img {
41
+ border-radius: var(--base-radius-sm);
42
+ }
43
+
44
+ .a1-figure--rounded-md .a1-figure__img {
45
+ border-radius: var(--base-radius-md);
46
+ }
47
+
48
+ .a1-figure--rounded-lg .a1-figure__img {
49
+ border-radius: var(--base-radius-lg);
50
+ }
51
+
52
+ /* ── Margin ──────────────────────────────────────────────────────────────── */
53
+
54
+ .a1-figure--mt-sm { margin-top: var(--base-spacing-8); }
55
+ .a1-figure--mt-md { margin-top: var(--base-spacing-16); }
56
+ .a1-figure--mt-lg { margin-top: var(--base-spacing-24); }
57
+
58
+ .a1-figure--mb-sm { margin-bottom: var(--base-spacing-8); }
59
+ .a1-figure--mb-md { margin-bottom: var(--base-spacing-16); }
60
+ .a1-figure--mb-lg { margin-bottom: var(--base-spacing-24); }
61
+
62
+ /* ── Alignment ───────────────────────────────────────────────────────────── */
63
+
64
+ /* start/end: constrain the figure itself and shift via margin */
65
+ .a1-figure--align-end { margin-inline-start: auto; }
66
+
67
+ /* center: figure stays full-width; size constrains img + caption */
68
+ .a1-figure--align-center { align-items: center; }
69
+
70
+ /* ── Size ────────────────────────────────────────────────────────────────── */
71
+
72
+ /* Default: constrain the figure element */
73
+ .a1-figure--xs { max-width: 12rem; }
74
+ .a1-figure--sm { max-width: 20rem; }
75
+ .a1-figure--md { max-width: 30rem; }
76
+ .a1-figure--lg { max-width: 40rem; }
77
+
78
+ /* Center override: transfer the constraint to img + caption instead */
79
+ .a1-figure--align-center { max-width: none; }
80
+
81
+ .a1-figure--align-center.a1-figure--xs .a1-figure__img,
82
+ .a1-figure--align-center.a1-figure--xs .a1-figure__caption { max-width: 12rem; }
83
+
84
+ .a1-figure--align-center.a1-figure--sm .a1-figure__img,
85
+ .a1-figure--align-center.a1-figure--sm .a1-figure__caption { max-width: 20rem; }
86
+
87
+ .a1-figure--align-center.a1-figure--md .a1-figure__img,
88
+ .a1-figure--align-center.a1-figure--md .a1-figure__caption { max-width: 30rem; }
89
+
90
+ .a1-figure--align-center.a1-figure--lg .a1-figure__img,
91
+ .a1-figure--align-center.a1-figure--lg .a1-figure__caption { max-width: 40rem; }
92
+
93
+ /* ── Caption position ──────────────────────────────────────────────────────── */
94
+
95
+ .a1-figure--caption-center .a1-figure__caption {
96
+ text-align: center;
97
+ }
@@ -1,9 +1,20 @@
1
1
  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
+ const gapSizes = {
5
+ sm: "var(--base-spacing-8)",
6
+ md: "var(--base-spacing-16)",
7
+ lg: "var(--base-spacing-24)",
8
+ xl: "var(--base-spacing-32)",
9
+ xxl: "var(--base-spacing-64)",
10
+ };
11
+ const layouts = ["default", "bento"];
12
+ const breakpoints = ["xs", "sm", "md", "lg", "xl"];
4
13
 
5
14
  function resolveGap(key) {
6
15
  if (key == null) return undefined;
16
+ if (gapSizes[key]) return gapSizes[key];
17
+
7
18
  const n = Number(key);
8
19
  return SPACING_KEYS.includes(n) ? `var(--base-spacing-${n})` : undefined;
9
20
  }
@@ -13,11 +24,18 @@ export function Grid({
13
24
  gap,
14
25
  rowGap,
15
26
  columnGap,
27
+ layout = "default",
28
+ autoRows,
16
29
  className = "",
17
30
  children,
18
31
  ...props
19
32
  }) {
20
33
  const classes = ["a1-grid"];
34
+ const resolvedLayout = layouts.includes(layout) ? layout : "default";
35
+
36
+ if (resolvedLayout !== "default") {
37
+ classes.push(`a1-grid--${resolvedLayout}`);
38
+ }
21
39
 
22
40
  let inlineCols;
23
41
  if (typeof columns === "number") {
@@ -36,6 +54,7 @@ export function Grid({
36
54
 
37
55
  const style = {
38
56
  ...(inlineCols != null ? { "--a1-grid-cols": inlineCols } : {}),
57
+ ...(autoRows ? { "--a1-grid-auto-rows": autoRows } : {}),
39
58
  ...(rowGapVal ? { rowGap: rowGapVal } : {}),
40
59
  ...(colGapVal ? { columnGap: colGapVal } : {}),
41
60
  ...props.style,
@@ -57,13 +76,28 @@ export function GridItem({
57
76
  }) {
58
77
  const classes = ["a1-grid-item"];
59
78
 
60
- if (span === "full") {
79
+ if (span && typeof span === "object") {
80
+ for (const [bp, value] of Object.entries(span)) {
81
+ if (!breakpoints.includes(bp)) continue;
82
+ if (value === "full") {
83
+ classes.push(`a1-grid-item--${bp}-span-full`);
84
+ } else if (typeof value === "number") {
85
+ classes.push(`a1-grid-item--${bp}-span-${value}`);
86
+ }
87
+ }
88
+ } else if (span === "full") {
61
89
  classes.push("a1-grid-item--span-full");
62
90
  } else if (typeof span === "number") {
63
91
  classes.push(`a1-grid-item--span-${span}`);
64
92
  }
65
93
 
66
- if (typeof rowSpan === "number") {
94
+ if (rowSpan && typeof rowSpan === "object") {
95
+ for (const [bp, value] of Object.entries(rowSpan)) {
96
+ if (breakpoints.includes(bp) && typeof value === "number") {
97
+ classes.push(`a1-grid-item--${bp}-row-span-${value}`);
98
+ }
99
+ }
100
+ } else if (typeof rowSpan === "number") {
67
101
  classes.push(`a1-grid-item--row-span-${rowSpan}`);
68
102
  }
69
103