@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.
- package/guidelines/Guidelines.md +228 -0
- package/package.json +4 -1
- package/src/breakpoints.css +29 -0
- package/src/color-scheme.css +586 -24
- package/src/components/accordion/Accordion.jsx +80 -0
- package/src/components/accordion/accordion.css +118 -0
- package/src/components/banner/Banner.jsx +66 -0
- package/src/components/banner/banner.css +205 -0
- package/src/components/bleed/Bleed.jsx +27 -0
- package/src/components/bleed/bleed.css +5 -0
- package/src/components/blockquote/Blockquote.jsx +40 -0
- package/src/components/blockquote/blockquote.css +166 -0
- package/src/components/breadcrumb/Breadcrumb.jsx +82 -0
- package/src/components/breadcrumb/breadcrumb.css +133 -0
- package/src/components/button/button.css +42 -12
- package/src/components/button-container/ButtonContainer.jsx +20 -1
- package/src/components/button-container/button-container.css +19 -1
- package/src/components/calendar/Calendar.jsx +383 -0
- package/src/components/calendar/calendar.css +225 -0
- package/src/components/card/Card.jsx +50 -12
- package/src/components/card/card.css +178 -14
- package/src/components/checkbox-group/CheckboxGroup.jsx +120 -0
- package/src/components/checkbox-group/checkbox-group.css +304 -0
- package/src/components/cluster/Cluster.jsx +52 -0
- package/src/components/cluster/cluster.css +9 -0
- package/src/components/code/Code.jsx +135 -0
- package/src/components/code/code.css +60 -0
- package/src/components/data-table/DataTable.jsx +721 -0
- package/src/components/data-table/DataTableFilters.jsx +339 -0
- package/src/components/data-table/data-table-filters.css +259 -0
- package/src/components/data-table/data-table.css +425 -0
- package/src/components/dialog/Dialog.jsx +45 -2
- package/src/components/dialog/dialog.css +13 -4
- package/src/components/divider/Divider.jsx +64 -0
- package/src/components/divider/divider.css +170 -0
- package/src/components/field/CreditCardField.jsx +131 -0
- package/src/components/field/DateField.jsx +11 -0
- package/src/components/field/NumberField.jsx +11 -0
- package/src/components/field/PhoneField.jsx +107 -0
- package/src/components/field/SelectField.jsx +86 -0
- package/src/components/field/TextField.jsx +83 -0
- package/src/components/field/TextareaField.jsx +147 -0
- package/src/components/field/TimeField.jsx +11 -0
- package/src/components/field/ZipField.jsx +114 -0
- package/src/components/field/credit-card.css +30 -0
- package/src/components/field/field.css +380 -0
- package/src/components/field/textarea-field.css +185 -0
- package/src/components/field-row/FieldRow.jsx +23 -0
- package/src/components/field-row/field-row.css +51 -0
- package/src/components/fieldset/Fieldset.jsx +49 -0
- package/src/components/fieldset/fieldset.css +75 -0
- package/src/components/figure/Figure.jsx +63 -0
- package/src/components/figure/figure.css +97 -0
- package/src/components/grid/Grid.jsx +36 -2
- package/src/components/grid/grid.css +129 -4
- package/src/components/heading/Heading.jsx +41 -1
- package/src/components/heading/heading.css +65 -4
- package/src/components/icon/icon.css +1 -0
- package/src/components/icon-button/icon-button.css +1 -0
- package/src/components/inline/inline.css +51 -0
- package/src/components/inline-editable/InlineEditable.jsx +77 -0
- package/src/components/inline-editable/inline-editable.css +47 -0
- package/src/components/inset/Inset.jsx +27 -0
- package/src/components/inset/inset.css +6 -0
- package/src/components/labels/Labels.jsx +5 -5
- package/src/components/link/Link.jsx +2 -3
- package/src/components/link/link.css +30 -1
- package/src/components/list/List.jsx +92 -0
- package/src/components/list/list.css +178 -0
- package/src/components/menu/Menu.jsx +243 -10
- package/src/components/menu/menu.css +157 -17
- package/src/components/message/Message.jsx +25 -50
- package/src/components/message/message.css +50 -33
- package/src/components/notification/Notification.jsx +1 -1
- package/src/components/page-layout/PageLayout.jsx +16 -1
- package/src/components/page-layout/page-layout.css +97 -4
- package/src/components/page-nav/PageNav.jsx +110 -0
- package/src/components/page-nav/page-nav.css +167 -0
- package/src/components/paragraph/Paragraph.jsx +35 -2
- package/src/components/paragraph/paragraph.css +38 -1
- package/src/components/radio-group/RadioGroup.jsx +121 -0
- package/src/components/radio-group/radio-group.css +268 -0
- package/src/components/section/Section.jsx +108 -0
- package/src/components/section/section.css +280 -0
- package/src/components/segmented-control/SegmentedControl.jsx +4 -0
- package/src/components/segmented-control/segmented.css +13 -0
- package/src/components/side-nav/SideNav.jsx +29 -9
- package/src/components/side-nav/scrim.css +1 -1
- package/src/components/side-nav/side-nav.css +70 -32
- package/src/components/snackbar/Snackbar.jsx +56 -0
- package/src/components/snackbar/snackbar.css +113 -0
- package/src/components/spacer/Spacer.jsx +36 -0
- package/src/components/spacer/spacer.css +44 -0
- package/src/components/stack/Stack.jsx +100 -0
- package/src/components/stack/stack.css +37 -0
- package/src/components/switch/Switch.jsx +114 -0
- package/src/components/switch/switch.css +276 -0
- package/src/components/system-banner/SystemBanner.jsx +57 -0
- package/src/components/system-banner/system-banner.css +118 -0
- package/src/components/tabs/Tabs.jsx +96 -28
- package/src/components/tabs/tabs.css +352 -15
- package/src/components/token-select/TokenSelect.jsx +159 -0
- package/src/components/token-select/token-select.css +110 -0
- package/src/components/top-header/TopHeader.jsx +641 -0
- package/src/components/top-header/top-header.css +337 -0
- package/src/illustrations/ComponentThumbnails.jsx +227 -0
- package/src/index.js +41 -5
- package/src/themes.css +256 -5
- package/src/tokens.css +919 -0
- package/src/utilities/spacing.css +8 -0
- 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
|
|
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
|
|
8
|
-
|
|
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
|
|
15
|
-
const
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
}
|