@sentropic/design-system-svelte 0.4.1 → 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,680 @@
1
+ <script lang="ts" module>
2
+ export type DatePickerRange = {
3
+ start: Date | null;
4
+ end: Date | null;
5
+ };
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ import type { HTMLAttributes } from "svelte/elements";
10
+
11
+ type DatePickerProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
12
+ label?: string;
13
+ helperText?: string;
14
+ errorText?: string;
15
+ invalid?: boolean;
16
+ disabled?: boolean;
17
+ mode?: "single" | "range";
18
+ /**
19
+ * Bound value. In `mode="single"` (default) this is `Date | null`. In
20
+ * `mode="range"` this is `DatePickerRange`. The runtime checks `mode` at
21
+ * read/write time so misuse is non-fatal.
22
+ */
23
+ value?: Date | DatePickerRange | null;
24
+ min?: Date;
25
+ max?: Date;
26
+ locale?: string;
27
+ placeholder?: string;
28
+ size?: "sm" | "md" | "lg";
29
+ class?: string;
30
+ id?: string;
31
+ openLabel?: string;
32
+ previousMonthLabel?: string;
33
+ nextMonthLabel?: string;
34
+ todayLabel?: string;
35
+ };
36
+
37
+ let {
38
+ label,
39
+ helperText,
40
+ errorText,
41
+ invalid = false,
42
+ disabled = false,
43
+ mode = "single",
44
+ value = $bindable(),
45
+ min,
46
+ max,
47
+ locale = "fr-FR",
48
+ placeholder,
49
+ size = "md",
50
+ class: className,
51
+ id,
52
+ openLabel,
53
+ previousMonthLabel,
54
+ nextMonthLabel,
55
+ todayLabel,
56
+ ...rest
57
+ }: DatePickerProps = $props();
58
+
59
+ // Normalize value defaults based on mode (only set if undefined to keep $bindable contract clean).
60
+ // Use a one-shot $effect.pre to satisfy Svelte's state-reactivity warnings around prop reads at init.
61
+ $effect.pre(() => {
62
+ if (value !== undefined) return;
63
+ value = mode === "range" ? { start: null, end: null } : null;
64
+ });
65
+
66
+ const isFr = $derived((locale ?? "fr-FR").toLowerCase().startsWith("fr"));
67
+
68
+ const resolvedOpenLabel = $derived(openLabel ?? (isFr ? "Ouvrir le calendrier" : "Open calendar"));
69
+ const resolvedPrevLabel = $derived(
70
+ previousMonthLabel ?? (isFr ? "Mois précédent" : "Previous month")
71
+ );
72
+ const resolvedNextLabel = $derived(
73
+ nextMonthLabel ?? (isFr ? "Mois suivant" : "Next month")
74
+ );
75
+ const resolvedTodayLabel = $derived(todayLabel ?? (isFr ? "Aujourd'hui" : "Today"));
76
+ const resolvedPlaceholder = $derived(
77
+ placeholder ?? (isFr ? (mode === "range" ? "jj/mm/aaaa - jj/mm/aaaa" : "jj/mm/aaaa") : mode === "range" ? "mm/dd/yyyy - mm/dd/yyyy" : "mm/dd/yyyy")
78
+ );
79
+
80
+ const fieldId = $derived(id ?? `st-datepicker-${Math.random().toString(36).slice(2, 9)}`);
81
+
82
+ let open = $state(false);
83
+
84
+ const dateFormatter = $derived(
85
+ new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" })
86
+ );
87
+ const monthFormatter = $derived(
88
+ new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" })
89
+ );
90
+ const weekdayFormatter = $derived(new Intl.DateTimeFormat(locale, { weekday: "short" }));
91
+ const cellFormatter = $derived(
92
+ new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", year: "numeric" })
93
+ );
94
+
95
+ function startOfDay(date: Date): Date {
96
+ const d = new Date(date);
97
+ d.setHours(0, 0, 0, 0);
98
+ return d;
99
+ }
100
+
101
+ function isSameDay(a: Date | null | undefined, b: Date | null | undefined): boolean {
102
+ if (!a || !b) return false;
103
+ return (
104
+ a.getFullYear() === b.getFullYear() &&
105
+ a.getMonth() === b.getMonth() &&
106
+ a.getDate() === b.getDate()
107
+ );
108
+ }
109
+
110
+ function pickInitialMonth(): Date {
111
+ if (mode === "single" && value instanceof Date) {
112
+ return startOfDay(value);
113
+ }
114
+ if (mode === "range" && value && typeof value === "object" && "start" in value && value.start) {
115
+ return startOfDay(value.start);
116
+ }
117
+ return startOfDay(new Date());
118
+ }
119
+
120
+ let viewYear = $state(pickInitialMonth().getFullYear());
121
+ let viewMonth = $state(pickInitialMonth().getMonth());
122
+
123
+ // Re-sync the visible month when the calendar reopens.
124
+ $effect(() => {
125
+ if (open) {
126
+ const initial = pickInitialMonth();
127
+ viewYear = initial.getFullYear();
128
+ viewMonth = initial.getMonth();
129
+ }
130
+ });
131
+
132
+ // Locale-aware "first day of week" inference (Mon vs Sun) based on Intl.Locale weekInfo when available.
133
+ function firstDayOfWeek(loc: string): number {
134
+ try {
135
+ // @ts-expect-error: weekInfo is recent and not in all TS lib versions.
136
+ const info = new Intl.Locale(loc).weekInfo;
137
+ if (info && typeof info.firstDay === "number") {
138
+ // Intl uses 1=Mon..7=Sun; JS Date.getDay uses 0=Sun..6=Sat.
139
+ return info.firstDay === 7 ? 0 : info.firstDay;
140
+ }
141
+ } catch {
142
+ // Ignore — fall back below.
143
+ }
144
+ // Fallback: French and most EU locales = Monday.
145
+ return loc.toLowerCase().startsWith("en-us") ? 0 : 1;
146
+ }
147
+
148
+ const weekStart = $derived(firstDayOfWeek(locale));
149
+
150
+ const weekdayLabels = $derived.by(() => {
151
+ // Build 7 weekday labels rotated to start at weekStart.
152
+ // Use a known week (2024-01-07 is a Sunday) to enumerate Sun..Sat then rotate.
153
+ const sample = new Date(Date.UTC(2024, 0, 7));
154
+ const labels: string[] = [];
155
+ for (let i = 0; i < 7; i++) {
156
+ const d = new Date(sample);
157
+ d.setUTCDate(sample.getUTCDate() + i);
158
+ labels.push(weekdayFormatter.format(d));
159
+ }
160
+ return [...labels.slice(weekStart), ...labels.slice(0, weekStart)];
161
+ });
162
+
163
+ type Cell = { date: Date; inMonth: boolean };
164
+
165
+ const grid = $derived.by<Cell[]>(() => {
166
+ const first = new Date(viewYear, viewMonth, 1);
167
+ const firstDayIdx = first.getDay();
168
+ const offset = (firstDayIdx - weekStart + 7) % 7;
169
+ const start = new Date(viewYear, viewMonth, 1 - offset);
170
+ const cells: Cell[] = [];
171
+ for (let i = 0; i < 42; i++) {
172
+ const d = new Date(start);
173
+ d.setDate(start.getDate() + i);
174
+ cells.push({ date: startOfDay(d), inMonth: d.getMonth() === viewMonth });
175
+ }
176
+ return cells;
177
+ });
178
+
179
+ function isOutOfBounds(date: Date): boolean {
180
+ const d = startOfDay(date).getTime();
181
+ if (min && d < startOfDay(min).getTime()) return true;
182
+ if (max && d > startOfDay(max).getTime()) return true;
183
+ return false;
184
+ }
185
+
186
+ function isSelected(date: Date): boolean {
187
+ if (mode === "single") {
188
+ return value instanceof Date && isSameDay(value, date);
189
+ }
190
+ if (mode === "range" && value && typeof value === "object" && "start" in value) {
191
+ return isSameDay(value.start, date) || isSameDay(value.end, date);
192
+ }
193
+ return false;
194
+ }
195
+
196
+ function isInRange(date: Date): boolean {
197
+ if (mode !== "range") return false;
198
+ if (!value || typeof value !== "object" || !("start" in value)) return false;
199
+ const { start, end } = value;
200
+ if (!start || !end) return false;
201
+ const d = startOfDay(date).getTime();
202
+ return d > startOfDay(start).getTime() && d < startOfDay(end).getTime();
203
+ }
204
+
205
+ function previousMonth() {
206
+ if (viewMonth === 0) {
207
+ viewMonth = 11;
208
+ viewYear -= 1;
209
+ } else {
210
+ viewMonth -= 1;
211
+ }
212
+ }
213
+
214
+ function nextMonth() {
215
+ if (viewMonth === 11) {
216
+ viewMonth = 0;
217
+ viewYear += 1;
218
+ } else {
219
+ viewMonth += 1;
220
+ }
221
+ }
222
+
223
+ function pickDate(date: Date) {
224
+ if (isOutOfBounds(date)) return;
225
+ const picked = startOfDay(date);
226
+ if (mode === "single") {
227
+ value = picked;
228
+ open = false;
229
+ return;
230
+ }
231
+ // range mode
232
+ const current =
233
+ value && typeof value === "object" && "start" in value
234
+ ? value
235
+ : { start: null, end: null };
236
+ if (!current.start || (current.start && current.end)) {
237
+ // Start a new range.
238
+ value = { start: picked, end: null };
239
+ return;
240
+ }
241
+ if (picked.getTime() < startOfDay(current.start).getTime()) {
242
+ // Reset to a new start when the picked date is before the current start.
243
+ value = { start: picked, end: null };
244
+ return;
245
+ }
246
+ value = { start: current.start, end: picked };
247
+ open = false;
248
+ }
249
+
250
+ function pickToday() {
251
+ pickDate(new Date());
252
+ }
253
+
254
+ function formattedValue(): string {
255
+ if (mode === "single") {
256
+ return value instanceof Date ? dateFormatter.format(value) : "";
257
+ }
258
+ if (value && typeof value === "object" && "start" in value) {
259
+ const s = value.start ? dateFormatter.format(value.start) : "";
260
+ const e = value.end ? dateFormatter.format(value.end) : "";
261
+ if (!s && !e) return "";
262
+ return `${s} - ${e}`;
263
+ }
264
+ return "";
265
+ }
266
+
267
+ const fieldClasses = () => ["st-field", className].filter(Boolean).join(" ");
268
+ const groupClasses = () => ["st-datepicker", `st-datepicker--${size}`].join(" ");
269
+ const isInvalid = () => invalid || Boolean(errorText);
270
+
271
+ // Outside-click closes the popover.
272
+ let hostEl = $state<HTMLDivElement | null>(null);
273
+
274
+ function onDocumentMouseDown(event: MouseEvent) {
275
+ if (!open) return;
276
+ const target = event.target as Node | null;
277
+ if (hostEl && target && !hostEl.contains(target)) {
278
+ open = false;
279
+ }
280
+ }
281
+
282
+ $effect(() => {
283
+ if (typeof document === "undefined") return;
284
+ document.addEventListener("mousedown", onDocumentMouseDown);
285
+ return () => document.removeEventListener("mousedown", onDocumentMouseDown);
286
+ });
287
+
288
+ function toggleOpen() {
289
+ if (disabled) return;
290
+ open = !open;
291
+ }
292
+
293
+ function onPanelKeyDown(event: KeyboardEvent) {
294
+ if (event.key === "Escape" && open) {
295
+ event.preventDefault();
296
+ open = false;
297
+ }
298
+ }
299
+
300
+ const monthLabel = $derived(
301
+ monthFormatter.format(new Date(viewYear, viewMonth, 1))
302
+ );
303
+ </script>
304
+
305
+ <div class={fieldClasses()} bind:this={hostEl} {...rest}>
306
+ <label class="st-field__control" for={fieldId}>
307
+ {#if label}<span class="st-field__label">{label}</span>{/if}
308
+ <span class={groupClasses()}>
309
+ <input
310
+ id={fieldId}
311
+ type="text"
312
+ readonly
313
+ class="st-datepicker__control"
314
+ value={formattedValue()}
315
+ placeholder={resolvedPlaceholder}
316
+ {disabled}
317
+ aria-invalid={isInvalid() ? "true" : undefined}
318
+ onclick={toggleOpen}
319
+ />
320
+ <button
321
+ type="button"
322
+ class="st-datepicker__trigger"
323
+ aria-label={resolvedOpenLabel}
324
+ aria-haspopup="dialog"
325
+ aria-expanded={open ? "true" : "false"}
326
+ {disabled}
327
+ onclick={toggleOpen}
328
+ >
329
+ <span aria-hidden="true">📅</span>
330
+ </button>
331
+ </span>
332
+ </label>
333
+ {#if open}
334
+ <div
335
+ class="st-datepicker__panel"
336
+ role="dialog"
337
+ tabindex="-1"
338
+ aria-label={label ?? resolvedOpenLabel}
339
+ onkeydown={onPanelKeyDown}
340
+ >
341
+ <div class="st-datepicker__nav">
342
+ <button
343
+ type="button"
344
+ class="st-datepicker__navBtn"
345
+ aria-label={resolvedPrevLabel}
346
+ onclick={previousMonth}
347
+ >
348
+ <span aria-hidden="true">‹</span>
349
+ </button>
350
+ <span class="st-datepicker__monthLabel" aria-live="polite">{monthLabel}</span>
351
+ <button
352
+ type="button"
353
+ class="st-datepicker__navBtn"
354
+ aria-label={resolvedNextLabel}
355
+ onclick={nextMonth}
356
+ >
357
+ <span aria-hidden="true">›</span>
358
+ </button>
359
+ </div>
360
+ <div class="st-datepicker__grid" role="grid">
361
+ <div class="st-datepicker__weekdays" role="row">
362
+ {#each weekdayLabels as wd (wd)}
363
+ <span class="st-datepicker__weekday" role="columnheader">{wd}</span>
364
+ {/each}
365
+ </div>
366
+ <div class="st-datepicker__days">
367
+ {#each grid as cell, i (i)}
368
+ {@const oob = isOutOfBounds(cell.date)}
369
+ {@const selected = isSelected(cell.date)}
370
+ {@const inRange = isInRange(cell.date)}
371
+ <button
372
+ type="button"
373
+ class="st-datepicker__day"
374
+ class:st-datepicker__day--outside={!cell.inMonth}
375
+ class:st-datepicker__day--selected={selected}
376
+ class:st-datepicker__day--inRange={inRange}
377
+ aria-label={cellFormatter.format(cell.date)}
378
+ aria-pressed={selected ? "true" : "false"}
379
+ aria-disabled={oob ? "true" : undefined}
380
+ disabled={oob}
381
+ onclick={() => pickDate(cell.date)}
382
+ >
383
+ {cell.date.getDate()}
384
+ </button>
385
+ {/each}
386
+ </div>
387
+ </div>
388
+ <div class="st-datepicker__footer">
389
+ <button
390
+ type="button"
391
+ class="st-datepicker__todayBtn"
392
+ onclick={pickToday}
393
+ disabled={isOutOfBounds(new Date())}
394
+ >
395
+ {resolvedTodayLabel}
396
+ </button>
397
+ </div>
398
+ </div>
399
+ {/if}
400
+ {#if errorText}
401
+ <span class="st-field__error">{errorText}</span>
402
+ {:else if helperText}
403
+ <span class="st-field__help">{helperText}</span>
404
+ {/if}
405
+ </div>
406
+
407
+ <style>
408
+ .st-field {
409
+ color: var(--st-component-field-labelText, var(--st-semantic-text-primary));
410
+ display: grid;
411
+ gap: var(--st-component-field-gap, 0.5rem);
412
+ max-width: var(--st-component-field-maxWidth, 28rem);
413
+ position: relative;
414
+ }
415
+
416
+ .st-field__control {
417
+ display: grid;
418
+ gap: var(--st-component-field-gap, 0.5rem);
419
+ }
420
+
421
+ .st-field__label {
422
+ font-size: 0.875rem;
423
+ font-weight: 600;
424
+ }
425
+
426
+ .st-field__help,
427
+ .st-field__error {
428
+ font-size: 0.8125rem;
429
+ line-height: 1.4;
430
+ }
431
+
432
+ .st-field__help {
433
+ color: var(--st-component-field-helpText, var(--st-semantic-text-secondary));
434
+ }
435
+
436
+ .st-field__error {
437
+ color: var(--st-component-field-errorText, var(--st-semantic-feedback-error));
438
+ }
439
+
440
+ .st-datepicker {
441
+ align-items: stretch;
442
+ background: var(--st-component-control-background, var(--st-semantic-surface-default));
443
+ border: 1px solid var(--st-component-control-border, var(--st-semantic-border-subtle));
444
+ border-radius: var(--st-component-control-radius, 0.375rem);
445
+ color: var(--st-component-control-text, var(--st-semantic-text-primary));
446
+ display: inline-flex;
447
+ overflow: hidden;
448
+ transition:
449
+ border-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
450
+ box-shadow var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
451
+ width: 100%;
452
+ }
453
+
454
+ .st-datepicker--sm {
455
+ min-height: var(--st-component-control-smHeight, 2rem);
456
+ }
457
+
458
+ .st-datepicker--md {
459
+ min-height: var(--st-component-control-mdHeight, 2.5rem);
460
+ }
461
+
462
+ .st-datepicker--lg {
463
+ min-height: var(--st-component-control-lgHeight, 3rem);
464
+ }
465
+
466
+ .st-datepicker:hover:not(:has(input:disabled)) {
467
+ border-color: var(--st-component-control-hoverBorder, var(--st-semantic-border-strong));
468
+ }
469
+
470
+ .st-datepicker:focus-within {
471
+ border-color: var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
472
+ box-shadow: 0 0 0 2px var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
473
+ }
474
+
475
+ .st-datepicker:has([aria-invalid="true"]) {
476
+ border-color: var(--st-component-control-invalidBorder, var(--st-semantic-feedback-error));
477
+ }
478
+
479
+ .st-datepicker__control {
480
+ background: transparent;
481
+ border: 0;
482
+ color: inherit;
483
+ cursor: pointer;
484
+ flex: 1 1 auto;
485
+ font: inherit;
486
+ min-width: 0;
487
+ padding: 0 0.75rem;
488
+ width: 100%;
489
+ }
490
+
491
+ .st-datepicker__control:focus {
492
+ outline: none;
493
+ }
494
+
495
+ .st-datepicker__control::placeholder {
496
+ color: var(--st-component-control-placeholderText, var(--st-semantic-text-muted));
497
+ }
498
+
499
+ .st-datepicker__control:disabled {
500
+ color: var(--st-component-control-disabledText, var(--st-semantic-text-muted));
501
+ cursor: not-allowed;
502
+ }
503
+
504
+ .st-datepicker__trigger {
505
+ align-items: center;
506
+ background: transparent;
507
+ border: 0;
508
+ border-left: 1px solid var(--st-component-control-border, var(--st-semantic-border-subtle));
509
+ color: var(--st-semantic-text-secondary);
510
+ cursor: pointer;
511
+ display: inline-flex;
512
+ flex: 0 0 auto;
513
+ font: inherit;
514
+ justify-content: center;
515
+ min-width: 2.25rem;
516
+ padding: 0 0.5rem;
517
+ transition: background-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
518
+ }
519
+
520
+ .st-datepicker__trigger:hover:not(:disabled) {
521
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
522
+ }
523
+
524
+ .st-datepicker__trigger:focus-visible {
525
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
526
+ outline-offset: -2px;
527
+ }
528
+
529
+ .st-datepicker__trigger:disabled {
530
+ cursor: not-allowed;
531
+ }
532
+
533
+ .st-datepicker__panel {
534
+ background: var(--st-component-popover-background, var(--st-semantic-surface-raised));
535
+ border: 1px solid var(--st-component-popover-border, var(--st-semantic-border-subtle));
536
+ border-radius: var(--st-component-popover-radius, 0.5rem);
537
+ box-shadow: var(--st-component-popover-shadow, 0 18px 45px rgb(15 23 42 / 0.18));
538
+ color: var(--st-component-popover-text, var(--st-semantic-text-primary));
539
+ display: grid;
540
+ gap: var(--st-spacing-3, 0.75rem);
541
+ left: 0;
542
+ margin-top: var(--st-spacing-1, 0.25rem);
543
+ min-width: 18rem;
544
+ padding: var(--st-spacing-3, 0.75rem);
545
+ position: absolute;
546
+ top: 100%;
547
+ z-index: var(--st-component-popover-zIndex, 80);
548
+ }
549
+
550
+ .st-datepicker__nav {
551
+ align-items: center;
552
+ display: grid;
553
+ grid-template-columns: auto 1fr auto;
554
+ gap: var(--st-spacing-2, 0.5rem);
555
+ }
556
+
557
+ .st-datepicker__navBtn {
558
+ background: transparent;
559
+ border: 0;
560
+ border-radius: var(--st-component-control-radius, 0.375rem);
561
+ color: inherit;
562
+ cursor: pointer;
563
+ font: inherit;
564
+ font-size: 1.125rem;
565
+ line-height: 1;
566
+ padding: 0.25rem 0.5rem;
567
+ }
568
+
569
+ .st-datepicker__navBtn:hover:not(:disabled) {
570
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
571
+ }
572
+
573
+ .st-datepicker__navBtn:focus-visible {
574
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
575
+ outline-offset: 2px;
576
+ }
577
+
578
+ .st-datepicker__monthLabel {
579
+ font-weight: 600;
580
+ text-align: center;
581
+ text-transform: capitalize;
582
+ }
583
+
584
+ .st-datepicker__grid {
585
+ display: grid;
586
+ gap: var(--st-spacing-1, 0.25rem);
587
+ }
588
+
589
+ .st-datepicker__weekdays,
590
+ .st-datepicker__days {
591
+ display: grid;
592
+ grid-template-columns: repeat(7, minmax(2rem, 1fr));
593
+ gap: 2px;
594
+ }
595
+
596
+ .st-datepicker__weekday {
597
+ color: var(--st-semantic-text-secondary);
598
+ font-size: 0.75rem;
599
+ font-weight: 600;
600
+ padding: 0.25rem 0;
601
+ text-align: center;
602
+ text-transform: capitalize;
603
+ }
604
+
605
+ .st-datepicker__day {
606
+ aspect-ratio: 1 / 1;
607
+ background: transparent;
608
+ border: 0;
609
+ border-radius: var(--st-component-control-radius, 0.375rem);
610
+ color: inherit;
611
+ cursor: pointer;
612
+ font: inherit;
613
+ font-size: 0.875rem;
614
+ line-height: 1;
615
+ min-width: 0;
616
+ padding: 0;
617
+ text-align: center;
618
+ }
619
+
620
+ .st-datepicker__day:hover:not(:disabled) {
621
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
622
+ }
623
+
624
+ .st-datepicker__day:focus-visible {
625
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
626
+ outline-offset: -2px;
627
+ }
628
+
629
+ .st-datepicker__day--outside {
630
+ color: var(--st-semantic-text-muted);
631
+ }
632
+
633
+ .st-datepicker__day--inRange {
634
+ background: var(
635
+ --st-component-dropdown-optionHoverBackground,
636
+ var(--st-semantic-surface-subtle)
637
+ );
638
+ }
639
+
640
+ .st-datepicker__day--selected {
641
+ background: var(--st-component-dropdown-selectedBackground, var(--st-semantic-action-primary));
642
+ color: var(--st-component-dropdown-selectedText, var(--st-semantic-action-primaryText));
643
+ }
644
+
645
+ .st-datepicker__day:disabled {
646
+ color: var(--st-semantic-text-muted);
647
+ cursor: not-allowed;
648
+ opacity: 0.5;
649
+ }
650
+
651
+ .st-datepicker__footer {
652
+ display: flex;
653
+ justify-content: flex-end;
654
+ }
655
+
656
+ .st-datepicker__todayBtn {
657
+ background: transparent;
658
+ border: 1px solid var(--st-component-control-border, var(--st-semantic-border-subtle));
659
+ border-radius: var(--st-component-control-radius, 0.375rem);
660
+ color: inherit;
661
+ cursor: pointer;
662
+ font: inherit;
663
+ font-size: 0.8125rem;
664
+ padding: 0.25rem 0.75rem;
665
+ }
666
+
667
+ .st-datepicker__todayBtn:hover:not(:disabled) {
668
+ background: var(--st-component-control-hoverBackground, var(--st-semantic-surface-subtle));
669
+ }
670
+
671
+ .st-datepicker__todayBtn:focus-visible {
672
+ outline: 2px solid var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
673
+ outline-offset: 2px;
674
+ }
675
+
676
+ .st-datepicker__todayBtn:disabled {
677
+ color: var(--st-semantic-text-muted);
678
+ cursor: not-allowed;
679
+ }
680
+ </style>
@@ -0,0 +1,34 @@
1
+ export type DatePickerRange = {
2
+ start: Date | null;
3
+ end: Date | null;
4
+ };
5
+ import type { HTMLAttributes } from "svelte/elements";
6
+ type DatePickerProps = Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
7
+ label?: string;
8
+ helperText?: string;
9
+ errorText?: string;
10
+ invalid?: boolean;
11
+ disabled?: boolean;
12
+ mode?: "single" | "range";
13
+ /**
14
+ * Bound value. In `mode="single"` (default) this is `Date | null`. In
15
+ * `mode="range"` this is `DatePickerRange`. The runtime checks `mode` at
16
+ * read/write time so misuse is non-fatal.
17
+ */
18
+ value?: Date | DatePickerRange | null;
19
+ min?: Date;
20
+ max?: Date;
21
+ locale?: string;
22
+ placeholder?: string;
23
+ size?: "sm" | "md" | "lg";
24
+ class?: string;
25
+ id?: string;
26
+ openLabel?: string;
27
+ previousMonthLabel?: string;
28
+ nextMonthLabel?: string;
29
+ todayLabel?: string;
30
+ };
31
+ declare const DatePicker: import("svelte").Component<DatePickerProps, {}, "value">;
32
+ type DatePicker = ReturnType<typeof DatePicker>;
33
+ export default DatePicker;
34
+ //# sourceMappingURL=DatePicker.svelte.d.ts.map