@makolabs/ripple 3.3.0 → 3.4.1
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/dist/forms/DateRange.svelte +85 -460
- package/dist/forms/Textarea.svelte +2 -0
- package/dist/forms/calendar/Calendar.svelte +369 -164
- package/dist/forms/calendar/calendar-types.d.ts +15 -5
- package/dist/forms/date-picker/DatePicker.svelte +0 -1
- package/dist/forms/form-types.d.ts +6 -0
- package/dist/forms/month-picker/MonthPicker.svelte +119 -40
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/layout/comment-composer/CommentComposer.svelte +111 -0
- package/dist/layout/comment-composer/CommentComposer.svelte.d.ts +4 -0
- package/dist/layout/comment-composer/comment-composer-types.d.ts +99 -0
- package/dist/layout/comment-composer/comment-composer-types.js +1 -0
- package/dist/layout/comment-composer/comment-composer.d.ts +76 -0
- package/dist/layout/comment-composer/comment-composer.js +47 -0
- package/package.json +1 -1
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Size } from '../variants.js';
|
|
5
5
|
import { formSizeTokens } from './form-size.js';
|
|
6
|
+
import Popover from '../elements/popover/Popover.svelte';
|
|
7
|
+
import Calendar from './calendar/Calendar.svelte';
|
|
6
8
|
import type { DateRangeProps } from '../index.js';
|
|
7
|
-
import Portal from '../utils/Portal.svelte';
|
|
8
|
-
import { fly } from 'svelte/transition';
|
|
9
|
-
import { quintOut } from 'svelte/easing';
|
|
10
|
-
import { onMount } from 'svelte';
|
|
11
9
|
|
|
12
10
|
let {
|
|
13
11
|
startDate = $bindable(),
|
|
14
12
|
endDate = $bindable(),
|
|
15
|
-
minDate
|
|
16
|
-
maxDate
|
|
13
|
+
minDate,
|
|
14
|
+
maxDate,
|
|
17
15
|
disabled = false,
|
|
18
16
|
class: className = '',
|
|
19
17
|
placeholder = 'Select date range',
|
|
@@ -28,224 +26,47 @@
|
|
|
28
26
|
testId
|
|
29
27
|
}: DateRangeProps = $props();
|
|
30
28
|
|
|
29
|
+
let open = $state(false);
|
|
31
30
|
const tokens = $derived(formSizeTokens[size]);
|
|
31
|
+
const hasErrors = $derived(errors?.length > 0);
|
|
32
|
+
const hasValue = $derived(!!startDate || !!endDate);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
typeof window !== 'undefined' && window.matchMedia('(max-width: 639.98px)').matches
|
|
35
|
-
);
|
|
36
|
-
onMount(() => {
|
|
37
|
-
const mql = window.matchMedia('(max-width: 639.98px)');
|
|
38
|
-
const handler = (e: MediaQueryListEvent) => (isMobile = e.matches);
|
|
39
|
-
mql.addEventListener('change', handler);
|
|
40
|
-
return () => mql.removeEventListener('change', handler);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
let isOpen = $state(false);
|
|
44
|
-
let hoveredDate = $state<Date | null>(null);
|
|
45
|
-
let datePickerRef = $state<HTMLDivElement | null>(null);
|
|
46
|
-
let calendarRef = $state<HTMLDivElement | null>(null);
|
|
47
|
-
|
|
48
|
-
let viewMode = $state<'days' | 'months' | 'years'>('days');
|
|
49
|
-
let viewDate = $state(new Date());
|
|
50
|
-
|
|
51
|
-
const handleOutsideClick = (event: MouseEvent) => {
|
|
52
|
-
if (isOpen && datePickerRef && calendarRef) {
|
|
53
|
-
const target = event.target as Node;
|
|
54
|
-
if (!datePickerRef.contains(target) && !calendarRef.contains(target)) {
|
|
55
|
-
isOpen = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
function formatDate(date: Date | null): string {
|
|
34
|
+
function formatDate(date: Date | undefined): string {
|
|
61
35
|
if (!date) return '';
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
return format.replace('yyyy',
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getDaysInMonth(month: Date): {
|
|
69
|
-
date: Date;
|
|
70
|
-
isCurrentMonth: boolean;
|
|
71
|
-
isToday: boolean;
|
|
72
|
-
isSelected: boolean;
|
|
73
|
-
isInRange: boolean;
|
|
74
|
-
isDisabled: boolean;
|
|
75
|
-
}[] {
|
|
76
|
-
const year = month.getFullYear();
|
|
77
|
-
const monthIndex = month.getMonth();
|
|
78
|
-
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
|
|
79
|
-
|
|
80
|
-
const firstDay = new Date(year, monthIndex, 1);
|
|
81
|
-
|
|
82
|
-
const firstDayOfWeek = firstDay.getDay();
|
|
83
|
-
|
|
84
|
-
const daysFromPrevMonth = firstDayOfWeek;
|
|
85
|
-
|
|
86
|
-
const prevMonth = new Date(year, monthIndex, 0);
|
|
87
|
-
const prevMonthDays = prevMonth.getDate();
|
|
88
|
-
|
|
89
|
-
const daysInCalendar = 42; // 6 rows of 7 days
|
|
90
|
-
const daysFromNextMonth = daysInCalendar - daysInMonth - daysFromPrevMonth;
|
|
91
|
-
|
|
92
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
93
|
-
const today = new Date();
|
|
94
|
-
today.setHours(0, 0, 0, 0);
|
|
95
|
-
|
|
96
|
-
const days = [];
|
|
97
|
-
|
|
98
|
-
for (let i = daysFromPrevMonth - 1; i >= 0; i--) {
|
|
99
|
-
const date = new Date(year, monthIndex - 1, prevMonthDays - i);
|
|
100
|
-
days.push({
|
|
101
|
-
date,
|
|
102
|
-
isCurrentMonth: false,
|
|
103
|
-
isToday: date.getTime() === today.getTime(),
|
|
104
|
-
isSelected: isDateSelected(date),
|
|
105
|
-
isInRange: isDateInRange(date),
|
|
106
|
-
isDisabled: isDateDisabled(date)
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for (let i = 1; i <= daysInMonth; i++) {
|
|
111
|
-
const date = new Date(year, monthIndex, i);
|
|
112
|
-
days.push({
|
|
113
|
-
date,
|
|
114
|
-
isCurrentMonth: true,
|
|
115
|
-
isToday: date.getTime() === today.getTime(),
|
|
116
|
-
isSelected: isDateSelected(date),
|
|
117
|
-
isInRange: isDateInRange(date),
|
|
118
|
-
isDisabled: isDateDisabled(date)
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for (let i = 1; i <= daysFromNextMonth; i++) {
|
|
123
|
-
const date = new Date(year, monthIndex + 1, i);
|
|
124
|
-
days.push({
|
|
125
|
-
date,
|
|
126
|
-
isCurrentMonth: false,
|
|
127
|
-
isToday: date.getTime() === today.getTime(),
|
|
128
|
-
isSelected: isDateSelected(date),
|
|
129
|
-
isInRange: isDateInRange(date),
|
|
130
|
-
isDisabled: isDateDisabled(date)
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return days;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function isDateSelected(date: Date): boolean {
|
|
138
|
-
return Boolean(
|
|
139
|
-
(startDate && isSameDate(date, startDate)) || (endDate && isSameDate(date, endDate))
|
|
140
|
-
);
|
|
36
|
+
const yyyy = String(date.getFullYear());
|
|
37
|
+
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
|
38
|
+
const dd = String(date.getDate()).padStart(2, '0');
|
|
39
|
+
return format.replace('yyyy', yyyy).replace('MM', MM).replace('dd', dd);
|
|
141
40
|
}
|
|
142
41
|
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return date >= hoverStart && date <= hoverEnd;
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return date >= startDate && date <= endDate;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function isDateDisabled(date: Date): boolean {
|
|
157
|
-
return date < minDate || date > maxDate || disabled;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function isSameDate(date1: Date, date2: Date): boolean {
|
|
161
|
-
return (
|
|
162
|
-
date1.getDate() === date2.getDate() &&
|
|
163
|
-
date1.getMonth() === date2.getMonth() &&
|
|
164
|
-
date1.getFullYear() === date2.getFullYear()
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function handleDateClick(date: Date): void {
|
|
169
|
-
if (isDateDisabled(date)) return;
|
|
170
|
-
|
|
171
|
-
if (!startDate || (startDate && endDate)) {
|
|
172
|
-
startDate = date;
|
|
173
|
-
endDate = undefined;
|
|
174
|
-
} else if (startDate && !endDate) {
|
|
175
|
-
if (date < startDate) {
|
|
176
|
-
endDate = startDate;
|
|
177
|
-
startDate = date;
|
|
178
|
-
} else {
|
|
179
|
-
endDate = date;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
42
|
+
const triggerText = $derived.by(() => {
|
|
43
|
+
if (startDate && endDate) return `${formatDate(startDate)} - ${formatDate(endDate)}`;
|
|
44
|
+
if (startDate) return `${formatDate(startDate)} - Select end date`;
|
|
45
|
+
return placeholder;
|
|
46
|
+
});
|
|
182
47
|
|
|
48
|
+
function handleSelect(sel: Date | { from: Date | null; to: Date | null }): void {
|
|
49
|
+
// Calendar emits the {from, to} shape in range mode. The Date
|
|
50
|
+
// branch is unreachable here but narrows the type for the compiler.
|
|
51
|
+
if (sel instanceof Date) return;
|
|
52
|
+
startDate = sel.from ?? undefined;
|
|
53
|
+
endDate = sel.to ?? undefined;
|
|
183
54
|
onselect?.({ startDate, endDate });
|
|
184
|
-
|
|
185
|
-
if (startDate && endDate) {
|
|
186
|
-
isOpen = false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function handleDateHover(date: Date): void {
|
|
191
|
-
hoveredDate = date;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function nextMonth(): void {
|
|
195
|
-
viewDate = new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function prevMonth(): void {
|
|
199
|
-
viewDate = new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function showMonths(): void {
|
|
203
|
-
viewMode = 'months';
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function showYears(): void {
|
|
207
|
-
viewMode = 'years';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function selectMonth(month: number): void {
|
|
211
|
-
viewDate = new Date(viewDate.getFullYear(), month, 1);
|
|
212
|
-
viewMode = 'days';
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function selectYear(year: number): void {
|
|
216
|
-
viewDate = new Date(year, viewDate.getMonth(), 1);
|
|
217
|
-
viewMode = 'months';
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function getMonthName(monthIndex: number): string {
|
|
221
|
-
return new Date(2000, monthIndex, 1).toLocaleString('default', { month: 'long' });
|
|
55
|
+
if (startDate && endDate) open = false;
|
|
222
56
|
}
|
|
223
57
|
|
|
224
|
-
function
|
|
225
|
-
if (disabled) return;
|
|
226
|
-
isOpen = !isOpen;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function clearDates(event: Event): void {
|
|
58
|
+
function clearDates(event: MouseEvent): void {
|
|
230
59
|
event.stopPropagation();
|
|
231
60
|
startDate = undefined;
|
|
232
61
|
endDate = undefined;
|
|
233
62
|
onselect?.({ startDate, endDate });
|
|
234
63
|
}
|
|
235
64
|
|
|
236
|
-
$effect(() => {
|
|
237
|
-
if (startDate && endDate) {
|
|
238
|
-
viewDate = new Date(startDate);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
65
|
function getValue(): string {
|
|
243
66
|
return `${startDate ? formatDate(startDate) : ''}:${endDate ? formatDate(endDate) : ''}`;
|
|
244
67
|
}
|
|
245
68
|
</script>
|
|
246
69
|
|
|
247
|
-
<svelte:window onmousedown={handleOutsideClick} />
|
|
248
|
-
|
|
249
70
|
<input type="hidden" name={`${name}[start]`} value={startDate?.toISOString()} />
|
|
250
71
|
<input type="hidden" name={`${name}[end]`} value={endDate?.toISOString()} />
|
|
251
72
|
<input type="hidden" name={`${name}[format]`} value={format} />
|
|
@@ -253,10 +74,9 @@
|
|
|
253
74
|
|
|
254
75
|
<div
|
|
255
76
|
class={cn('relative block w-full', className)}
|
|
256
|
-
bind:this={datePickerRef}
|
|
257
77
|
data-testid={buildTestId('date-range', undefined, testId)}
|
|
258
78
|
>
|
|
259
|
-
<
|
|
79
|
+
<Popover trigger="manual" bind:open placement="bottom" {disabled}>
|
|
260
80
|
<button
|
|
261
81
|
{id}
|
|
262
82
|
type="button"
|
|
@@ -269,23 +89,21 @@
|
|
|
269
89
|
tokens.shadow,
|
|
270
90
|
disabled
|
|
271
91
|
? 'bg-default-100 text-default-400 cursor-not-allowed opacity-50'
|
|
272
|
-
:
|
|
273
|
-
? 'border-danger-300 focus-within:
|
|
92
|
+
: hasErrors
|
|
93
|
+
? 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500 focus-within:ring-2'
|
|
274
94
|
: 'focus-visible:border-primary-500 focus-visible:ring-primary-500 hover:border-default-400 focus-visible:ring-2'
|
|
275
95
|
)}
|
|
276
|
-
onclick={
|
|
96
|
+
onclick={() => {
|
|
97
|
+
if (!disabled) open = !open;
|
|
98
|
+
}}
|
|
277
99
|
data-testid={buildTestId('date-range', 'trigger', testId)}
|
|
278
100
|
aria-haspopup="true"
|
|
279
|
-
aria-expanded={
|
|
280
|
-
aria-describedby={
|
|
101
|
+
aria-expanded={open}
|
|
102
|
+
aria-describedby={hasErrors ? `${id}-errors` : undefined}
|
|
281
103
|
{disabled}
|
|
282
104
|
>
|
|
283
|
-
<span class={
|
|
284
|
-
{
|
|
285
|
-
? `${formatDate(startDate)} - ${formatDate(endDate)}`
|
|
286
|
-
: startDate
|
|
287
|
-
? `${formatDate(startDate)} - Select end date`
|
|
288
|
-
: placeholder}
|
|
105
|
+
<span class={hasValue ? 'text-default-900' : 'text-default-500'}>
|
|
106
|
+
{triggerText}
|
|
289
107
|
</span>
|
|
290
108
|
<svg class={cn('text-default-400', tokens.iconSize)} viewBox="0 0 20 20" fill="currentColor">
|
|
291
109
|
<path
|
|
@@ -296,254 +114,61 @@
|
|
|
296
114
|
</svg>
|
|
297
115
|
</button>
|
|
298
116
|
|
|
299
|
-
{#
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
117
|
+
{#snippet content()}
|
|
118
|
+
<!-- Wrap Calendar in our own card so the labelled footer
|
|
119
|
+
reads as part of the same surface. Calendar's default
|
|
120
|
+
border/shadow are neutralized via class so we don't
|
|
121
|
+
get a card-in-card. -->
|
|
122
|
+
<div class="border-default-200 rounded-lg border bg-white shadow-xs">
|
|
123
|
+
<Calendar
|
|
124
|
+
mode="range"
|
|
125
|
+
valueStart={startDate ?? null}
|
|
126
|
+
valueEnd={endDate ?? null}
|
|
127
|
+
{minDate}
|
|
128
|
+
{maxDate}
|
|
129
|
+
{size}
|
|
130
|
+
class="rounded-none border-0 shadow-none"
|
|
131
|
+
onselect={handleSelect}
|
|
132
|
+
/>
|
|
133
|
+
{#if hasValue}
|
|
134
|
+
<div
|
|
135
|
+
class="border-default-200 text-default-500 flex flex-wrap justify-between gap-x-4 gap-y-1 border-t px-3 py-2 text-xs"
|
|
136
|
+
>
|
|
137
|
+
<div>{startDate ? `${startLabel}: ${formatDate(startDate)}` : ''}</div>
|
|
138
|
+
<div>{endDate ? `${endLabel}: ${formatDate(endDate)}` : ''}</div>
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
</div>
|
|
142
|
+
{/snippet}
|
|
143
|
+
</Popover>
|
|
322
144
|
|
|
323
|
-
{#if
|
|
145
|
+
{#if hasValue && !disabled}
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
class={cn(
|
|
149
|
+
'text-default-400 hover:text-default-500 absolute top-1/2 -translate-y-1/2',
|
|
150
|
+
// Sit just left of the calendar icon; the trigger has the icon at far right.
|
|
151
|
+
'right-8'
|
|
152
|
+
)}
|
|
153
|
+
onclick={clearDates}
|
|
154
|
+
data-testid={buildTestId('date-range', 'clear', testId)}
|
|
155
|
+
aria-label="Clear dates"
|
|
156
|
+
>
|
|
157
|
+
<svg class={cn(tokens.iconSize)} viewBox="0 0 20 20" fill="currentColor">
|
|
158
|
+
<path
|
|
159
|
+
fill-rule="evenodd"
|
|
160
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
161
|
+
clip-rule="evenodd"
|
|
162
|
+
/>
|
|
163
|
+
</svg>
|
|
164
|
+
</button>
|
|
165
|
+
{/if}
|
|
166
|
+
|
|
167
|
+
{#if hasErrors}
|
|
324
168
|
<div id="{id}-errors" data-testid={buildTestId('date-range', 'errors', testId)}>
|
|
325
169
|
{#each errors as error, i (i)}
|
|
326
170
|
<p class="text-danger-600 mt-1 text-sm">{error}</p>
|
|
327
171
|
{/each}
|
|
328
172
|
</div>
|
|
329
173
|
{/if}
|
|
330
|
-
|
|
331
|
-
{#if isOpen && !isMobile}
|
|
332
|
-
<Portal target={datePickerRef}>
|
|
333
|
-
<div
|
|
334
|
-
bind:this={calendarRef}
|
|
335
|
-
class="ring-opacity-5 ring-default-300 absolute z-10 mt-1 w-full origin-top-left rounded-md bg-white p-4 shadow-lg ring-1 focus:outline-none"
|
|
336
|
-
data-testid={buildTestId('date-range', 'panel', testId)}
|
|
337
|
-
transition:fly={{ y: -8, duration: 300, easing: quintOut }}
|
|
338
|
-
>
|
|
339
|
-
{@render calendarContent()}
|
|
340
|
-
</div>
|
|
341
|
-
</Portal>
|
|
342
|
-
{/if}
|
|
343
|
-
|
|
344
|
-
{#if isOpen && isMobile}
|
|
345
|
-
<Portal target={datePickerRef}>
|
|
346
|
-
<button
|
|
347
|
-
type="button"
|
|
348
|
-
class="fixed inset-0 z-[9998] cursor-pointer bg-black/40 backdrop-blur-sm"
|
|
349
|
-
aria-label="Close"
|
|
350
|
-
onclick={() => (isOpen = false)}
|
|
351
|
-
></button>
|
|
352
|
-
<div
|
|
353
|
-
class="fixed inset-x-0 bottom-0 z-[9999] flex max-h-[85vh] min-h-48 flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl"
|
|
354
|
-
transition:fly={{ y: 300, duration: 200, easing: quintOut }}
|
|
355
|
-
bind:this={calendarRef}
|
|
356
|
-
>
|
|
357
|
-
<div class="flex justify-center py-2">
|
|
358
|
-
<div class="bg-default-300 h-1 w-8 rounded-full"></div>
|
|
359
|
-
</div>
|
|
360
|
-
<div class="flex-1 cursor-pointer overflow-y-auto p-4">
|
|
361
|
-
{@render calendarContent()}
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
364
|
-
</Portal>
|
|
365
|
-
{/if}
|
|
366
174
|
</div>
|
|
367
|
-
|
|
368
|
-
{#snippet calendarContent()}
|
|
369
|
-
<div class="mb-2 flex items-center justify-between">
|
|
370
|
-
{#if viewMode === 'days'}
|
|
371
|
-
<button
|
|
372
|
-
type="button"
|
|
373
|
-
aria-label="Previous month"
|
|
374
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
375
|
-
onclick={prevMonth}
|
|
376
|
-
>
|
|
377
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
378
|
-
><path
|
|
379
|
-
fill-rule="evenodd"
|
|
380
|
-
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
|
381
|
-
clip-rule="evenodd"
|
|
382
|
-
/></svg
|
|
383
|
-
>
|
|
384
|
-
</button>
|
|
385
|
-
<button
|
|
386
|
-
type="button"
|
|
387
|
-
class="text-default-700 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md px-2 py-1 text-sm font-medium"
|
|
388
|
-
onclick={showMonths}
|
|
389
|
-
>
|
|
390
|
-
{getMonthName(viewDate.getMonth())}
|
|
391
|
-
{viewDate.getFullYear()}
|
|
392
|
-
</button>
|
|
393
|
-
<button
|
|
394
|
-
type="button"
|
|
395
|
-
aria-label="Next month"
|
|
396
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
397
|
-
onclick={nextMonth}
|
|
398
|
-
>
|
|
399
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
400
|
-
><path
|
|
401
|
-
fill-rule="evenodd"
|
|
402
|
-
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
403
|
-
clip-rule="evenodd"
|
|
404
|
-
/></svg
|
|
405
|
-
>
|
|
406
|
-
</button>
|
|
407
|
-
{:else if viewMode === 'months'}
|
|
408
|
-
<button
|
|
409
|
-
type="button"
|
|
410
|
-
aria-label="Previous year"
|
|
411
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
412
|
-
onclick={() => (viewDate = new Date(viewDate.getFullYear() - 1, viewDate.getMonth(), 1))}
|
|
413
|
-
>
|
|
414
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
415
|
-
><path
|
|
416
|
-
fill-rule="evenodd"
|
|
417
|
-
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
|
418
|
-
clip-rule="evenodd"
|
|
419
|
-
/></svg
|
|
420
|
-
>
|
|
421
|
-
</button>
|
|
422
|
-
<button
|
|
423
|
-
type="button"
|
|
424
|
-
class="text-default-700 inline-flex cursor-pointer items-center rounded-md px-2 py-1 text-sm font-medium"
|
|
425
|
-
onclick={showYears}>{viewDate.getFullYear()}</button
|
|
426
|
-
>
|
|
427
|
-
<button
|
|
428
|
-
type="button"
|
|
429
|
-
aria-label="Next year"
|
|
430
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
431
|
-
onclick={() => (viewDate = new Date(viewDate.getFullYear() + 1, viewDate.getMonth(), 1))}
|
|
432
|
-
>
|
|
433
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
434
|
-
><path
|
|
435
|
-
fill-rule="evenodd"
|
|
436
|
-
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
437
|
-
clip-rule="evenodd"
|
|
438
|
-
/></svg
|
|
439
|
-
>
|
|
440
|
-
</button>
|
|
441
|
-
{:else if viewMode === 'years'}
|
|
442
|
-
<button
|
|
443
|
-
type="button"
|
|
444
|
-
aria-label="Previous years"
|
|
445
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
446
|
-
onclick={() => (viewDate = new Date(viewDate.getFullYear() - 12, viewDate.getMonth(), 1))}
|
|
447
|
-
>
|
|
448
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
449
|
-
><path
|
|
450
|
-
fill-rule="evenodd"
|
|
451
|
-
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
|
452
|
-
clip-rule="evenodd"
|
|
453
|
-
/></svg
|
|
454
|
-
>
|
|
455
|
-
</button>
|
|
456
|
-
<button
|
|
457
|
-
type="button"
|
|
458
|
-
class="text-default-700 inline-flex cursor-pointer items-center rounded-md px-2 py-1 text-sm font-medium"
|
|
459
|
-
>{viewDate.getFullYear() - 6} - {viewDate.getFullYear() + 5}</button
|
|
460
|
-
>
|
|
461
|
-
<button
|
|
462
|
-
type="button"
|
|
463
|
-
aria-label="Next years"
|
|
464
|
-
class="text-default-500 hover:bg-default-100 inline-flex cursor-pointer items-center rounded-md p-1 text-sm"
|
|
465
|
-
onclick={() => (viewDate = new Date(viewDate.getFullYear() + 12, viewDate.getMonth(), 1))}
|
|
466
|
-
>
|
|
467
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
|
|
468
|
-
><path
|
|
469
|
-
fill-rule="evenodd"
|
|
470
|
-
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
471
|
-
clip-rule="evenodd"
|
|
472
|
-
/></svg
|
|
473
|
-
>
|
|
474
|
-
</button>
|
|
475
|
-
{/if}
|
|
476
|
-
</div>
|
|
477
|
-
|
|
478
|
-
{#if viewMode === 'days'}
|
|
479
|
-
<div class="text-default-500 mb-1 grid grid-cols-7 gap-1 text-center text-xs font-medium">
|
|
480
|
-
<div>Su</div>
|
|
481
|
-
<div>Mo</div>
|
|
482
|
-
<div>Tu</div>
|
|
483
|
-
<div>We</div>
|
|
484
|
-
<div>Th</div>
|
|
485
|
-
<div>Fr</div>
|
|
486
|
-
<div>Sa</div>
|
|
487
|
-
</div>
|
|
488
|
-
<div class="grid grid-cols-7 gap-1">
|
|
489
|
-
{#each getDaysInMonth(viewDate) as { date, isCurrentMonth, isToday, isSelected, isInRange, isDisabled } (date.getTime())}
|
|
490
|
-
<button
|
|
491
|
-
type="button"
|
|
492
|
-
class={cn(
|
|
493
|
-
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-full text-sm font-medium',
|
|
494
|
-
isDisabled ? 'text-default-300 cursor-not-allowed' : 'hover:bg-default-100',
|
|
495
|
-
isSelected ? 'bg-primary-500 hover:bg-primary-600 text-white' : '',
|
|
496
|
-
isInRange && !isSelected ? 'bg-primary-100 text-primary-800' : '',
|
|
497
|
-
!isCurrentMonth && !isSelected && !isInRange ? 'text-default-400' : '',
|
|
498
|
-
isToday && !isSelected ? 'border-primary-500 border' : ''
|
|
499
|
-
)}
|
|
500
|
-
onclick={() => handleDateClick(date)}
|
|
501
|
-
onmouseenter={() => handleDateHover(date)}
|
|
502
|
-
disabled={isDisabled}>{date.getDate()}</button
|
|
503
|
-
>
|
|
504
|
-
{/each}
|
|
505
|
-
</div>
|
|
506
|
-
{:else if viewMode === 'months'}
|
|
507
|
-
<div class="grid grid-cols-3 gap-2">
|
|
508
|
-
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
|
|
509
|
-
{#each Array(12).fill(0) as _, month (month)}
|
|
510
|
-
<button
|
|
511
|
-
type="button"
|
|
512
|
-
class={cn(
|
|
513
|
-
'flex cursor-pointer items-center justify-center rounded-md px-2 py-1 text-sm font-medium',
|
|
514
|
-
viewDate.getMonth() === month
|
|
515
|
-
? 'bg-primary-500 text-white'
|
|
516
|
-
: 'text-default-700 hover:bg-default-100'
|
|
517
|
-
)}
|
|
518
|
-
onclick={() => selectMonth(month)}>{getMonthName(month).substring(0, 3)}</button
|
|
519
|
-
>
|
|
520
|
-
{/each}
|
|
521
|
-
</div>
|
|
522
|
-
{:else if viewMode === 'years'}
|
|
523
|
-
<div class="grid grid-cols-3 gap-2">
|
|
524
|
-
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
|
|
525
|
-
{#each Array(12).fill(0) as _, i (i)}
|
|
526
|
-
{@const year = viewDate.getFullYear() - 6 + i}
|
|
527
|
-
<button
|
|
528
|
-
type="button"
|
|
529
|
-
class={cn(
|
|
530
|
-
'flex cursor-pointer items-center justify-center rounded-md px-2 py-1 text-sm font-medium',
|
|
531
|
-
viewDate.getFullYear() === year
|
|
532
|
-
? 'bg-primary-500 text-white'
|
|
533
|
-
: 'text-default-700 hover:bg-default-100'
|
|
534
|
-
)}
|
|
535
|
-
onclick={() => selectYear(year)}>{year}</button
|
|
536
|
-
>
|
|
537
|
-
{/each}
|
|
538
|
-
</div>
|
|
539
|
-
{/if}
|
|
540
|
-
|
|
541
|
-
{#if startDate || endDate}
|
|
542
|
-
<div
|
|
543
|
-
class="border-default-200 text-default-500 mt-4 flex flex-wrap justify-between gap-x-4 gap-y-1 border-t pt-3 text-xs"
|
|
544
|
-
>
|
|
545
|
-
<div>{startDate ? `${startLabel}: ${formatDate(startDate)}` : ''}</div>
|
|
546
|
-
<div>{endDate ? `${endLabel}: ${formatDate(endDate)}` : ''}</div>
|
|
547
|
-
</div>
|
|
548
|
-
{/if}
|
|
549
|
-
{/snippet}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
class: className = '',
|
|
24
24
|
oninput,
|
|
25
25
|
onblur,
|
|
26
|
+
onkeydown,
|
|
26
27
|
testId
|
|
27
28
|
}: TextareaProps = $props();
|
|
28
29
|
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
{value}
|
|
104
105
|
oninput={handleInput}
|
|
105
106
|
onblur={handleBlur}
|
|
107
|
+
{onkeydown}
|
|
106
108
|
></textarea>
|
|
107
109
|
|
|
108
110
|
{#if showCount && maxLength !== undefined}
|