@robr0/design-system 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +53 -2
- package/README.md +36 -5
- package/components/AgentPlan/AgentPlan.css +231 -0
- package/components/AgentPlan/AgentPlan.d.ts +41 -0
- package/components/AgentPlan/AgentPlan.js +99 -0
- package/components/AgentStatus/AgentStatus.css +26 -11
- package/components/Card/Card.css +9 -0
- package/components/Card/Card.d.ts +6 -0
- package/components/Card/Card.js +2 -1
- package/components/Composer/Composer.css +18 -0
- package/components/Composer/Composer.d.ts +3 -0
- package/components/Composer/Composer.js +12 -1
- package/components/DataTable/DataTable.css +105 -0
- package/components/DataTable/DataTable.d.ts +81 -0
- package/components/DataTable/DataTable.js +235 -0
- package/components/EventCalendar/EventCalendar.css +328 -0
- package/components/EventCalendar/EventCalendar.d.ts +51 -0
- package/components/EventCalendar/EventCalendar.js +213 -0
- package/components/ModelPicker/ModelPicker.css +238 -0
- package/components/ModelPicker/ModelPicker.d.ts +60 -0
- package/components/ModelPicker/ModelPicker.js +217 -0
- package/components/NotificationCenter/NotificationCenter.css +262 -0
- package/components/NotificationCenter/NotificationCenter.d.ts +72 -0
- package/components/NotificationCenter/NotificationCenter.js +128 -0
- package/components/Prose/Prose.css +18 -1
- package/components/Reasoning/Reasoning.css +19 -4
- package/components/ShaderField/ShaderField.css +24 -0
- package/components/ShaderField/ShaderField.d.ts +68 -0
- package/components/ShaderField/ShaderField.js +54 -0
- package/components/ShaderField/field.glsl.d.ts +39 -0
- package/components/ShaderField/field.glsl.js +178 -0
- package/components/ShaderField/useShaderField.d.ts +103 -0
- package/components/ShaderField/useShaderField.js +409 -0
- package/components/Timeline/Timeline.css +17 -0
- package/components/Timeline/Timeline.d.ts +2 -0
- package/components/Timeline/Timeline.js +1 -0
- package/components/registry.json +48 -0
- package/components/registry.json.d.ts +48 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +6 -0
- package/index.js +19 -0
- package/package.json +5 -1
- package/tokens/registry.json +1 -0
- package/tokens/registry.json.d.ts +1 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +1 -1
- package/tokens/tokens-light.css +2 -2
- package/tokens/tokens-motion.css +2 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
EVENT CALENDAR COMPONENT
|
|
3
|
+
Month-at-a-glance grid with event pills.
|
|
4
|
+
Rows share the height of the fullest day in
|
|
5
|
+
their week (grid-auto-rows), so the calendar
|
|
6
|
+
grows with its content instead of fixing a
|
|
7
|
+
cell height.
|
|
8
|
+
|
|
9
|
+
Event accents use the core accent roles —
|
|
10
|
+
never the action teal, which stays reserved
|
|
11
|
+
for actions.
|
|
12
|
+
============================================ */
|
|
13
|
+
|
|
14
|
+
.ds-event-calendar {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--gap-sm);
|
|
18
|
+
width: 100%;
|
|
19
|
+
background-color: var(--color-bg-container-primary);
|
|
20
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
21
|
+
border-radius: var(--radius-md);
|
|
22
|
+
padding: var(--padding-md);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ============================================
|
|
26
|
+
HEADER
|
|
27
|
+
============================================ */
|
|
28
|
+
|
|
29
|
+
.ds-event-calendar__header {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: var(--gap-sm);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ds-event-calendar__month {
|
|
36
|
+
flex: 1 1 auto;
|
|
37
|
+
min-width: 0;
|
|
38
|
+
color: var(--color-text-primary);
|
|
39
|
+
font-family: var(--font-title-body-family);
|
|
40
|
+
font-size: var(--font-title-body-size);
|
|
41
|
+
font-weight: var(--font-title-body-weight);
|
|
42
|
+
line-height: var(--font-title-body-line-height);
|
|
43
|
+
letter-spacing: var(--font-title-body-letter-spacing);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ds-event-calendar__nav {
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: var(--gap-xxs);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ds-event-calendar__nav-btn {
|
|
52
|
+
--icon-size: var(--icon-size-sm);
|
|
53
|
+
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
padding: var(--padding-xxs);
|
|
58
|
+
background-color: var(--color-action-passive-bg);
|
|
59
|
+
border: none;
|
|
60
|
+
border-radius: var(--radius-full);
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
color: var(--color-icon-primary);
|
|
63
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ds-event-calendar__nav-btn:hover {
|
|
67
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ds-event-calendar__nav-btn:focus-visible {
|
|
71
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
72
|
+
outline-offset: 2px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.ds-event-calendar__actions {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: var(--gap-sm);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ============================================
|
|
82
|
+
WEEKDAY ROW
|
|
83
|
+
============================================ */
|
|
84
|
+
|
|
85
|
+
.ds-event-calendar__weekdays {
|
|
86
|
+
display: grid;
|
|
87
|
+
grid-template-columns: repeat(7, 1fr);
|
|
88
|
+
gap: var(--gap-xxs);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ds-event-calendar__weekday {
|
|
92
|
+
padding: var(--padding-xxs) var(--padding-xs);
|
|
93
|
+
color: var(--color-text-tertiary);
|
|
94
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
95
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
96
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
97
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
98
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ============================================
|
|
102
|
+
GRID
|
|
103
|
+
============================================ */
|
|
104
|
+
|
|
105
|
+
.ds-event-calendar__grid {
|
|
106
|
+
display: grid;
|
|
107
|
+
grid-template-columns: repeat(7, 1fr);
|
|
108
|
+
grid-auto-rows: 1fr;
|
|
109
|
+
gap: var(--gap-xxs);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.ds-event-calendar__cell {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
gap: var(--gap-xxs);
|
|
116
|
+
min-width: 0;
|
|
117
|
+
padding: var(--padding-xxs);
|
|
118
|
+
background-color: var(--color-bg-container-secondary);
|
|
119
|
+
border-radius: var(--radius-sm);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ds-event-calendar__cell--outside {
|
|
123
|
+
background-color: var(--color-bg-container-primary-transparent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ds-event-calendar__cell--outside .ds-event-calendar__day {
|
|
127
|
+
color: var(--color-text-tertiary);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ============================================
|
|
131
|
+
DAY NUMBER
|
|
132
|
+
============================================ */
|
|
133
|
+
|
|
134
|
+
.ds-event-calendar__day {
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
align-self: flex-start;
|
|
139
|
+
min-width: var(--icon-size-md);
|
|
140
|
+
height: var(--icon-size-md);
|
|
141
|
+
padding: 0 var(--padding-xxs);
|
|
142
|
+
background: none;
|
|
143
|
+
border: none;
|
|
144
|
+
border-radius: var(--radius-full);
|
|
145
|
+
color: var(--color-text-secondary);
|
|
146
|
+
font-family: var(--font-paragraph-sm-family);
|
|
147
|
+
font-size: var(--font-paragraph-sm-size);
|
|
148
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
149
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
150
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
151
|
+
font-variant-numeric: tabular-nums;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
button.ds-event-calendar__day {
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
button.ds-event-calendar__day:hover {
|
|
160
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
button.ds-event-calendar__day:focus-visible {
|
|
164
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
165
|
+
outline-offset: 2px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Today — inverse chip on the day number, like DatePicker's today ring
|
|
169
|
+
but filled so it reads at calendar density */
|
|
170
|
+
.ds-event-calendar__cell--today .ds-event-calendar__day {
|
|
171
|
+
background-color: var(--color-bg-page-inverse);
|
|
172
|
+
color: var(--color-text-on-inverse);
|
|
173
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
174
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
175
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ============================================
|
|
179
|
+
EVENT PILLS
|
|
180
|
+
============================================ */
|
|
181
|
+
|
|
182
|
+
.ds-event-calendar__events {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: var(--gap-xxs);
|
|
186
|
+
min-width: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ds-event-calendar__event {
|
|
190
|
+
--ds-event-accent: var(--color-status-neutral-border);
|
|
191
|
+
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: var(--gap-xxs);
|
|
195
|
+
min-width: 0;
|
|
196
|
+
padding: var(--padding-xxxs) var(--padding-xs);
|
|
197
|
+
background-color: var(--color-bg-container-primary);
|
|
198
|
+
border: none;
|
|
199
|
+
border-radius: var(--radius-xs);
|
|
200
|
+
text-align: left;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
button.ds-event-calendar__event {
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
button.ds-event-calendar__event:hover {
|
|
209
|
+
background-color: var(--color-bg-container-tertiary);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
button.ds-event-calendar__event:focus-visible {
|
|
213
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
214
|
+
outline-offset: -2px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.ds-event-calendar__event-dot {
|
|
218
|
+
flex: none;
|
|
219
|
+
width: var(--gap-xs);
|
|
220
|
+
height: var(--gap-xs);
|
|
221
|
+
border-radius: var(--radius-full);
|
|
222
|
+
background-color: var(--ds-event-accent);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.ds-event-calendar__event-title {
|
|
226
|
+
flex: 1 1 auto;
|
|
227
|
+
min-width: 0;
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
text-overflow: ellipsis;
|
|
230
|
+
white-space: nowrap;
|
|
231
|
+
color: var(--color-text-primary);
|
|
232
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
233
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
234
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
235
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
236
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.ds-event-calendar__event-time {
|
|
240
|
+
flex: none;
|
|
241
|
+
color: var(--color-text-tertiary);
|
|
242
|
+
font-family: var(--font-paragraph-sm-family);
|
|
243
|
+
font-size: var(--font-paragraph-sm-size);
|
|
244
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
245
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
246
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
247
|
+
font-variant-numeric: tabular-nums;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* ============================================
|
|
251
|
+
EVENT ACCENTS — core accent roles only
|
|
252
|
+
============================================ */
|
|
253
|
+
|
|
254
|
+
.ds-event-calendar__event--coral {
|
|
255
|
+
--ds-event-accent: var(--color-core-accent-coral);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.ds-event-calendar__event--violet {
|
|
259
|
+
--ds-event-accent: var(--color-core-accent-violet);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.ds-event-calendar__event--cobalt {
|
|
263
|
+
--ds-event-accent: var(--color-core-accent-cobalt);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.ds-event-calendar__event--amber {
|
|
267
|
+
--ds-event-accent: var(--color-core-accent-amber);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.ds-event-calendar__event--gold {
|
|
271
|
+
--ds-event-accent: var(--color-core-accent-gold);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.ds-event-calendar__event--mint {
|
|
275
|
+
--ds-event-accent: var(--color-core-accent-mint);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* ============================================
|
|
279
|
+
OVERFLOW
|
|
280
|
+
============================================ */
|
|
281
|
+
|
|
282
|
+
.ds-event-calendar__more {
|
|
283
|
+
align-self: flex-start;
|
|
284
|
+
padding: var(--padding-xxxs) var(--padding-xs);
|
|
285
|
+
background: none;
|
|
286
|
+
border: none;
|
|
287
|
+
color: var(--color-text-tertiary);
|
|
288
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
289
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
290
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
291
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
292
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
button.ds-event-calendar__more {
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
border-radius: var(--radius-xs);
|
|
298
|
+
transition:
|
|
299
|
+
background-color var(--motion-duration-fast) var(--motion-ease-standard),
|
|
300
|
+
color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
button.ds-event-calendar__more:hover {
|
|
304
|
+
background-color: var(--color-bg-container-tertiary);
|
|
305
|
+
color: var(--color-text-secondary);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
button.ds-event-calendar__more:focus-visible {
|
|
309
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
310
|
+
outline-offset: -2px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* ============================================
|
|
314
|
+
RESPONSIVE
|
|
315
|
+
At phone widths the pills drop their time so
|
|
316
|
+
titles keep at least a few characters.
|
|
317
|
+
============================================ */
|
|
318
|
+
|
|
319
|
+
@media (max-width: 768px) {
|
|
320
|
+
.ds-event-calendar__event-time {
|
|
321
|
+
display: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.ds-event-calendar__weekday {
|
|
325
|
+
padding: var(--padding-xxs) 0;
|
|
326
|
+
text-align: center;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type EventCalendarColor = 'neutral' | 'coral' | 'violet' | 'cobalt' | 'amber' | 'gold' | 'mint';
|
|
3
|
+
export interface EventCalendarEvent {
|
|
4
|
+
/** Unique event identifier. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** The day the event falls on (YYYY-MM-DD). */
|
|
7
|
+
date: string;
|
|
8
|
+
/** Short label shown in the event pill. */
|
|
9
|
+
title: string;
|
|
10
|
+
/** Start time as already-formatted text like "09:30". Untimed events sort first. */
|
|
11
|
+
time?: string;
|
|
12
|
+
/** Accent for the pill's dot, from the core accent roles. Defaults to `neutral`. */
|
|
13
|
+
color?: EventCalendarColor;
|
|
14
|
+
}
|
|
15
|
+
/** Props owned by EventCalendar itself — everything else falls through to the root element. */
|
|
16
|
+
type EventCalendarOwnProps = {
|
|
17
|
+
/** The events to place on the grid, including any that fall on the visible leading and trailing days of the neighbouring months. */
|
|
18
|
+
events?: EventCalendarEvent[];
|
|
19
|
+
/** Shown month (YYYY-MM) for controlled use. Pair with `onMonthChange`. */
|
|
20
|
+
month?: string;
|
|
21
|
+
/** Initially shown month (YYYY-MM) for uncontrolled use. Defaults to the current month. */
|
|
22
|
+
defaultMonth?: string;
|
|
23
|
+
/** Fires with the newly shown month (YYYY-MM) after prev/next navigation. */
|
|
24
|
+
onMonthChange?: (month: string) => void;
|
|
25
|
+
/** Event pills shown per day before the rest collapse into "+N more". */
|
|
26
|
+
maxEventsPerDay?: number;
|
|
27
|
+
/** Fires with the clicked event. Pills only render as buttons when this is set. */
|
|
28
|
+
onEventClick?: (event: EventCalendarEvent) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Fires with the clicked day (YYYY-MM-DD) — from the day number, and from
|
|
31
|
+
* the "+N more" overflow row. Both only become buttons when this is set.
|
|
32
|
+
*/
|
|
33
|
+
onDateClick?: (date: string) => void;
|
|
34
|
+
/** Trailing header slot, e.g. a "New event" Button. */
|
|
35
|
+
actions?: React.ReactNode;
|
|
36
|
+
/** Additional CSS classes */
|
|
37
|
+
className?: string;
|
|
38
|
+
};
|
|
39
|
+
export interface EventCalendarProps extends EventCalendarOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof EventCalendarOwnProps> {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* EventCalendar is the month-at-a-glance view: a full month grid with event
|
|
43
|
+
* pills on their days, an overflow row once a day is full, and prev/next
|
|
44
|
+
* month navigation. DatePicker selects a date; this one shows a schedule.
|
|
45
|
+
*
|
|
46
|
+
* The calendar owns no event data semantics — it places what it is given.
|
|
47
|
+
* Filtering, fetching, and what clicking an event means stay with the
|
|
48
|
+
* consumer.
|
|
49
|
+
*/
|
|
50
|
+
export declare const EventCalendar: React.ForwardRefExoticComponent<EventCalendarProps & React.RefAttributes<HTMLDivElement>>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo, useState } from "react";
|
|
3
|
+
import "./EventCalendar.css";
|
|
4
|
+
import "../../fonts/material-symbols.css";
|
|
5
|
+
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
6
|
+
const MONTHS = [
|
|
7
|
+
"January",
|
|
8
|
+
"February",
|
|
9
|
+
"March",
|
|
10
|
+
"April",
|
|
11
|
+
"May",
|
|
12
|
+
"June",
|
|
13
|
+
"July",
|
|
14
|
+
"August",
|
|
15
|
+
"September",
|
|
16
|
+
"October",
|
|
17
|
+
"November",
|
|
18
|
+
"December"
|
|
19
|
+
];
|
|
20
|
+
function parseMonth(str) {
|
|
21
|
+
const [y, m] = str.split("-").map(Number);
|
|
22
|
+
return [y, m - 1];
|
|
23
|
+
}
|
|
24
|
+
function formatMonth(year, monthIndex) {
|
|
25
|
+
return `${year}-${String(monthIndex + 1).padStart(2, "0")}`;
|
|
26
|
+
}
|
|
27
|
+
function formatDate(date) {
|
|
28
|
+
const y = date.getFullYear();
|
|
29
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
30
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
31
|
+
return `${y}-${m}-${d}`;
|
|
32
|
+
}
|
|
33
|
+
const EventCalendar = React.forwardRef(
|
|
34
|
+
({
|
|
35
|
+
events = [],
|
|
36
|
+
month,
|
|
37
|
+
defaultMonth,
|
|
38
|
+
onMonthChange,
|
|
39
|
+
maxEventsPerDay = 3,
|
|
40
|
+
onEventClick,
|
|
41
|
+
onDateClick,
|
|
42
|
+
actions,
|
|
43
|
+
className = "",
|
|
44
|
+
...rest
|
|
45
|
+
}, ref) => {
|
|
46
|
+
const baseClass = "ds-event-calendar";
|
|
47
|
+
const today = useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
48
|
+
const isControlled = month !== void 0;
|
|
49
|
+
const [uncontrolledMonth, setUncontrolledMonth] = useState(
|
|
50
|
+
defaultMonth ?? formatMonth(today.getFullYear(), today.getMonth())
|
|
51
|
+
);
|
|
52
|
+
const currentMonth = isControlled ? month : uncontrolledMonth;
|
|
53
|
+
const [viewYear, viewMonth] = parseMonth(currentMonth);
|
|
54
|
+
const navigate = (offset) => {
|
|
55
|
+
const next = new Date(viewYear, viewMonth + offset, 1);
|
|
56
|
+
const value = formatMonth(next.getFullYear(), next.getMonth());
|
|
57
|
+
if (!isControlled) setUncontrolledMonth(value);
|
|
58
|
+
onMonthChange?.(value);
|
|
59
|
+
};
|
|
60
|
+
const eventsByDate = useMemo(() => {
|
|
61
|
+
const map = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
const list = map.get(event.date) ?? [];
|
|
64
|
+
list.push(event);
|
|
65
|
+
map.set(event.date, list);
|
|
66
|
+
}
|
|
67
|
+
for (const list of map.values()) {
|
|
68
|
+
list.sort((a, b) => {
|
|
69
|
+
if (!a.time && !b.time) return 0;
|
|
70
|
+
if (!a.time) return -1;
|
|
71
|
+
if (!b.time) return 1;
|
|
72
|
+
return a.time.localeCompare(b.time);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return map;
|
|
76
|
+
}, [events]);
|
|
77
|
+
const firstDay = new Date(viewYear, viewMonth, 1).getDay();
|
|
78
|
+
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
|
|
79
|
+
const weekCount = Math.ceil((firstDay + daysInMonth) / 7);
|
|
80
|
+
const cells = [];
|
|
81
|
+
for (let i = 0; i < weekCount * 7; i++) {
|
|
82
|
+
const date = new Date(viewYear, viewMonth, i - firstDay + 1);
|
|
83
|
+
cells.push({ date, inMonth: date.getMonth() === viewMonth });
|
|
84
|
+
}
|
|
85
|
+
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
87
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
|
|
88
|
+
/* @__PURE__ */ jsxs("span", { className: `${baseClass}__month`, children: [
|
|
89
|
+
MONTHS[viewMonth],
|
|
90
|
+
" ",
|
|
91
|
+
viewYear
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__nav`, children: [
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"button",
|
|
96
|
+
{
|
|
97
|
+
type: "button",
|
|
98
|
+
className: `${baseClass}__nav-btn material-symbols-rounded`,
|
|
99
|
+
onClick: () => navigate(-1),
|
|
100
|
+
"aria-label": "Previous month",
|
|
101
|
+
children: "chevron_left"
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
/* @__PURE__ */ jsx(
|
|
105
|
+
"button",
|
|
106
|
+
{
|
|
107
|
+
type: "button",
|
|
108
|
+
className: `${baseClass}__nav-btn material-symbols-rounded`,
|
|
109
|
+
onClick: () => navigate(1),
|
|
110
|
+
"aria-label": "Next month",
|
|
111
|
+
children: "chevron_right"
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
] }),
|
|
115
|
+
actions && /* @__PURE__ */ jsx("div", { className: `${baseClass}__actions`, children: actions })
|
|
116
|
+
] }),
|
|
117
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__weekdays`, "aria-hidden": "true", children: DAYS.map((day) => /* @__PURE__ */ jsx("div", { className: `${baseClass}__weekday`, children: day }, day)) }),
|
|
118
|
+
/* @__PURE__ */ jsx(
|
|
119
|
+
"div",
|
|
120
|
+
{
|
|
121
|
+
className: `${baseClass}__grid`,
|
|
122
|
+
role: "group",
|
|
123
|
+
"aria-label": `${MONTHS[viewMonth]} ${viewYear}`,
|
|
124
|
+
children: cells.map(({ date, inMonth }) => {
|
|
125
|
+
const dateStr = formatDate(date);
|
|
126
|
+
const dayEvents = eventsByDate.get(dateStr) ?? [];
|
|
127
|
+
const visible = dayEvents.slice(0, maxEventsPerDay);
|
|
128
|
+
const overflow = dayEvents.length - visible.length;
|
|
129
|
+
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
|
130
|
+
const dayLabel = date.toLocaleDateString(void 0, {
|
|
131
|
+
weekday: "long",
|
|
132
|
+
year: "numeric",
|
|
133
|
+
month: "long",
|
|
134
|
+
day: "numeric"
|
|
135
|
+
});
|
|
136
|
+
const cellClasses = [
|
|
137
|
+
`${baseClass}__cell`,
|
|
138
|
+
!inMonth ? `${baseClass}__cell--outside` : "",
|
|
139
|
+
isToday ? `${baseClass}__cell--today` : ""
|
|
140
|
+
].filter(Boolean).join(" ");
|
|
141
|
+
return /* @__PURE__ */ jsxs("div", { className: cellClasses, children: [
|
|
142
|
+
onDateClick ? /* @__PURE__ */ jsx(
|
|
143
|
+
"button",
|
|
144
|
+
{
|
|
145
|
+
type: "button",
|
|
146
|
+
className: `${baseClass}__day`,
|
|
147
|
+
onClick: () => onDateClick(dateStr),
|
|
148
|
+
"aria-label": dayLabel,
|
|
149
|
+
"aria-current": isToday ? "date" : void 0,
|
|
150
|
+
children: date.getDate()
|
|
151
|
+
}
|
|
152
|
+
) : /* @__PURE__ */ jsx(
|
|
153
|
+
"span",
|
|
154
|
+
{
|
|
155
|
+
className: `${baseClass}__day`,
|
|
156
|
+
"aria-label": dayLabel,
|
|
157
|
+
"aria-current": isToday ? "date" : void 0,
|
|
158
|
+
children: date.getDate()
|
|
159
|
+
}
|
|
160
|
+
),
|
|
161
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__events`, children: [
|
|
162
|
+
visible.map((event) => {
|
|
163
|
+
const pillClasses = [
|
|
164
|
+
`${baseClass}__event`,
|
|
165
|
+
`${baseClass}__event--${event.color ?? "neutral"}`
|
|
166
|
+
].join(" ");
|
|
167
|
+
const pillContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__event-dot`, "aria-hidden": "true" }),
|
|
169
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__event-title`, children: event.title }),
|
|
170
|
+
event.time && /* @__PURE__ */ jsx("span", { className: `${baseClass}__event-time`, children: event.time })
|
|
171
|
+
] });
|
|
172
|
+
return onEventClick ? /* @__PURE__ */ jsx(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
type: "button",
|
|
176
|
+
className: pillClasses,
|
|
177
|
+
onClick: () => onEventClick(event),
|
|
178
|
+
"aria-label": `${event.title}${event.time ? `, ${event.time}` : ""}, ${dayLabel}`,
|
|
179
|
+
children: pillContent
|
|
180
|
+
},
|
|
181
|
+
event.id
|
|
182
|
+
) : /* @__PURE__ */ jsx("span", { className: pillClasses, children: pillContent }, event.id);
|
|
183
|
+
}),
|
|
184
|
+
overflow > 0 && (onDateClick ? /* @__PURE__ */ jsxs(
|
|
185
|
+
"button",
|
|
186
|
+
{
|
|
187
|
+
type: "button",
|
|
188
|
+
className: `${baseClass}__more`,
|
|
189
|
+
onClick: () => onDateClick(dateStr),
|
|
190
|
+
"aria-label": `${overflow} more event${overflow === 1 ? "" : "s"}, ${dayLabel}`,
|
|
191
|
+
children: [
|
|
192
|
+
"+",
|
|
193
|
+
overflow,
|
|
194
|
+
" more"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
) : /* @__PURE__ */ jsxs("span", { className: `${baseClass}__more`, children: [
|
|
198
|
+
"+",
|
|
199
|
+
overflow,
|
|
200
|
+
" more"
|
|
201
|
+
] }))
|
|
202
|
+
] })
|
|
203
|
+
] }, dateStr);
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
EventCalendar.displayName = "EventCalendar";
|
|
211
|
+
export {
|
|
212
|
+
EventCalendar
|
|
213
|
+
};
|