@joewinke/jatui 0.41.0 → 0.42.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/CLAUDE.md +9 -3
- package/dist/components/AnimatedNumber.svelte +22 -4
- package/dist/components/BookingScheduler.svelte +412 -0
- package/dist/components/BookingScheduler.svelte.d.ts +66 -0
- package/dist/components/CalendarPicker.svelte +190 -205
- package/dist/components/CalendarPicker.svelte.d.ts +19 -1
- package/dist/components/DatePicker.svelte +419 -0
- package/dist/components/DatePicker.svelte.d.ts +44 -0
- package/dist/components/DateRangePicker.svelte +471 -5
- package/dist/components/DateRangePicker.svelte.d.ts +32 -3
- package/dist/components/EventCalendar.svelte +1324 -0
- package/dist/components/EventCalendar.svelte.d.ts +101 -0
- package/dist/components/TimeSlotPicker.svelte +174 -108
- package/dist/components/TimeSlotPicker.svelte.d.ts +27 -0
- package/dist/components/calendar/CalendarHeader.svelte +98 -0
- package/dist/components/calendar/CalendarHeader.svelte.d.ts +24 -0
- package/dist/components/calendar/MonthGrid.svelte +361 -0
- package/dist/components/calendar/MonthGrid.svelte.d.ts +55 -0
- package/dist/components/calendar/calendarGrid.d.ts +219 -0
- package/dist/components/calendar/calendarGrid.js +494 -0
- package/dist/components/calendar/createCalendarGrid.d.ts +25 -0
- package/dist/components/calendar/createCalendarGrid.js +57 -0
- package/dist/components/calendar/dayOverlap.d.ts +92 -0
- package/dist/components/calendar/dayOverlap.js +147 -0
- package/dist/components/calendar/eventCalendar.d.ts +75 -0
- package/dist/components/calendar/eventCalendar.js +187 -0
- package/dist/components/calendar/index.d.ts +32 -0
- package/dist/components/calendar/index.js +25 -0
- package/dist/components/calendar/monthPacking.d.ts +97 -0
- package/dist/components/calendar/monthPacking.js +254 -0
- package/dist/components/calendar/timeSlots.d.ts +57 -0
- package/dist/components/calendar/timeSlots.js +163 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +11 -0
- package/dist/showcase/Globe.svelte +1 -1
- package/package.json +6 -1
|
@@ -5,13 +5,33 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Pass `getAvailability` callback to load availability data for the visible date range.
|
|
7
7
|
* Pass `isDateEnabled` to control which dates are selectable.
|
|
8
|
+
*
|
|
9
|
+
* Built on the shared calendar grid (jatui-lfxyy): a real `role="grid"` with the
|
|
10
|
+
* APG keyboard model (arrows, Home/End, PageUp/PageDown, Shift+Page for years),
|
|
11
|
+
* locale-aware week start (opt in with `weekStart="locale"`), token-coloured
|
|
12
|
+
* availability dots and a static accent map (the old `hover:bg-${accentColor}`
|
|
13
|
+
* classes were never emitted by Tailwind).
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
import type { AvailabilityCounts } from '../types/booking';
|
|
17
|
+
import MonthGrid from './calendar/MonthGrid.svelte';
|
|
18
|
+
import CalendarHeader from './calendar/CalendarHeader.svelte';
|
|
19
|
+
import { createCalendarGrid } from './calendar/createCalendarGrid';
|
|
20
|
+
import {
|
|
21
|
+
addDays,
|
|
22
|
+
buildMonthMatrix,
|
|
23
|
+
calendarAccentStyle,
|
|
24
|
+
compareDays,
|
|
25
|
+
formatMonthYear,
|
|
26
|
+
resolveWeekStart,
|
|
27
|
+
startOfDay,
|
|
28
|
+
toIsoDate,
|
|
29
|
+
type Weekday
|
|
30
|
+
} from './calendar/calendarGrid';
|
|
11
31
|
|
|
12
32
|
export type { AvailabilityCounts };
|
|
13
33
|
|
|
14
|
-
|
|
34
|
+
type Level = 'none' | 'low' | 'medium' | 'high' | 'full';
|
|
15
35
|
|
|
16
36
|
let {
|
|
17
37
|
onSelect,
|
|
@@ -21,6 +41,12 @@
|
|
|
21
41
|
minNoticeHours = 24,
|
|
22
42
|
weekdaysOnly = true,
|
|
23
43
|
accentColor = 'primary',
|
|
44
|
+
selectedDate = $bindable(null),
|
|
45
|
+
weekStart = 0,
|
|
46
|
+
locale,
|
|
47
|
+
showLegend,
|
|
48
|
+
size = 'md',
|
|
49
|
+
class: className = ''
|
|
24
50
|
}: {
|
|
25
51
|
onSelect: (date: Date) => void;
|
|
26
52
|
getAvailability?: (startDate: Date, endDate: Date) => Promise<AvailabilityCounts>;
|
|
@@ -28,263 +54,222 @@
|
|
|
28
54
|
maxDaysInAdvance?: number;
|
|
29
55
|
minNoticeHours?: number;
|
|
30
56
|
weekdaysOnly?: boolean;
|
|
57
|
+
/** primary | secondary | accent | info | success | warning | error | neutral */
|
|
31
58
|
accentColor?: string;
|
|
59
|
+
/** The picked day (bindable). Shown as the solid cell. */
|
|
60
|
+
selectedDate?: Date | null;
|
|
61
|
+
/** First column (0 = Sunday, the default) or `'locale'` for the viewer's convention. */
|
|
62
|
+
weekStart?: Weekday | 'locale';
|
|
63
|
+
/** BCP 47 locale for month/weekday names. Defaults to the runtime locale. */
|
|
64
|
+
locale?: string;
|
|
65
|
+
/** Availability legend under the grid. Defaults to on when `getAvailability` is set. */
|
|
66
|
+
showLegend?: boolean;
|
|
67
|
+
size?: 'sm' | 'md' | 'lg';
|
|
68
|
+
class?: string;
|
|
32
69
|
} = $props();
|
|
33
70
|
|
|
34
|
-
|
|
71
|
+
const firstDay = $derived(resolveWeekStart(weekStart, locale));
|
|
35
72
|
let availabilityCounts = $state(new Map<string, { available: number; total: number }>());
|
|
36
73
|
let isLoadingAvailability = $state(false);
|
|
74
|
+
let requestId = 0;
|
|
37
75
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const firstDay = new Date(year, month, 1);
|
|
43
|
-
const startDate = new Date(firstDay);
|
|
44
|
-
startDate.setDate(startDate.getDate() - startDate.getDay());
|
|
45
|
-
|
|
46
|
-
const days: Date[] = [];
|
|
47
|
-
const current = new Date(startDate);
|
|
76
|
+
function today(): Date {
|
|
77
|
+
return startOfDay(new Date());
|
|
78
|
+
}
|
|
48
79
|
|
|
49
|
-
|
|
50
|
-
days.push(new Date(current));
|
|
51
|
-
current.setDate(current.getDate() + 1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return days;
|
|
55
|
-
});
|
|
80
|
+
const maxDate = $derived(addDays(today(), maxDaysInAdvance));
|
|
56
81
|
|
|
57
82
|
function defaultIsDateEnabled(date: Date): boolean {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (date < today) return false;
|
|
62
|
-
|
|
63
|
-
const maxDate = new Date(today);
|
|
64
|
-
maxDate.setDate(maxDate.getDate() + maxDaysInAdvance);
|
|
65
|
-
if (date > maxDate) return false;
|
|
83
|
+
const t = today();
|
|
84
|
+
if (compareDays(date, t) < 0) return false;
|
|
85
|
+
if (compareDays(date, maxDate) > 0) return false;
|
|
66
86
|
|
|
67
87
|
const minNoticeDate = new Date();
|
|
68
88
|
minNoticeDate.setHours(minNoticeDate.getHours() + minNoticeHours);
|
|
69
|
-
|
|
70
|
-
if (date < minNoticeDate) return false;
|
|
89
|
+
if (compareDays(date, minNoticeDate) < 0) return false;
|
|
71
90
|
|
|
72
91
|
if (weekdaysOnly) {
|
|
73
92
|
const dayOfWeek = date.getDay();
|
|
74
93
|
if (dayOfWeek === 0 || dayOfWeek === 6) return false;
|
|
75
94
|
}
|
|
76
|
-
|
|
77
95
|
return true;
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
return defaultIsDateEnabled(date);
|
|
98
|
+
function isDisabled(date: Date): boolean {
|
|
99
|
+
return isDateEnabled ? !isDateEnabled(date) : !defaultIsDateEnabled(date);
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
date.getFullYear() === today.getFullYear()
|
|
91
|
-
);
|
|
92
|
-
}
|
|
102
|
+
// Past months and months beyond the booking window are not reachable — but a
|
|
103
|
+
// custom `isDateEnabled` owns its own rules, so only the defaults clamp.
|
|
104
|
+
const minNav = $derived(isDateEnabled ? null : today());
|
|
105
|
+
const maxNav = $derived(isDateEnabled ? null : maxDate);
|
|
106
|
+
const cal = createCalendarGrid({ initial: () => selectedDate, min: () => minNav, max: () => maxNav });
|
|
93
107
|
|
|
94
|
-
function
|
|
95
|
-
|
|
108
|
+
function handleSelect(date: Date) {
|
|
109
|
+
selectedDate = date;
|
|
110
|
+
onSelect(date);
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
function
|
|
99
|
-
const newDate = new Date(viewDate);
|
|
100
|
-
newDate.setMonth(newDate.getMonth() - 1);
|
|
101
|
-
viewDate = newDate;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function nextMonth() {
|
|
105
|
-
const newDate = new Date(viewDate);
|
|
106
|
-
newDate.setMonth(newDate.getMonth() + 1);
|
|
107
|
-
viewDate = newDate;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function handleDateClick(date: Date) {
|
|
111
|
-
if (checkDateEnabled(date)) {
|
|
112
|
-
onSelect(date);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const canGoPrev = $derived(() => {
|
|
117
|
-
const today = new Date();
|
|
118
|
-
return (
|
|
119
|
-
viewDate.getMonth() > today.getMonth() ||
|
|
120
|
-
viewDate.getFullYear() > today.getFullYear()
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const canGoNext = $derived(() => {
|
|
125
|
-
const today = new Date();
|
|
126
|
-
const maxDate = new Date(today);
|
|
127
|
-
maxDate.setDate(maxDate.getDate() + maxDaysInAdvance);
|
|
128
|
-
return (
|
|
129
|
-
viewDate.getMonth() < maxDate.getMonth() ||
|
|
130
|
-
viewDate.getFullYear() < maxDate.getFullYear()
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
async function loadAvailability() {
|
|
113
|
+
async function loadAvailability(month: Date) {
|
|
135
114
|
if (!getAvailability) return;
|
|
136
|
-
|
|
115
|
+
const id = ++requestId;
|
|
137
116
|
isLoadingAvailability = true;
|
|
138
117
|
try {
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const counts = await getAvailability(startDate, endDate);
|
|
118
|
+
// Always the full six-week matrix, as before — callers size their query on it.
|
|
119
|
+
const days = buildMonthMatrix(month, firstDay, true).flat();
|
|
120
|
+
const counts = await getAvailability(days[0].date, days[days.length - 1].date);
|
|
121
|
+
if (id !== requestId) return; // a newer month won the race
|
|
144
122
|
// AvailabilityCounts is a Map, but tolerate plain-object returns too —
|
|
145
123
|
// Object.entries(aMap) is [] and silently kills the dots.
|
|
146
|
-
const entries =
|
|
147
|
-
counts instanceof Map ? counts.entries() : Object.entries(counts ?? {});
|
|
124
|
+
const entries = counts instanceof Map ? counts.entries() : Object.entries(counts ?? {});
|
|
148
125
|
const map = new Map<string, { available: number; total: number }>();
|
|
149
|
-
for (const [key, value] of entries)
|
|
150
|
-
map.set(key, value);
|
|
151
|
-
}
|
|
126
|
+
for (const [key, value] of entries) map.set(key, value);
|
|
152
127
|
availabilityCounts = map;
|
|
153
128
|
} catch {
|
|
154
129
|
// Silently ignore — dots won't show
|
|
155
130
|
} finally {
|
|
156
|
-
isLoadingAvailability = false;
|
|
131
|
+
if (id === requestId) isLoadingAvailability = false;
|
|
157
132
|
}
|
|
158
133
|
}
|
|
159
134
|
|
|
160
135
|
$effect(() => {
|
|
161
|
-
|
|
162
|
-
loadAvailability();
|
|
136
|
+
void loadAvailability(cal.viewMonth);
|
|
163
137
|
});
|
|
164
138
|
|
|
165
|
-
function getAvailabilityLevel(date: Date):
|
|
139
|
+
function getAvailabilityLevel(date: Date): Level {
|
|
166
140
|
// Key by LOCAL calendar date — toISOString() shifts to UTC and is off by
|
|
167
141
|
// one day for positive-offset timezones (calendar days are local midnights).
|
|
168
|
-
const
|
|
169
|
-
const counts = availabilityCounts.get(dateStr);
|
|
170
|
-
|
|
142
|
+
const counts = availabilityCounts.get(toIsoDate(date));
|
|
171
143
|
if (!counts || counts.total === 0) return 'none';
|
|
172
|
-
|
|
173
144
|
const ratio = counts.available / counts.total;
|
|
174
145
|
if (ratio === 0) return 'full';
|
|
175
146
|
if (ratio <= 0.25) return 'low';
|
|
176
147
|
if (ratio <= 0.6) return 'medium';
|
|
177
148
|
return 'high';
|
|
178
149
|
}
|
|
179
|
-
</script>
|
|
180
150
|
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
aria-label="Previous month"
|
|
189
|
-
>
|
|
190
|
-
<!-- ChevronLeft -->
|
|
191
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
192
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
193
|
-
</svg>
|
|
194
|
-
</button>
|
|
151
|
+
const DOTS: Record<Level, { n: number; tone: string; label: string }> = {
|
|
152
|
+
high: { n: 3, tone: 'positive', label: 'many slots' },
|
|
153
|
+
medium: { n: 2, tone: 'caution', label: 'some slots' },
|
|
154
|
+
low: { n: 1, tone: 'negative', label: 'few slots' },
|
|
155
|
+
full: { n: 1, tone: 'neutral', label: 'fully booked' },
|
|
156
|
+
none: { n: 0, tone: 'neutral', label: '' }
|
|
157
|
+
};
|
|
195
158
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</h3>
|
|
199
|
-
|
|
200
|
-
<button
|
|
201
|
-
onclick={nextMonth}
|
|
202
|
-
disabled={!canGoNext()}
|
|
203
|
-
class="btn btn-ghost btn-sm btn-circle disabled:opacity-30"
|
|
204
|
-
aria-label="Next month"
|
|
205
|
-
>
|
|
206
|
-
<!-- ChevronRight -->
|
|
207
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
208
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
209
|
-
</svg>
|
|
210
|
-
</button>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<!-- Day Headers -->
|
|
214
|
-
<div class="grid grid-cols-7 gap-1 mb-2">
|
|
215
|
-
{#each DAYS_SHORT as day}
|
|
216
|
-
<div class="text-center text-[0.8125rem] text-base-content/45 py-2">
|
|
217
|
-
{day}
|
|
218
|
-
</div>
|
|
219
|
-
{/each}
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
<!-- Calendar Grid -->
|
|
223
|
-
<div class="grid grid-cols-7 gap-1">
|
|
224
|
-
{#each calendarDays() as date}
|
|
225
|
-
{@const available = checkDateEnabled(date)}
|
|
226
|
-
{@const today = isToday(date)}
|
|
227
|
-
{@const currentMonth = isCurrentMonth(date)}
|
|
228
|
-
{@const availLevel = getAvailabilityLevel(date)}
|
|
229
|
-
|
|
230
|
-
<button
|
|
231
|
-
onclick={() => handleDateClick(date)}
|
|
232
|
-
disabled={!available}
|
|
233
|
-
class="aspect-square flex flex-col items-center justify-center rounded-lg text-[0.9375rem] font-medium
|
|
234
|
-
transition-all duration-200 relative
|
|
235
|
-
{currentMonth ? '' : 'opacity-30'}
|
|
236
|
-
{available
|
|
237
|
-
? `hover:bg-${accentColor} hover:text-${accentColor}-content cursor-pointer bg-${accentColor}/15 text-${accentColor}`
|
|
238
|
-
: 'text-base-content/30 cursor-not-allowed'}
|
|
239
|
-
{today ? `ring-2 ring-${accentColor} ring-offset-2 ring-offset-base-100` : ''}"
|
|
240
|
-
>
|
|
241
|
-
<span>{date.getDate()}</span>
|
|
242
|
-
{#if currentMonth && available && !isLoadingAvailability}
|
|
243
|
-
<div class="flex gap-0.5 mt-0.5">
|
|
244
|
-
{#if availLevel === 'high'}
|
|
245
|
-
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
|
246
|
-
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
|
247
|
-
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
|
248
|
-
{:else if availLevel === 'medium'}
|
|
249
|
-
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
|
|
250
|
-
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
|
|
251
|
-
{:else if availLevel === 'low'}
|
|
252
|
-
<span class="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
|
253
|
-
{:else if availLevel === 'full'}
|
|
254
|
-
<span class="w-1.5 h-1.5 rounded-full bg-base-content/20"></span>
|
|
255
|
-
{/if}
|
|
256
|
-
</div>
|
|
257
|
-
{/if}
|
|
258
|
-
</button>
|
|
259
|
-
{/each}
|
|
260
|
-
</div>
|
|
159
|
+
const legendOn = $derived(showLegend ?? !!getAvailability);
|
|
160
|
+
</script>
|
|
261
161
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
162
|
+
<div class="calendar-picker {className}" data-calendar-root style={calendarAccentStyle(accentColor)}>
|
|
163
|
+
<CalendarHeader
|
|
164
|
+
label={formatMonthYear(cal.viewMonth, locale)}
|
|
165
|
+
onPrev={() => cal.shift(-1)}
|
|
166
|
+
onNext={() => cal.shift(1)}
|
|
167
|
+
canPrev={cal.canPrev}
|
|
168
|
+
canNext={cal.canNext}
|
|
169
|
+
size={size === 'sm' ? 'sm' : 'md'}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<MonthGrid
|
|
173
|
+
month={cal.viewMonth}
|
|
174
|
+
focusedDate={cal.focusedDate}
|
|
175
|
+
weekStart={firstDay}
|
|
176
|
+
{locale}
|
|
177
|
+
selected={selectedDate}
|
|
178
|
+
{isDisabled}
|
|
179
|
+
min={minNav}
|
|
180
|
+
max={maxNav}
|
|
181
|
+
fixedWeeks
|
|
182
|
+
highlightEnabled
|
|
183
|
+
{size}
|
|
184
|
+
onSelect={handleSelect}
|
|
185
|
+
onFocusDate={cal.focus}
|
|
186
|
+
describeDay={(date) => (isLoadingAvailability || isDisabled(date) ? undefined : DOTS[getAvailabilityLevel(date)].label || undefined)}
|
|
187
|
+
>
|
|
188
|
+
{#snippet day(d)}
|
|
189
|
+
{@const level = d.inMonth && !d.disabled && !isLoadingAvailability ? getAvailabilityLevel(d.date) : 'none'}
|
|
190
|
+
{#if DOTS[level].n > 0}
|
|
191
|
+
<span class="calendar-picker__dots" data-tone={DOTS[level].tone}>
|
|
192
|
+
{#each { length: DOTS[level].n } as _, i (i)}
|
|
193
|
+
<span class="calendar-picker__dot"></span>
|
|
194
|
+
{/each}
|
|
195
|
+
</span>
|
|
196
|
+
{/if}
|
|
197
|
+
{/snippet}
|
|
198
|
+
</MonthGrid>
|
|
199
|
+
|
|
200
|
+
{#if legendOn}
|
|
201
|
+
<div class="calendar-picker__legend">
|
|
202
|
+
<span class="calendar-picker__legend-item">
|
|
203
|
+
<span class="calendar-picker__dots" data-tone="positive" aria-hidden="true">
|
|
204
|
+
<span class="calendar-picker__dot"></span><span class="calendar-picker__dot"></span><span class="calendar-picker__dot"></span>
|
|
205
|
+
</span>
|
|
206
|
+
Many slots
|
|
207
|
+
</span>
|
|
208
|
+
<span class="calendar-picker__legend-item">
|
|
209
|
+
<span class="calendar-picker__dots" data-tone="caution" aria-hidden="true">
|
|
210
|
+
<span class="calendar-picker__dot"></span><span class="calendar-picker__dot"></span>
|
|
211
|
+
</span>
|
|
212
|
+
Some slots
|
|
213
|
+
</span>
|
|
214
|
+
<span class="calendar-picker__legend-item">
|
|
215
|
+
<span class="calendar-picker__dots" data-tone="negative" aria-hidden="true"><span class="calendar-picker__dot"></span></span>
|
|
216
|
+
Few slots
|
|
217
|
+
</span>
|
|
218
|
+
<span class="calendar-picker__legend-item">
|
|
219
|
+
<span class="calendar-picker__today-swatch" aria-hidden="true"></span>
|
|
220
|
+
Today
|
|
221
|
+
</span>
|
|
288
222
|
</div>
|
|
289
223
|
{/if}
|
|
290
224
|
</div>
|
|
225
|
+
|
|
226
|
+
<style>
|
|
227
|
+
.calendar-picker__dots {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
gap: 2px;
|
|
230
|
+
--_dot: var(--jatui-status-neutral, color-mix(in oklab, var(--color-base-content) 40%, transparent));
|
|
231
|
+
}
|
|
232
|
+
.calendar-picker__dots[data-tone='positive'] {
|
|
233
|
+
--_dot: var(--jatui-status-positive, var(--color-success));
|
|
234
|
+
}
|
|
235
|
+
.calendar-picker__dots[data-tone='caution'] {
|
|
236
|
+
--_dot: var(--jatui-status-caution, var(--color-warning));
|
|
237
|
+
}
|
|
238
|
+
.calendar-picker__dots[data-tone='negative'] {
|
|
239
|
+
--_dot: var(--jatui-status-negative, var(--color-error));
|
|
240
|
+
}
|
|
241
|
+
.calendar-picker__dot {
|
|
242
|
+
width: 0.3125rem;
|
|
243
|
+
height: 0.3125rem;
|
|
244
|
+
border-radius: 9999px;
|
|
245
|
+
background: var(--_dot);
|
|
246
|
+
}
|
|
247
|
+
/* On the solid (picked) day the dots take the accent's content colour. */
|
|
248
|
+
:global(.jatui-month-grid__day.is-solid) .calendar-picker__dot {
|
|
249
|
+
background: currentColor;
|
|
250
|
+
opacity: 0.85;
|
|
251
|
+
}
|
|
252
|
+
.calendar-picker__legend {
|
|
253
|
+
display: flex;
|
|
254
|
+
flex-wrap: wrap;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 0.5rem 1.25rem;
|
|
257
|
+
margin-block-start: 1rem;
|
|
258
|
+
padding-block-start: 0.75rem;
|
|
259
|
+
border-block-start: 1px solid var(--jatui-separator, var(--color-base-300));
|
|
260
|
+
font-size: 0.75rem;
|
|
261
|
+
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
|
262
|
+
}
|
|
263
|
+
.calendar-picker__legend-item {
|
|
264
|
+
display: inline-flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
gap: 0.5rem;
|
|
267
|
+
}
|
|
268
|
+
.calendar-picker__today-swatch {
|
|
269
|
+
width: 0.875rem;
|
|
270
|
+
height: 0.875rem;
|
|
271
|
+
border-radius: var(--radius-field, 0.25rem);
|
|
272
|
+
box-shadow: inset 0 0 0 1px
|
|
273
|
+
color-mix(in oklab, var(--jatui-cal-accent, var(--jatui-selection-solid, var(--color-primary))) 55%, transparent);
|
|
274
|
+
}
|
|
275
|
+
</style>
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Pass `getAvailability` callback to load availability data for the visible date range.
|
|
6
6
|
* Pass `isDateEnabled` to control which dates are selectable.
|
|
7
|
+
*
|
|
8
|
+
* Built on the shared calendar grid (jatui-lfxyy): a real `role="grid"` with the
|
|
9
|
+
* APG keyboard model (arrows, Home/End, PageUp/PageDown, Shift+Page for years),
|
|
10
|
+
* locale-aware week start (opt in with `weekStart="locale"`), token-coloured
|
|
11
|
+
* availability dots and a static accent map (the old `hover:bg-${accentColor}`
|
|
12
|
+
* classes were never emitted by Tailwind).
|
|
7
13
|
*/
|
|
8
14
|
import type { AvailabilityCounts } from '../types/booking';
|
|
15
|
+
import { type Weekday } from './calendar/calendarGrid';
|
|
9
16
|
type $$ComponentProps = {
|
|
10
17
|
onSelect: (date: Date) => void;
|
|
11
18
|
getAvailability?: (startDate: Date, endDate: Date) => Promise<AvailabilityCounts>;
|
|
@@ -13,10 +20,21 @@ type $$ComponentProps = {
|
|
|
13
20
|
maxDaysInAdvance?: number;
|
|
14
21
|
minNoticeHours?: number;
|
|
15
22
|
weekdaysOnly?: boolean;
|
|
23
|
+
/** primary | secondary | accent | info | success | warning | error | neutral */
|
|
16
24
|
accentColor?: string;
|
|
25
|
+
/** The picked day (bindable). Shown as the solid cell. */
|
|
26
|
+
selectedDate?: Date | null;
|
|
27
|
+
/** First column (0 = Sunday, the default) or `'locale'` for the viewer's convention. */
|
|
28
|
+
weekStart?: Weekday | 'locale';
|
|
29
|
+
/** BCP 47 locale for month/weekday names. Defaults to the runtime locale. */
|
|
30
|
+
locale?: string;
|
|
31
|
+
/** Availability legend under the grid. Defaults to on when `getAvailability` is set. */
|
|
32
|
+
showLegend?: boolean;
|
|
33
|
+
size?: 'sm' | 'md' | 'lg';
|
|
34
|
+
class?: string;
|
|
17
35
|
};
|
|
18
36
|
declare const CalendarPicker: import("svelte").Component<$$ComponentProps, {
|
|
19
37
|
AvailabilityCounts: typeof AvailabilityCounts;
|
|
20
|
-
}, "">;
|
|
38
|
+
}, "selectedDate">;
|
|
21
39
|
type CalendarPicker = ReturnType<typeof CalendarPicker>;
|
|
22
40
|
export default CalendarPicker;
|