@aiaiai-pt/design-system 0.4.3 → 0.5.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.
@@ -0,0 +1,50 @@
1
+ export default Calendar;
2
+ type Calendar = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * Calendar
8
+ *
9
+ * Full-page scheduling calendar with month, week, and day views.
10
+ * Events rendered as colored blocks with title, time, and optional status.
11
+ * Navigation with prev/next, today button, and view switcher.
12
+ * Consumes --calendar-* tokens from components.css.
13
+ *
14
+ * @example Basic month view
15
+ * <Calendar events={events} oneventclick={(ev) => console.log(ev)} />
16
+ *
17
+ * @example Week view with locale
18
+ * <Calendar view="week" events={events} locale={pt} />
19
+ *
20
+ * @example Custom event rendering
21
+ * <Calendar events={events}>
22
+ * {#snippet event(ev)}
23
+ * <span>{ev.title}</span>
24
+ * <Badge>{ev.status}</Badge>
25
+ * {/snippet}
26
+ * </Calendar>
27
+ */
28
+ declare const Calendar: import("svelte").Component<{
29
+ view?: string;
30
+ date?: any;
31
+ events?: any[];
32
+ maxVisible?: number;
33
+ locale?: typeof enUS;
34
+ oneventclick?: any;
35
+ ondateclick?: any;
36
+ event?: any;
37
+ class?: string;
38
+ } & Record<string, any>, {}, "view" | "date">;
39
+ type $$ComponentProps = {
40
+ view?: string;
41
+ date?: any;
42
+ events?: any[];
43
+ maxVisible?: number;
44
+ locale?: typeof enUS;
45
+ oneventclick?: any;
46
+ ondateclick?: any;
47
+ event?: any;
48
+ class?: string;
49
+ } & Record<string, any>;
50
+ import { enUS } from 'date-fns/locale';
@@ -0,0 +1,473 @@
1
+ <!--
2
+ @component DatePicker
3
+
4
+ Calendar dropdown for date selection. Values displayed in Berkeley Mono (data font).
5
+ Locale-aware formatting via date-fns. Follows Input patterns for label, help, error.
6
+ Consumes --datepicker-* and --input-* tokens from components.css.
7
+
8
+ @example Basic
9
+ <DatePicker label="START DATE" bind:value={startDate} />
10
+
11
+ @example With constraints
12
+ <DatePicker label="BIRTH DATE" min={new Date(1900, 0, 1)} max={new Date()} />
13
+
14
+ @example Error
15
+ <DatePicker label="DUE DATE" error="Date is required" />
16
+
17
+ @example Locale
18
+ <DatePicker label="DATA" locale={pt} />
19
+ -->
20
+ <script module>
21
+ let _datepickerUid = 0;
22
+ </script>
23
+
24
+ <script>
25
+ import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, addMonths, subMonths, isSameDay, isSameMonth, isBefore, isAfter } from 'date-fns';
26
+ import { enUS } from 'date-fns/locale';
27
+ import Popover from './Popover.svelte';
28
+
29
+ /**
30
+ * @typedef {'sm' | 'md' | 'lg'} Size
31
+ */
32
+
33
+ let {
34
+ /** @type {Date | null} */
35
+ value = $bindable(null),
36
+ /** @type {string | undefined} */
37
+ label = undefined,
38
+ /** @type {string | undefined} */
39
+ placeholder = 'Select date',
40
+ /** @type {string | undefined} */
41
+ help = undefined,
42
+ /** @type {string | undefined} */
43
+ error = undefined,
44
+ /** @type {Size} */
45
+ size = 'md',
46
+ /** @type {boolean} */
47
+ disabled = false,
48
+ /** @type {boolean} */
49
+ readonly = false,
50
+ /** @type {Date | undefined} */
51
+ min = undefined,
52
+ /** @type {Date | undefined} */
53
+ max = undefined,
54
+ /** @type {string} */
55
+ displayFormat = 'dd/MM/yyyy',
56
+ /** @type {import('date-fns').Locale} */
57
+ locale = enUS,
58
+ /** @type {((date: Date) => void) | undefined} */
59
+ onchange = undefined,
60
+ /** @type {string | undefined} */
61
+ id = undefined,
62
+ /** @type {string} */
63
+ class: className = '',
64
+ ...rest
65
+ } = $props();
66
+
67
+ const fallbackId = `datepicker-${_datepickerUid++}`;
68
+ const inputId = $derived(id ?? fallbackId);
69
+ const hintId = $derived(`${inputId}-hint`);
70
+ const hasHint = $derived(!!error || !!help);
71
+
72
+ let open = $state(false);
73
+ /** @type {HTMLElement | undefined} */
74
+ let triggerEl = $state();
75
+ let viewDate = $state(value ?? new Date());
76
+
77
+ const displayValue = $derived(value ? format(value, displayFormat, { locale }) : '');
78
+
79
+ const weekdays = $derived(getWeekdays(locale));
80
+ const calendarDays = $derived(getCalendarDays(viewDate, locale));
81
+
82
+ /** @param {import('date-fns').Locale} loc */
83
+ function getWeekdays(loc) {
84
+ const start = startOfWeek(new Date(), { locale: loc });
85
+ return Array.from({ length: 7 }, (_, i) =>
86
+ format(addDays(start, i), 'EEEEEE', { locale: loc })
87
+ );
88
+ }
89
+
90
+ /** @param {Date} month @param {import('date-fns').Locale} loc */
91
+ function getCalendarDays(month, loc) {
92
+ const monthStart = startOfMonth(month);
93
+ const monthEnd = endOfMonth(month);
94
+ const calStart = startOfWeek(monthStart, { locale: loc });
95
+ const calEnd = endOfWeek(monthEnd, { locale: loc });
96
+
97
+ /** @type {Date[]} */
98
+ const days = [];
99
+ let cursor = calStart;
100
+ while (isBefore(cursor, calEnd) || isSameDay(cursor, calEnd)) {
101
+ days.push(cursor);
102
+ cursor = addDays(cursor, 1);
103
+ }
104
+ return days;
105
+ }
106
+
107
+ /** @param {Date} date */
108
+ function isDisabledDate(date) {
109
+ if (min && isBefore(date, min)) return true;
110
+ if (max && isAfter(date, max)) return true;
111
+ return false;
112
+ }
113
+
114
+ /** @param {Date} date */
115
+ function selectDate(date) {
116
+ if (isDisabledDate(date)) return;
117
+ value = date;
118
+ open = false;
119
+ onchange?.(date);
120
+ }
121
+
122
+ function prevMonth() {
123
+ viewDate = subMonths(viewDate, 1);
124
+ }
125
+
126
+ function nextMonth() {
127
+ viewDate = addMonths(viewDate, 1);
128
+ }
129
+
130
+ function handleTriggerClick() {
131
+ if (disabled || readonly) return;
132
+ open = !open;
133
+ }
134
+
135
+ /** @param {KeyboardEvent} e */
136
+ function handleTriggerKeydown(e) {
137
+ if (disabled || readonly) return;
138
+ if (e.key === 'Enter' || e.key === ' ') {
139
+ e.preventDefault();
140
+ open = !open;
141
+ }
142
+ if (e.key === 'ArrowDown') {
143
+ e.preventDefault();
144
+ open = true;
145
+ }
146
+ }
147
+
148
+ /** @param {KeyboardEvent} e @param {Date} date */
149
+ function handleDayKeydown(e, date) {
150
+ if (e.key === 'Enter' || e.key === ' ') {
151
+ e.preventDefault();
152
+ selectDate(date);
153
+ }
154
+ }
155
+
156
+ // Sync viewDate when value changes externally
157
+ $effect(() => {
158
+ if (value) {
159
+ viewDate = value;
160
+ }
161
+ });
162
+ </script>
163
+
164
+ <div class="datepicker {className}" {...rest}>
165
+ {#if label}
166
+ <label class="datepicker-label" for={inputId}>{label}</label>
167
+ {/if}
168
+
169
+ <button
170
+ bind:this={triggerEl}
171
+ id={inputId}
172
+ type="button"
173
+ class="datepicker-trigger datepicker-trigger-{size}"
174
+ class:datepicker-trigger-error={!!error}
175
+ class:datepicker-trigger-readonly={readonly}
176
+ aria-haspopup="dialog"
177
+ aria-expanded={open}
178
+ aria-invalid={error ? true : undefined}
179
+ aria-describedby={hasHint ? hintId : undefined}
180
+ {disabled}
181
+ onclick={handleTriggerClick}
182
+ onkeydown={handleTriggerKeydown}
183
+ >
184
+ <span class="datepicker-value" class:datepicker-placeholder={!value}>
185
+ {displayValue || placeholder}
186
+ </span>
187
+ <svg class="datepicker-icon" viewBox="0 0 256 256" fill="none" aria-hidden="true">
188
+ <rect x="40" y="40" width="176" height="176" rx="8" stroke="currentColor" stroke-width="16" fill="none" />
189
+ <line x1="176" y1="24" x2="176" y2="56" stroke="currentColor" stroke-width="16" stroke-linecap="round" />
190
+ <line x1="80" y1="24" x2="80" y2="56" stroke="currentColor" stroke-width="16" stroke-linecap="round" />
191
+ <line x1="40" y1="88" x2="216" y2="88" stroke="currentColor" stroke-width="16" stroke-linecap="round" />
192
+ </svg>
193
+ </button>
194
+
195
+ <Popover bind:open anchor={triggerEl} placement="bottom-start" onclose={() => (open = false)}>
196
+ <div class="datepicker-calendar" role="application" aria-label="Calendar">
197
+ <div class="datepicker-nav">
198
+ <button type="button" class="datepicker-nav-btn" onclick={prevMonth} aria-label="Previous month">
199
+ <svg viewBox="0 0 256 256" aria-hidden="true">
200
+ <polyline points="160,208 80,128 160,48" fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
201
+ </svg>
202
+ </button>
203
+ <span class="datepicker-nav-title">
204
+ {format(viewDate, 'LLLL yyyy', { locale })}
205
+ </span>
206
+ <button type="button" class="datepicker-nav-btn" onclick={nextMonth} aria-label="Next month">
207
+ <svg viewBox="0 0 256 256" aria-hidden="true">
208
+ <polyline points="96,48 176,128 96,208" fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" />
209
+ </svg>
210
+ </button>
211
+ </div>
212
+
213
+ <div class="datepicker-grid" role="grid" aria-label="Calendar dates">
214
+ <div class="datepicker-weekdays" role="row">
215
+ {#each weekdays as day}
216
+ <span class="datepicker-weekday" role="columnheader">{day}</span>
217
+ {/each}
218
+ </div>
219
+
220
+ <div class="datepicker-days" role="rowgroup">
221
+ {#each calendarDays as date}
222
+ {@const isSelected = value && isSameDay(date, value)}
223
+ {@const isToday = isSameDay(date, new Date())}
224
+ {@const isOutside = !isSameMonth(date, viewDate)}
225
+ {@const isDisabled = isDisabledDate(date)}
226
+ <button
227
+ type="button"
228
+ class="datepicker-day"
229
+ class:datepicker-day-selected={isSelected}
230
+ class:datepicker-day-today={isToday && !isSelected}
231
+ class:datepicker-day-outside={isOutside}
232
+ role="gridcell"
233
+ aria-selected={isSelected}
234
+ aria-disabled={isDisabled}
235
+ tabindex={isSelected || (!value && isToday) ? 0 : -1}
236
+ disabled={isDisabled}
237
+ onclick={() => selectDate(date)}
238
+ onkeydown={(e) => handleDayKeydown(e, date)}
239
+ >
240
+ {date.getDate()}
241
+ </button>
242
+ {/each}
243
+ </div>
244
+ </div>
245
+ </div>
246
+ </Popover>
247
+
248
+ {#if error}
249
+ <span id={hintId} class="datepicker-error-text" role="alert">{error}</span>
250
+ {:else if help}
251
+ <span id={hintId} class="datepicker-help">{help}</span>
252
+ {/if}
253
+ </div>
254
+
255
+ <style>
256
+ .datepicker {
257
+ display: flex;
258
+ flex-direction: column;
259
+ gap: var(--input-label-gap);
260
+ width: 100%;
261
+ }
262
+
263
+ .datepicker-label {
264
+ font-family: var(--input-label-font);
265
+ font-size: var(--input-label-size);
266
+ letter-spacing: var(--input-label-tracking);
267
+ color: var(--input-label-color);
268
+ }
269
+
270
+ /* ─── Trigger ─── */
271
+ .datepicker-trigger {
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: space-between;
275
+ gap: var(--space-xs);
276
+ font-family: var(--input-font);
277
+ font-size: var(--input-font-size);
278
+ border: var(--input-border);
279
+ border-radius: var(--input-radius);
280
+ background: var(--input-bg);
281
+ color: var(--input-text);
282
+ cursor: pointer;
283
+ width: 100%;
284
+ text-align: left;
285
+ transition: border var(--input-transition);
286
+ }
287
+
288
+ .datepicker-trigger-sm {
289
+ height: var(--input-sm-height);
290
+ padding: 0 var(--input-sm-padding-x);
291
+ }
292
+
293
+ .datepicker-trigger-md {
294
+ height: var(--input-md-height);
295
+ padding: 0 var(--input-md-padding-x);
296
+ }
297
+
298
+ .datepicker-trigger-lg {
299
+ height: var(--input-lg-height);
300
+ padding: 0 var(--input-lg-padding-x);
301
+ }
302
+
303
+ .datepicker-trigger:focus {
304
+ outline: none;
305
+ border: var(--input-border-focus);
306
+ }
307
+
308
+ .datepicker-trigger:disabled {
309
+ opacity: 0.5;
310
+ cursor: not-allowed;
311
+ }
312
+
313
+ .datepicker-trigger-readonly {
314
+ background: var(--color-surface-secondary);
315
+ cursor: default;
316
+ }
317
+
318
+ .datepicker-trigger-error {
319
+ border-color: var(--input-error-border-color);
320
+ }
321
+
322
+ .datepicker-value {
323
+ flex: 1;
324
+ min-width: 0;
325
+ }
326
+
327
+ .datepicker-placeholder {
328
+ color: var(--datepicker-placeholder-color);
329
+ }
330
+
331
+ .datepicker-icon {
332
+ flex-shrink: 0;
333
+ width: var(--datepicker-icon-size);
334
+ height: var(--datepicker-icon-size);
335
+ color: var(--datepicker-icon-color);
336
+ }
337
+
338
+ /* ─── Calendar ─── */
339
+ .datepicker-calendar {
340
+ padding: var(--datepicker-calendar-padding);
341
+ }
342
+
343
+ .datepicker-nav {
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: space-between;
347
+ margin-bottom: var(--space-sm);
348
+ }
349
+
350
+ .datepicker-nav-title {
351
+ font-family: var(--datepicker-nav-font);
352
+ font-size: var(--datepicker-nav-size);
353
+ letter-spacing: var(--datepicker-nav-tracking);
354
+ color: var(--datepicker-nav-color);
355
+ text-transform: uppercase;
356
+ }
357
+
358
+ .datepicker-nav-btn {
359
+ display: flex;
360
+ align-items: center;
361
+ justify-content: center;
362
+ width: var(--datepicker-nav-btn-size);
363
+ height: var(--datepicker-nav-btn-size);
364
+ border: none;
365
+ border-radius: var(--datepicker-nav-btn-radius);
366
+ background: transparent;
367
+ color: var(--color-text-secondary);
368
+ cursor: pointer;
369
+ transition: background var(--duration-instant) var(--easing-default);
370
+ }
371
+
372
+ .datepicker-nav-btn:hover {
373
+ background: var(--datepicker-nav-btn-hover-bg);
374
+ }
375
+
376
+ .datepicker-nav-btn svg {
377
+ width: var(--icon-size-xs);
378
+ height: var(--icon-size-xs);
379
+ }
380
+
381
+ /* ─── Grid ─── */
382
+ .datepicker-grid {
383
+ display: flex;
384
+ flex-direction: column;
385
+ }
386
+
387
+ .datepicker-weekdays {
388
+ display: grid;
389
+ grid-template-columns: repeat(7, 1fr);
390
+ margin-bottom: var(--space-2xs);
391
+ }
392
+
393
+ .datepicker-weekday {
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ height: var(--datepicker-day-size);
398
+ font-family: var(--datepicker-weekday-font);
399
+ font-size: var(--datepicker-weekday-size);
400
+ letter-spacing: var(--datepicker-weekday-tracking);
401
+ color: var(--datepicker-weekday-color);
402
+ text-transform: uppercase;
403
+ }
404
+
405
+ .datepicker-days {
406
+ display: grid;
407
+ grid-template-columns: repeat(7, 1fr);
408
+ }
409
+
410
+ .datepicker-day {
411
+ display: flex;
412
+ align-items: center;
413
+ justify-content: center;
414
+ width: var(--datepicker-day-size);
415
+ height: var(--datepicker-day-size);
416
+ font-family: var(--datepicker-day-font);
417
+ font-size: var(--datepicker-day-font-size);
418
+ border: none;
419
+ border-radius: var(--datepicker-day-radius);
420
+ background: transparent;
421
+ color: var(--color-text);
422
+ cursor: pointer;
423
+ transition: background var(--duration-instant) var(--easing-default);
424
+ margin: 0 auto;
425
+ }
426
+
427
+ .datepicker-day:hover:not(:disabled) {
428
+ background: var(--datepicker-day-hover-bg);
429
+ }
430
+
431
+ .datepicker-day-selected {
432
+ background: var(--datepicker-day-selected-bg);
433
+ color: var(--datepicker-day-selected-text);
434
+ }
435
+
436
+ .datepicker-day-selected:hover:not(:disabled) {
437
+ background: var(--datepicker-day-selected-bg);
438
+ }
439
+
440
+ .datepicker-day-today {
441
+ border: var(--datepicker-day-today-border);
442
+ }
443
+
444
+ .datepicker-day-outside {
445
+ opacity: var(--datepicker-day-outside-opacity);
446
+ }
447
+
448
+ .datepicker-day:disabled {
449
+ opacity: var(--datepicker-day-disabled-opacity);
450
+ cursor: not-allowed;
451
+ }
452
+
453
+ /* ─── Hint / Error ─── */
454
+ .datepicker-help {
455
+ font-family: var(--input-help-font);
456
+ font-size: var(--input-help-size);
457
+ color: var(--input-help-color);
458
+ }
459
+
460
+ .datepicker-error-text {
461
+ font-family: var(--input-help-font);
462
+ font-size: var(--input-help-size);
463
+ color: var(--input-error-text);
464
+ }
465
+
466
+ @media (prefers-reduced-motion: reduce) {
467
+ .datepicker-trigger,
468
+ .datepicker-nav-btn,
469
+ .datepicker-day {
470
+ transition: none;
471
+ }
472
+ }
473
+ </style>
@@ -0,0 +1,59 @@
1
+ export default DatePicker;
2
+ type DatePicker = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * DatePicker
8
+ *
9
+ * Calendar dropdown for date selection. Values displayed in Berkeley Mono (data font).
10
+ * Locale-aware formatting via date-fns. Follows Input patterns for label, help, error.
11
+ * Consumes --datepicker-* and --input-* tokens from components.css.
12
+ *
13
+ * @example Basic
14
+ * <DatePicker label="START DATE" bind:value={startDate} />
15
+ *
16
+ * @example With constraints
17
+ * <DatePicker label="BIRTH DATE" min={new Date(1900, 0, 1)} max={new Date()} />
18
+ *
19
+ * @example Error
20
+ * <DatePicker label="DUE DATE" error="Date is required" />
21
+ *
22
+ * @example Locale
23
+ * <DatePicker label="DATA" locale={pt} />
24
+ */
25
+ declare const DatePicker: import("svelte").Component<{
26
+ value?: any;
27
+ label?: any;
28
+ placeholder?: string;
29
+ help?: any;
30
+ error?: any;
31
+ size?: string;
32
+ disabled?: boolean;
33
+ readonly?: boolean;
34
+ min?: any;
35
+ max?: any;
36
+ displayFormat?: string;
37
+ locale?: typeof enUS;
38
+ onchange?: any;
39
+ id?: any;
40
+ class?: string;
41
+ } & Record<string, any>, {}, "value">;
42
+ type $$ComponentProps = {
43
+ value?: any;
44
+ label?: any;
45
+ placeholder?: string;
46
+ help?: any;
47
+ error?: any;
48
+ size?: string;
49
+ disabled?: boolean;
50
+ readonly?: boolean;
51
+ min?: any;
52
+ max?: any;
53
+ displayFormat?: string;
54
+ locale?: typeof enUS;
55
+ onchange?: any;
56
+ id?: any;
57
+ class?: string;
58
+ } & Record<string, any>;
59
+ import { enUS } from 'date-fns/locale';