@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,383 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ import { Button } from "../button/Button.jsx";
3
+ import { IconButton } from "../icon-button/IconButton.jsx";
4
+ import { SelectField } from "../field/SelectField.jsx";
5
+ import { useLabel } from "../labels/Labels.jsx";
6
+ import "./calendar.css";
7
+
8
+ function addMonths(year, month, delta) {
9
+ let m = month + delta;
10
+ let y = year;
11
+ while (m < 0) { m += 12; y--; }
12
+ while (m > 11) { m -= 12; y++; }
13
+ return { year: y, month: m };
14
+ }
15
+
16
+ function rotateArray(arr, n) {
17
+ return [...arr.slice(n), ...arr.slice(0, n)];
18
+ }
19
+
20
+ function buildWeeks(year, month, weekStart = 0) {
21
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
22
+ const firstDay = new Date(year, month, 1).getDay();
23
+ const offset = (firstDay - weekStart + 7) % 7;
24
+ const weeks = [];
25
+ let week = new Array(offset).fill(null);
26
+
27
+ for (let day = 1; day <= daysInMonth; day++) {
28
+ week.push(day);
29
+ if (week.length === 7) { weeks.push(week); week = []; }
30
+ }
31
+ if (week.length) {
32
+ while (week.length < 7) week.push(null);
33
+ weeks.push(week);
34
+ }
35
+ return weeks;
36
+ }
37
+
38
+ export function Calendar({
39
+ initialMonth,
40
+ monthsToShow = 13,
41
+ highlightToday = true,
42
+ dimPast = true,
43
+ variant = "scroll",
44
+ todayButton = false,
45
+ className = "",
46
+ ...props
47
+ }) {
48
+ const today = new Date();
49
+ const todayYear = today.getFullYear();
50
+ const todayMonth = today.getMonth();
51
+ const todayDay = today.getDate();
52
+
53
+ let centerYear = todayYear, centerMonth = todayMonth;
54
+ if (initialMonth instanceof Date) {
55
+ centerYear = initialMonth.getFullYear();
56
+ centerMonth = initialMonth.getMonth();
57
+ } else if (initialMonth && typeof initialMonth === "object") {
58
+ centerYear = initialMonth.year;
59
+ centerMonth = initialMonth.month - 1;
60
+ }
61
+
62
+ const [viewYear, setViewYear] = useState(centerYear);
63
+ const [viewMonth, setViewMonth] = useState(centerMonth);
64
+ const currentMonthRef = useRef(null);
65
+
66
+ // ── Localised strings ─────────────────────────────────────────
67
+
68
+ // Month names
69
+ const monthJan = useLabel("calendar.month.january", "January");
70
+ const monthFeb = useLabel("calendar.month.february", "February");
71
+ const monthMar = useLabel("calendar.month.march", "March");
72
+ const monthApr = useLabel("calendar.month.april", "April");
73
+ const monthMay = useLabel("calendar.month.may", "May");
74
+ const monthJun = useLabel("calendar.month.june", "June");
75
+ const monthJul = useLabel("calendar.month.july", "July");
76
+ const monthAug = useLabel("calendar.month.august", "August");
77
+ const monthSep = useLabel("calendar.month.september", "September");
78
+ const monthOct = useLabel("calendar.month.october", "October");
79
+ const monthNov = useLabel("calendar.month.november", "November");
80
+ const monthDec = useLabel("calendar.month.december", "December");
81
+
82
+ // Weekday full names (used in abbr attribute and aria-label)
83
+ const dayFullSun = useLabel("calendar.weekday.sunday.full", "Sunday");
84
+ const dayFullMon = useLabel("calendar.weekday.monday.full", "Monday");
85
+ const dayFullTue = useLabel("calendar.weekday.tuesday.full", "Tuesday");
86
+ const dayFullWed = useLabel("calendar.weekday.wednesday.full", "Wednesday");
87
+ const dayFullThu = useLabel("calendar.weekday.thursday.full", "Thursday");
88
+ const dayFullFri = useLabel("calendar.weekday.friday.full", "Friday");
89
+ const dayFullSat = useLabel("calendar.weekday.saturday.full", "Saturday");
90
+
91
+ // Weekday 2-letter abbreviations (medium containers)
92
+ const dayShortSun = useLabel("calendar.weekday.sunday.short", "Su");
93
+ const dayShortMon = useLabel("calendar.weekday.monday.short", "Mo");
94
+ const dayShortTue = useLabel("calendar.weekday.tuesday.short", "Tu");
95
+ const dayShortWed = useLabel("calendar.weekday.wednesday.short", "We");
96
+ const dayShortThu = useLabel("calendar.weekday.thursday.short", "Th");
97
+ const dayShortFri = useLabel("calendar.weekday.friday.short", "Fr");
98
+ const dayShortSat = useLabel("calendar.weekday.saturday.short", "Sa");
99
+
100
+ // Weekday 1-letter abbreviations (smallest containers)
101
+ const dayLetterSun = useLabel("calendar.weekday.sunday.letter", "S");
102
+ const dayLetterMon = useLabel("calendar.weekday.monday.letter", "M");
103
+ const dayLetterTue = useLabel("calendar.weekday.tuesday.letter", "T");
104
+ const dayLetterWed = useLabel("calendar.weekday.wednesday.letter", "W");
105
+ const dayLetterThu = useLabel("calendar.weekday.thursday.letter", "T");
106
+ const dayLetterFri = useLabel("calendar.weekday.friday.letter", "F");
107
+ const dayLetterSat = useLabel("calendar.weekday.saturday.letter", "S");
108
+
109
+ // Navigation labels
110
+ const prevMonthLabel = useLabel("calendar.previousMonth", "Previous month");
111
+ const nextMonthLabel = useLabel("calendar.nextMonth", "Next month");
112
+ const selectMonthLabel = useLabel("calendar.selectMonth", "Month");
113
+ const selectYearLabel = useLabel("calendar.selectYear", "Year");
114
+ const todayLabel = useLabel("calendar.today", "Today");
115
+
116
+ // Locale config
117
+ const weekStartStr = useLabel("calendar.weekStart", "0");
118
+ const directionStr = useLabel("calendar.direction", "ltr");
119
+
120
+ // Build localised arrays used throughout the render
121
+ const MONTHS = [monthJan, monthFeb, monthMar, monthApr, monthMay, monthJun,
122
+ monthJul, monthAug, monthSep, monthOct, monthNov, monthDec];
123
+
124
+ const weekStartDay = parseInt(weekStartStr, 10) || 0;
125
+ const isRtl = directionStr === "rtl";
126
+
127
+ // Weekday headers rotated so the correct start day is first
128
+ const WEEKDAYS_LONG = rotateArray([dayFullSun, dayFullMon, dayFullTue, dayFullWed, dayFullThu, dayFullFri, dayFullSat], weekStartDay);
129
+ const WEEKDAYS_SHORT = rotateArray([dayShortSun, dayShortMon, dayShortTue, dayShortWed, dayShortThu, dayShortFri, dayShortSat], weekStartDay);
130
+ const WEEKDAYS_LETTER = rotateArray([dayLetterSun, dayLetterMon, dayLetterTue, dayLetterWed, dayLetterThu, dayLetterFri, dayLetterSat], weekStartDay);
131
+
132
+ useEffect(() => {
133
+ if (variant !== "scroll") return;
134
+ const el = currentMonthRef.current;
135
+ if (!el) return;
136
+
137
+ // Walk up to find the nearest explicitly scrollable ancestor, stopping at
138
+ // the document root so we never scroll the page itself.
139
+ let container = el.parentElement;
140
+ while (container && container !== document.documentElement) {
141
+ const oy = window.getComputedStyle(container).overflowY;
142
+ if (oy === "auto" || oy === "scroll" || oy === "overlay") break;
143
+ container = container.parentElement;
144
+ }
145
+
146
+ if (!container || container === document.documentElement) return;
147
+
148
+ const containerRect = container.getBoundingClientRect();
149
+ const elRect = el.getBoundingClientRect();
150
+ container.scrollTop += elRect.top - containerRect.top;
151
+ }, []);
152
+
153
+ // Renders the weekday/day grid table — shared between both variants
154
+ function renderGrid(year, month, monthStarted) {
155
+ const weeks = buildWeeks(year, month, weekStartDay);
156
+ return (
157
+ <table
158
+ className="a1-calendar__grid"
159
+ role="grid"
160
+ aria-label={`${MONTHS[month]} ${year}`}
161
+ >
162
+ <thead className="a1-calendar__thead">
163
+ <tr>
164
+ {WEEKDAYS_LONG.map((name, i) => (
165
+ <th
166
+ key={name}
167
+ className="a1-calendar__weekday"
168
+ scope="col"
169
+ abbr={name}
170
+ >
171
+ <span className="a1-calendar__weekday-label a1-calendar__weekday-label--long">
172
+ {name.slice(0, 3)}
173
+ </span>
174
+ <span className="a1-calendar__weekday-label a1-calendar__weekday-label--short">
175
+ {WEEKDAYS_SHORT[i]}
176
+ </span>
177
+ <span className="a1-calendar__weekday-label a1-calendar__weekday-label--letter">
178
+ {WEEKDAYS_LETTER[i]}
179
+ </span>
180
+ </th>
181
+ ))}
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ {weeks.map((week, wi) => (
186
+ <tr key={wi} className="a1-calendar__week">
187
+ {week.map((day, di) => {
188
+ if (day === null) {
189
+ const isLeadingBlank = wi === 0 && monthStarted;
190
+ return (
191
+ <td
192
+ key={di}
193
+ className={[
194
+ "a1-calendar__day",
195
+ "a1-calendar__day--empty",
196
+ isLeadingBlank && "a1-calendar__day--past",
197
+ ].filter(Boolean).join(" ")}
198
+ aria-hidden="true"
199
+ />
200
+ );
201
+ }
202
+
203
+ const isToday =
204
+ highlightToday &&
205
+ year === todayYear &&
206
+ month === todayMonth &&
207
+ day === todayDay;
208
+
209
+ const isPast =
210
+ dimPast &&
211
+ new Date(year, month, day) < new Date(todayYear, todayMonth, todayDay);
212
+
213
+ return (
214
+ <td
215
+ key={di}
216
+ className={[
217
+ "a1-calendar__day",
218
+ isToday && "a1-calendar__day--today",
219
+ isPast && "a1-calendar__day--past",
220
+ ].filter(Boolean).join(" ")}
221
+ aria-current={isToday ? "date" : undefined}
222
+ >
223
+ <span className="a1-calendar__day-number" aria-hidden="true">
224
+ {day}
225
+ </span>
226
+ </td>
227
+ );
228
+ })}
229
+ </tr>
230
+ ))}
231
+ </tbody>
232
+ </table>
233
+ );
234
+ }
235
+
236
+ // ── Paginated variant ─────────────────────────────────────────
237
+ if (variant === "paginated") {
238
+ const yearStart = todayYear - 10;
239
+ const yearEnd = todayYear + 15;
240
+ const years = Array.from({ length: yearEnd - yearStart + 1 }, (_, i) => yearStart + i);
241
+
242
+ const goPrev = () => {
243
+ const p = addMonths(viewYear, viewMonth, -1);
244
+ setViewYear(p.year);
245
+ setViewMonth(p.month);
246
+ };
247
+
248
+ const goNext = () => {
249
+ const n = addMonths(viewYear, viewMonth, 1);
250
+ setViewYear(n.year);
251
+ setViewMonth(n.month);
252
+ };
253
+
254
+ const goToday = () => {
255
+ setViewYear(todayYear);
256
+ setViewMonth(todayMonth);
257
+ };
258
+
259
+ const isCurrentMonth = viewYear === todayYear && viewMonth === todayMonth;
260
+
261
+ const monthStarted = dimPast &&
262
+ new Date(viewYear, viewMonth, 1) <= new Date(todayYear, todayMonth, todayDay);
263
+
264
+ return (
265
+ <div
266
+ className={["a1-calendar", "a1-calendar--paginated", className].filter(Boolean).join(" ")}
267
+ {...props}
268
+ >
269
+ <div className="a1-calendar__inner">
270
+ <div className="a1-calendar__nav">
271
+
272
+ <span className="a1-calendar__nav-wide">
273
+ <Button variant="tertiary" size="sm" icon={isRtl ? "chevron_right" : "chevron_left"} iconPosition="start" onClick={goPrev}>
274
+ {prevMonthLabel}
275
+ </Button>
276
+ </span>
277
+ <span className="a1-calendar__nav-narrow">
278
+ <IconButton icon={isRtl ? "chevron_right" : "chevron_left"} label={prevMonthLabel} variant="tertiary" onClick={goPrev} />
279
+ </span>
280
+
281
+ <div className="a1-calendar__nav-label">
282
+ <SelectField
283
+ className="a1-calendar__nav-field"
284
+ size="compact"
285
+ aria-label={selectMonthLabel}
286
+ value={viewMonth}
287
+ onChange={e => setViewMonth(Number(e.target.value))}
288
+ >
289
+ {MONTHS.map((name, i) => (
290
+ <option key={i} value={i}>{name}</option>
291
+ ))}
292
+ </SelectField>
293
+ <SelectField
294
+ className="a1-calendar__nav-field"
295
+ size="compact"
296
+ aria-label={selectYearLabel}
297
+ value={viewYear}
298
+ onChange={e => setViewYear(Number(e.target.value))}
299
+ >
300
+ {years.map(y => (
301
+ <option key={y} value={y}>{y}</option>
302
+ ))}
303
+ </SelectField>
304
+ {todayButton && (
305
+ <div className="a1-calendar__nav-today">
306
+ <span className="a1-calendar__nav-wide">
307
+ <Button
308
+ variant="secondary"
309
+ size="sm"
310
+ onClick={goToday}
311
+ disabled={isCurrentMonth}
312
+ >
313
+ {todayLabel}
314
+ </Button>
315
+ </span>
316
+ <span className="a1-calendar__nav-narrow">
317
+ <IconButton
318
+ icon="calendar_today"
319
+ label={todayLabel}
320
+ variant="secondary"
321
+ onClick={goToday}
322
+ disabled={isCurrentMonth}
323
+ />
324
+ </span>
325
+ </div>
326
+ )}
327
+ </div>
328
+
329
+ <span className="a1-calendar__nav-wide">
330
+ <Button variant="tertiary" size="sm" icon={isRtl ? "chevron_left" : "chevron_right"} iconPosition="end" onClick={goNext}>
331
+ {nextMonthLabel}
332
+ </Button>
333
+ </span>
334
+ <span className="a1-calendar__nav-narrow">
335
+ <IconButton icon={isRtl ? "chevron_left" : "chevron_right"} label={nextMonthLabel} variant="tertiary" onClick={goNext} />
336
+ </span>
337
+
338
+ </div>
339
+
340
+ {renderGrid(viewYear, viewMonth, monthStarted)}
341
+ </div>
342
+ </div>
343
+ );
344
+ }
345
+
346
+ // ── Scroll variant (default) ──────────────────────────────────
347
+ const half = Math.floor(monthsToShow / 2);
348
+ const months = Array.from({ length: monthsToShow }, (_, i) =>
349
+ addMonths(centerYear, centerMonth, i - half)
350
+ );
351
+
352
+ return (
353
+ <div
354
+ className={["a1-calendar", className].filter(Boolean).join(" ")}
355
+ {...props}
356
+ >
357
+ <div className="a1-calendar__inner">
358
+ {months.map(({ year, month }) => {
359
+ const isCurrent = year === todayYear && month === todayMonth;
360
+ const monthStarted = dimPast &&
361
+ new Date(year, month, 1) <= new Date(todayYear, todayMonth, todayDay);
362
+
363
+ return (
364
+ <section
365
+ key={`${year}-${month}`}
366
+ className={[
367
+ "a1-calendar__month",
368
+ isCurrent && "a1-calendar__month--current",
369
+ ].filter(Boolean).join(" ")}
370
+ ref={isCurrent ? currentMonthRef : undefined}
371
+ >
372
+ <h2 className="a1-calendar__month-heading">
373
+ <span className="a1-calendar__month-name">{MONTHS[month]}</span>
374
+ <span className="a1-calendar__year">{year}</span>
375
+ </h2>
376
+ {renderGrid(year, month, monthStarted)}
377
+ </section>
378
+ );
379
+ })}
380
+ </div>
381
+ </div>
382
+ );
383
+ }
@@ -0,0 +1,225 @@
1
+ /* ── Outer scroll shell — plain block, no containment ───────── */
2
+
3
+ .a1-calendar {
4
+ font-family: var(--component-paragraph-font-family);
5
+ }
6
+
7
+ /* ── Inner container — establishes the container query context ─ */
8
+
9
+ .a1-calendar__inner {
10
+ container-type: inline-size;
11
+ container-name: a1-calendar;
12
+ }
13
+
14
+ /* ── Month block ─────────────────────────────────────────── */
15
+
16
+ .a1-calendar__month {
17
+ padding-block-end: var(--component-calendar-month-gap);
18
+ }
19
+
20
+ .a1-calendar__month:last-child {
21
+ padding-block-end: 0;
22
+ }
23
+
24
+ /* ── Month heading ───────────────────────────────────────── */
25
+
26
+ .a1-calendar__month-heading {
27
+ display: flex;
28
+ align-items: baseline;
29
+ gap: var(--component-calendar-heading-gap);
30
+ padding-block: var(--component-calendar-heading-padding-block);
31
+ font-size: var(--semantic-font-size-heading-xs);
32
+ font-weight: var(--semantic-font-weight-heading);
33
+ line-height: var(--semantic-font-line-height-heading);
34
+ color: var(--semantic-color-text-default);
35
+ margin: 0;
36
+ }
37
+
38
+ .a1-calendar__year {
39
+ font-size: var(--semantic-font-size-body-md);
40
+ font-weight: var(--semantic-font-weight-body);
41
+ color: var(--semantic-color-text-muted);
42
+ }
43
+
44
+ /* ── Grid (table) ────────────────────────────────────────── */
45
+
46
+ .a1-calendar__grid {
47
+ width: 100%;
48
+ border-collapse: collapse;
49
+ table-layout: fixed;
50
+ }
51
+
52
+ /* ── Weekday header row ──────────────────────────────────── */
53
+
54
+ .a1-calendar__weekday {
55
+ font-size: var(--semantic-font-size-body-xs);
56
+ font-weight: var(--base-font-weight-medium);
57
+ color: var(--semantic-color-text-muted);
58
+ text-align: center;
59
+ padding-block: var(--component-calendar-cell-padding);
60
+ text-transform: uppercase;
61
+ letter-spacing: 0.06em;
62
+ border-block-end: 1px solid var(--semantic-color-border-subtle);
63
+ }
64
+
65
+ /* Three label lengths — show/hide via container queries below */
66
+ .a1-calendar__weekday-label--long { display: inline; }
67
+ .a1-calendar__weekday-label--short { display: none; }
68
+ .a1-calendar__weekday-label--letter { display: none; }
69
+
70
+ /* ── Day cells ───────────────────────────────────────────── */
71
+
72
+ .a1-calendar__day {
73
+ text-align: center;
74
+ padding: var(--component-calendar-cell-padding);
75
+ font-size: var(--semantic-font-size-body-sm);
76
+ color: var(--semantic-color-text-default);
77
+ vertical-align: middle;
78
+ border: 1px solid var(--semantic-color-border-subtle);
79
+ }
80
+
81
+ .a1-calendar__day--empty {
82
+ border: none;
83
+ pointer-events: none;
84
+ }
85
+
86
+ .a1-calendar__day-number {
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ width: var(--component-calendar-cell-size);
91
+ height: var(--component-calendar-cell-size);
92
+ border-radius: var(--component-calendar-cell-border-radius);
93
+ margin-inline: auto;
94
+ font-variant-numeric: tabular-nums;
95
+ line-height: 1;
96
+ }
97
+
98
+ /* Today highlight — fills the whole cell in full-size view */
99
+ .a1-calendar__day--today {
100
+ background: var(--semantic-color-action-background);
101
+ color: var(--semantic-color-text-inverse);
102
+ }
103
+
104
+ .a1-calendar__day--today .a1-calendar__day-number {
105
+ font-weight: var(--semantic-font-weight-heading);
106
+ }
107
+
108
+ /* Past dates — background differentiates; text stays at full contrast (~16:1) */
109
+ .a1-calendar__day--past {
110
+ background: var(--semantic-color-surface-raised);
111
+ }
112
+
113
+ /* ── Paginated variant — nav bar ─────────────────────────── */
114
+
115
+ .a1-calendar__nav {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: space-between;
119
+ padding-block: var(--component-calendar-heading-padding-block);
120
+ border-block-end: 1px solid var(--semantic-color-border-subtle);
121
+ }
122
+
123
+ .a1-calendar__nav-label {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: var(--component-calendar-heading-gap);
127
+ }
128
+
129
+ /* Responsive nav controls: Button (wide) ↔ IconButton (narrow) */
130
+ .a1-calendar__nav-wide { display: flex; }
131
+ .a1-calendar__nav-narrow { display: none; }
132
+
133
+ /* Today button — visually separated from the selects */
134
+ .a1-calendar__nav-today {
135
+ display: flex;
136
+ align-items: center;
137
+ margin-inline-start: var(--component-calendar-heading-gap);
138
+ padding-inline-start: var(--component-calendar-heading-gap);
139
+ border-inline-start: 1px solid var(--semantic-color-border-subtle);
140
+ }
141
+
142
+ /* SelectField context overrides — strip layout width so selects size to content */
143
+ .a1-calendar__nav-field,
144
+ .a1-calendar__nav-field .a1-field__control,
145
+ .a1-calendar__nav-field .a1-field__select {
146
+ width: auto;
147
+ }
148
+
149
+ /* ── Container: Medium (< 400px) ─────────────────────────── */
150
+
151
+ @container a1-calendar (max-width: 479px) {
152
+ .a1-calendar__month-heading {
153
+ font-size: var(--semantic-font-size-body-lg);
154
+ padding-block: var(--component-calendar-heading-padding-block-compact);
155
+ }
156
+
157
+ .a1-calendar__year {
158
+ font-size: var(--semantic-font-size-body-sm);
159
+ }
160
+
161
+ .a1-calendar__weekday,
162
+ .a1-calendar__day {
163
+ padding: var(--component-calendar-cell-padding-compact);
164
+ }
165
+
166
+ .a1-calendar__day {
167
+ border: none;
168
+ }
169
+
170
+ /* Today: revert to circle-only highlight in narrow view */
171
+ .a1-calendar__day--today {
172
+ background: none;
173
+ color: var(--semantic-color-text-default);
174
+ }
175
+
176
+ .a1-calendar__day--today .a1-calendar__day-number {
177
+ background: var(--semantic-color-action-background);
178
+ color: var(--semantic-color-text-inverse);
179
+ }
180
+
181
+ .a1-calendar__nav {
182
+ padding-block: var(--component-calendar-heading-padding-block-compact);
183
+ }
184
+
185
+ /* Swap to icon-only buttons in narrow view */
186
+ .a1-calendar__nav-wide { display: none; }
187
+ .a1-calendar__nav-narrow { display: flex; }
188
+
189
+ .a1-calendar__weekday-label--long { display: none; }
190
+ .a1-calendar__weekday-label--short { display: inline; }
191
+ .a1-calendar__weekday-label--letter { display: none; }
192
+ }
193
+
194
+ /* ── Container: Compact (< 240px) ────────────────────────── */
195
+
196
+ @container a1-calendar (max-width: 319px) {
197
+ .a1-calendar__month-heading {
198
+ font-size: var(--semantic-font-size-body-md);
199
+ gap: var(--component-calendar-cell-padding-compact);
200
+ }
201
+
202
+ .a1-calendar__year {
203
+ font-size: var(--semantic-font-size-body-xl);
204
+ }
205
+
206
+ .a1-calendar__weekday {
207
+ font-size: var(--semantic-font-size-body-xs);
208
+ padding: var(--component-calendar-cell-padding-minimal);
209
+ letter-spacing: 0;
210
+ }
211
+
212
+ .a1-calendar__day {
213
+ padding: var(--component-calendar-cell-padding-minimal);
214
+ font-size: var(--semantic-font-size-body-xs);
215
+ }
216
+
217
+ .a1-calendar__day-number {
218
+ width: var(--component-calendar-cell-size-compact);
219
+ height: var(--component-calendar-cell-size-compact);
220
+ }
221
+
222
+ .a1-calendar__weekday-label--long { display: none; }
223
+ .a1-calendar__weekday-label--short { display: none; }
224
+ .a1-calendar__weekday-label--letter { display: inline; }
225
+ }
@@ -1,29 +1,67 @@
1
1
  import "./card.css";
2
2
  import { Icon } from "../icon/Icon.jsx";
3
3
 
4
- const shadows = ["none", "xs", "sm", "md", "lg", "xl"];
4
+ const HERO_COLORS = {
5
+ action: "var(--semantic-color-action-background)",
6
+ neutral: "var(--semantic-color-surface-inverse)",
7
+ info: "var(--semantic-color-status-info-background)",
8
+ success: "var(--semantic-color-status-success-background)",
9
+ warn: "var(--semantic-color-status-warn-background)",
10
+ error: "var(--semantic-color-status-error-background)",
11
+ };
12
+
13
+ const VALID_ICON_DISPLAY = ["none", "default", "hero"];
5
14
 
6
15
  export function Card({
7
- as: Component = "div",
8
- shadow = "sm",
16
+ as,
17
+ bare = false,
18
+ variant = "default",
19
+ href,
9
20
  icon,
21
+ iconDisplay = "default",
22
+ heroColor = "action",
10
23
  className = "",
11
24
  children,
12
25
  ...props
13
26
  }) {
14
- const resolvedShadow = shadows.includes(shadow) ? shadow : "sm";
15
- const classes = ["a1-card", `a1-card--shadow-${resolvedShadow}`, className]
27
+ const isNavigation = variant === "navigation";
28
+ const Component = as ?? (isNavigation ? (href ? "a" : "button") : "div");
29
+
30
+ const resolvedDisplay = icon && VALID_ICON_DISPLAY.includes(iconDisplay)
31
+ ? iconDisplay
32
+ : "none";
33
+
34
+ const classes = [
35
+ "a1-card",
36
+ bare && "a1-card--bare",
37
+ isNavigation && "a1-card--navigation",
38
+ resolvedDisplay === "hero" && "a1-card--has-hero",
39
+ resolvedDisplay === "default" && "a1-card--has-icon",
40
+ className,
41
+ ]
16
42
  .filter(Boolean)
17
43
  .join(" ");
18
44
 
45
+ const heroBg = HERO_COLORS[heroColor] ?? heroColor;
46
+ const interactiveProps = isNavigation && Component === "button" && !props.type
47
+ ? { type: "button" }
48
+ : {};
49
+
19
50
  return (
20
- <Component className={classes} {...props}>
21
- {icon && (
22
- <span className="a1-card__icon" aria-hidden="true">
23
- <Icon name={icon} />
24
- </span>
25
- )}
26
- {children}
51
+ <Component className={classes} href={href} {...interactiveProps} {...props}>
52
+ <div className="a1-card__layout">
53
+ {resolvedDisplay === "hero" && (
54
+ <div className="a1-card__hero" style={{ "--a1-card-hero-bg": heroBg }}>
55
+ <Icon name={icon} aria-hidden="true" />
56
+ </div>
57
+ )}
58
+ {resolvedDisplay === "default" && (
59
+ <span className="a1-card__icon" aria-hidden="true">
60
+ <Icon name={icon} />
61
+ </span>
62
+ )}
63
+ <div className="a1-card__content">{children}</div>
64
+ </div>
27
65
  </Component>
28
66
  );
29
67
  }