@makolabs/ripple 0.0.1-dev.7 → 0.0.1-dev.8
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/README.md +393 -53
- package/dist/button/Button.svelte +5 -3
- package/dist/button/Button.svelte.d.ts +1 -1
- package/dist/button/button.d.ts +40 -63
- package/dist/button/button.js +15 -14
- package/dist/charts/Chart.svelte +533 -0
- package/dist/charts/Chart.svelte.d.ts +4 -0
- package/dist/drawer/Drawer.svelte +13 -2
- package/dist/drawer/Drawer.svelte.d.ts +1 -1
- package/dist/drawer/drawer.d.ts +0 -17
- package/dist/elements/alert/Alert.svelte +53 -0
- package/dist/elements/alert/Alert.svelte.d.ts +4 -0
- package/dist/elements/badge/Badge.svelte +13 -5
- package/dist/elements/badge/Badge.svelte.d.ts +1 -1
- package/dist/elements/badge/badge.d.ts +0 -12
- package/dist/elements/dropdown/Dropdown.svelte +32 -37
- package/dist/elements/dropdown/Dropdown.svelte.d.ts +1 -1
- package/dist/elements/dropdown/Select.svelte +143 -59
- package/dist/elements/dropdown/Select.svelte.d.ts +1 -1
- package/dist/elements/dropdown/dropdown.d.ts +34 -57
- package/dist/elements/dropdown/dropdown.js +10 -4
- package/dist/elements/dropdown/select.d.ts +34 -54
- package/dist/elements/dropdown/select.js +22 -14
- package/dist/elements/file-upload/FileUpload.svelte +213 -0
- package/dist/elements/file-upload/FileUpload.svelte.d.ts +4 -0
- package/dist/elements/progress/Progress.svelte +87 -0
- package/dist/elements/progress/Progress.svelte.d.ts +4 -0
- package/dist/elements/timeline/Timeline.svelte +92 -0
- package/dist/elements/timeline/Timeline.svelte.d.ts +7 -0
- package/dist/forms/Checkbox.svelte +54 -0
- package/dist/forms/Checkbox.svelte.d.ts +4 -0
- package/dist/forms/DateRange.svelte +493 -0
- package/dist/forms/DateRange.svelte.d.ts +4 -0
- package/dist/forms/Form.svelte +39 -0
- package/dist/forms/Form.svelte.d.ts +4 -0
- package/dist/forms/Input.svelte +86 -0
- package/dist/forms/Input.svelte.d.ts +4 -0
- package/dist/forms/NumberInput.svelte +159 -0
- package/dist/forms/NumberInput.svelte.d.ts +4 -0
- package/dist/forms/RadioInputs.svelte +64 -0
- package/dist/forms/RadioInputs.svelte.d.ts +4 -0
- package/dist/forms/RadioPill.svelte +66 -0
- package/dist/forms/RadioPill.svelte.d.ts +4 -0
- package/dist/forms/Slider.svelte +342 -0
- package/dist/forms/Slider.svelte.d.ts +4 -0
- package/dist/forms/Tags.svelte +181 -0
- package/dist/forms/Tags.svelte.d.ts +4 -0
- package/dist/forms/Toggle.svelte +132 -0
- package/dist/forms/Toggle.svelte.d.ts +4 -0
- package/dist/forms/slider.d.ts +143 -0
- package/dist/forms/slider.js +62 -0
- package/dist/header/Breadcrumbs.svelte +2 -1
- package/dist/header/Breadcrumbs.svelte.d.ts +1 -1
- package/dist/header/PageHeader.svelte +2 -2
- package/dist/header/PageHeader.svelte.d.ts +1 -1
- package/dist/header/breadcrumbs.d.ts +20 -14
- package/dist/header/breadcrumbs.js +6 -0
- package/dist/header/pageheaders.d.ts +1 -1
- package/dist/helper/date.d.ts +7 -0
- package/dist/helper/date.js +15 -0
- package/dist/index.d.ts +742 -9
- package/dist/index.js +59 -16
- package/dist/layout/card/Card.svelte +5 -8
- package/dist/layout/card/Card.svelte.d.ts +1 -1
- package/dist/layout/card/StatsCard.svelte +116 -87
- package/dist/layout/card/card.d.ts +22 -33
- package/dist/layout/card/card.js +9 -8
- package/dist/layout/card/stats-card.d.ts +23 -25
- package/dist/layout/card/stats-card.js +13 -13
- package/dist/layout/navbar/navbar.d.ts +0 -23
- package/dist/layout/sidebar/NavGroup.svelte +20 -44
- package/dist/layout/sidebar/NavGroup.svelte.d.ts +1 -1
- package/dist/layout/sidebar/NavItem.svelte +1 -1
- package/dist/layout/sidebar/NavItem.svelte.d.ts +1 -1
- package/dist/layout/sidebar/Sidebar.svelte +19 -25
- package/dist/layout/sidebar/Sidebar.svelte.d.ts +1 -1
- package/dist/layout/table/table.d.ts +1 -1
- package/dist/layout/tabs/tabs.d.ts +1 -1
- package/dist/modal/Modal.svelte +2 -1
- package/dist/modal/Modal.svelte.d.ts +1 -1
- package/dist/modal/modal.d.ts +0 -23
- package/dist/sonner/sonner.svelte +13 -0
- package/dist/sonner/sonner.svelte.d.ts +4 -0
- package/dist/types/variants.d.ts +1 -21
- package/dist/types/variants.js +1 -19
- package/dist/variants.d.ts +20 -0
- package/dist/variants.js +19 -0
- package/package.json +6 -2
- package/dist/button/index.d.ts +0 -1
- package/dist/button/index.js +0 -1
- package/dist/drawer/index.d.ts +0 -2
- package/dist/drawer/index.js +0 -1
- package/dist/elements/badge/index.d.ts +0 -2
- package/dist/elements/badge/index.js +0 -2
- package/dist/elements/dropdown/index.d.ts +0 -3
- package/dist/elements/dropdown/index.js +0 -2
- package/dist/header/index.d.ts +0 -4
- package/dist/header/index.js +0 -2
- package/dist/layout/card/index.d.ts +0 -4
- package/dist/layout/card/index.js +0 -2
- package/dist/layout/index.d.ts +0 -5
- package/dist/layout/index.js +0 -5
- package/dist/layout/navbar/index.d.ts +0 -2
- package/dist/layout/navbar/index.js +0 -2
- package/dist/layout/sidebar/index.d.ts +0 -2
- package/dist/layout/sidebar/index.js +0 -1
- package/dist/layout/sidebar/sidebar.d.ts +0 -46
- package/dist/layout/sidebar/sidebar.js +0 -1
- package/dist/layout/table/index.d.ts +0 -3
- package/dist/layout/table/index.js +0 -2
- package/dist/layout/tabs/index.d.ts +0 -3
- package/dist/layout/tabs/index.js +0 -3
- package/dist/modal/index.d.ts +0 -1
- package/dist/modal/index.js +0 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../helper/cls.js';
|
|
3
|
+
import type { DateRangeProps } from '../index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
startDate = $bindable(),
|
|
7
|
+
endDate = $bindable(),
|
|
8
|
+
minDate = new Date(new Date().getFullYear() - 5, 0, 1),
|
|
9
|
+
maxDate = new Date(new Date().getFullYear() + 5, 11, 31),
|
|
10
|
+
disabled = false,
|
|
11
|
+
class: className = '',
|
|
12
|
+
placeholder = 'Select date range',
|
|
13
|
+
startLabel = 'Start date',
|
|
14
|
+
endLabel = 'End date',
|
|
15
|
+
format = 'MM/dd/yyyy',
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
onselect
|
|
19
|
+
}: DateRangeProps = $props();
|
|
20
|
+
|
|
21
|
+
let isOpen = $state(false);
|
|
22
|
+
let hoveredDate = $state<Date | null>(null);
|
|
23
|
+
let datePickerRef = $state<HTMLDivElement | null>(null);
|
|
24
|
+
|
|
25
|
+
let viewMode = $state<'days' | 'months' | 'years'>('days');
|
|
26
|
+
let viewDate = $state(new Date());
|
|
27
|
+
|
|
28
|
+
const handleOutsideClick = (event: MouseEvent) => {
|
|
29
|
+
if (isOpen && datePickerRef && !datePickerRef.contains(event.target as Node)) {
|
|
30
|
+
isOpen = false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function formatDate(date: Date | null): string {
|
|
35
|
+
if (!date) return '';
|
|
36
|
+
const year = date.getFullYear();
|
|
37
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
38
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
39
|
+
return format.replace('yyyy', String(year)).replace('MM', month).replace('dd', day);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseDate(dateStr: string): Date | null {
|
|
43
|
+
if (!dateStr) return null;
|
|
44
|
+
|
|
45
|
+
const parts = dateStr.split('/');
|
|
46
|
+
if (parts.length !== 3) return null;
|
|
47
|
+
|
|
48
|
+
const month = parseInt(parts[0], 10) - 1;
|
|
49
|
+
const day = parseInt(parts[1], 10);
|
|
50
|
+
const year = parseInt(parts[2], 10);
|
|
51
|
+
|
|
52
|
+
if (isNaN(month) || isNaN(day) || isNaN(year)) return null;
|
|
53
|
+
|
|
54
|
+
return new Date(year, month, day);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getDaysInMonth(month: Date): {
|
|
58
|
+
date: Date;
|
|
59
|
+
isCurrentMonth: boolean;
|
|
60
|
+
isToday: boolean;
|
|
61
|
+
isSelected: boolean;
|
|
62
|
+
isInRange: boolean;
|
|
63
|
+
isDisabled: boolean;
|
|
64
|
+
}[] {
|
|
65
|
+
const year = month.getFullYear();
|
|
66
|
+
const monthIndex = month.getMonth();
|
|
67
|
+
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
|
|
68
|
+
|
|
69
|
+
const firstDay = new Date(year, monthIndex, 1);
|
|
70
|
+
|
|
71
|
+
const firstDayOfWeek = firstDay.getDay();
|
|
72
|
+
|
|
73
|
+
const daysFromPrevMonth = firstDayOfWeek;
|
|
74
|
+
|
|
75
|
+
const prevMonth = new Date(year, monthIndex, 0);
|
|
76
|
+
const prevMonthDays = prevMonth.getDate();
|
|
77
|
+
|
|
78
|
+
const daysInCalendar = 42; // 6 rows of 7 days
|
|
79
|
+
const daysFromNextMonth = daysInCalendar - daysInMonth - daysFromPrevMonth;
|
|
80
|
+
|
|
81
|
+
const today = new Date();
|
|
82
|
+
today.setHours(0, 0, 0, 0);
|
|
83
|
+
|
|
84
|
+
const days = [];
|
|
85
|
+
|
|
86
|
+
for (let i = daysFromPrevMonth - 1; i >= 0; i--) {
|
|
87
|
+
const date = new Date(year, monthIndex - 1, prevMonthDays - i);
|
|
88
|
+
days.push({
|
|
89
|
+
date,
|
|
90
|
+
isCurrentMonth: false,
|
|
91
|
+
isToday: date.getTime() === today.getTime(),
|
|
92
|
+
isSelected: isDateSelected(date),
|
|
93
|
+
isInRange: isDateInRange(date),
|
|
94
|
+
isDisabled: isDateDisabled(date)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
99
|
+
const date = new Date(year, monthIndex, i);
|
|
100
|
+
days.push({
|
|
101
|
+
date,
|
|
102
|
+
isCurrentMonth: true,
|
|
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 <= daysFromNextMonth; i++) {
|
|
111
|
+
const date = new Date(year, monthIndex + 1, i);
|
|
112
|
+
days.push({
|
|
113
|
+
date,
|
|
114
|
+
isCurrentMonth: false,
|
|
115
|
+
isToday: date.getTime() === today.getTime(),
|
|
116
|
+
isSelected: isDateSelected(date),
|
|
117
|
+
isInRange: isDateInRange(date),
|
|
118
|
+
isDisabled: isDateDisabled(date)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return days;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isDateSelected(date: Date): boolean {
|
|
126
|
+
return Boolean(
|
|
127
|
+
(startDate && isSameDate(date, startDate)) || (endDate && isSameDate(date, endDate))
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isDateInRange(date: Date): boolean {
|
|
132
|
+
if (!startDate || !endDate) {
|
|
133
|
+
if (startDate && hoveredDate) {
|
|
134
|
+
const hoverStart = startDate < hoveredDate ? startDate : hoveredDate;
|
|
135
|
+
const hoverEnd = startDate < hoveredDate ? hoveredDate : startDate;
|
|
136
|
+
return date >= hoverStart && date <= hoverEnd;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return date >= startDate && date <= endDate;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isDateDisabled(date: Date): boolean {
|
|
145
|
+
return date < minDate || date > maxDate || disabled;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isSameDate(date1: Date, date2: Date): boolean {
|
|
149
|
+
return (
|
|
150
|
+
date1.getDate() === date2.getDate() &&
|
|
151
|
+
date1.getMonth() === date2.getMonth() &&
|
|
152
|
+
date1.getFullYear() === date2.getFullYear()
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleDateClick(date: Date): void {
|
|
157
|
+
if (isDateDisabled(date)) return;
|
|
158
|
+
|
|
159
|
+
if (!startDate || (startDate && endDate)) {
|
|
160
|
+
startDate = date;
|
|
161
|
+
endDate = undefined;
|
|
162
|
+
} else if (startDate && !endDate) {
|
|
163
|
+
if (date < startDate) {
|
|
164
|
+
endDate = startDate;
|
|
165
|
+
startDate = date;
|
|
166
|
+
} else {
|
|
167
|
+
endDate = date;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onselect?.({ startDate, endDate });
|
|
172
|
+
|
|
173
|
+
if (startDate && endDate) {
|
|
174
|
+
isOpen = false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleDateHover(date: Date): void {
|
|
179
|
+
hoveredDate = date;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function nextMonth(): void {
|
|
183
|
+
viewDate = new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function prevMonth(): void {
|
|
187
|
+
viewDate = new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function showMonths(): void {
|
|
191
|
+
viewMode = 'months';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function showYears(): void {
|
|
195
|
+
viewMode = 'years';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function selectMonth(month: number): void {
|
|
199
|
+
viewDate = new Date(viewDate.getFullYear(), month, 1);
|
|
200
|
+
viewMode = 'days';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function selectYear(year: number): void {
|
|
204
|
+
viewDate = new Date(year, viewDate.getMonth(), 1);
|
|
205
|
+
viewMode = 'months';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getMonthName(monthIndex: number): string {
|
|
209
|
+
return new Date(2000, monthIndex, 1).toLocaleString('default', { month: 'long' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function toggleDatepicker(): void {
|
|
213
|
+
if (disabled) return;
|
|
214
|
+
isOpen = !isOpen;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function clearDates(event: Event): void {
|
|
218
|
+
event.stopPropagation();
|
|
219
|
+
startDate = undefined;
|
|
220
|
+
endDate = undefined;
|
|
221
|
+
onselect?.({ startDate, endDate });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$effect(() => {
|
|
225
|
+
if (startDate && endDate) {
|
|
226
|
+
viewDate = new Date(startDate);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
function getValue(): string {
|
|
231
|
+
return `${startDate ? formatDate(startDate) : ''}:${endDate ? formatDate(endDate) : ''}`;
|
|
232
|
+
}
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
<svelte:window onmousedown={handleOutsideClick} />
|
|
236
|
+
|
|
237
|
+
<input type="hidden" name={`${name}[start]`} value={startDate?.toISOString()} />
|
|
238
|
+
<input type="hidden" name={`${name}[end]`} value={endDate?.toISOString()} />
|
|
239
|
+
<input type="hidden" name={`${name}[format]`} value={format} />
|
|
240
|
+
<input type="hidden" name={`${name}`} value={getValue()} />
|
|
241
|
+
|
|
242
|
+
<div class={cn('relative block w-full', className)} bind:this={datePickerRef}>
|
|
243
|
+
<div class="relative">
|
|
244
|
+
<button
|
|
245
|
+
{id}
|
|
246
|
+
type="button"
|
|
247
|
+
class={cn(
|
|
248
|
+
'flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm',
|
|
249
|
+
disabled
|
|
250
|
+
? 'cursor-not-allowed bg-gray-100 text-gray-400'
|
|
251
|
+
: 'focus:border-primary-500 focus:ring-primary-500 hover:border-gray-400 focus:ring-2'
|
|
252
|
+
)}
|
|
253
|
+
onclick={toggleDatepicker}
|
|
254
|
+
aria-haspopup="true"
|
|
255
|
+
aria-expanded={isOpen}
|
|
256
|
+
{disabled}
|
|
257
|
+
>
|
|
258
|
+
<span class={startDate && endDate ? 'text-gray-900' : 'text-gray-500'}>
|
|
259
|
+
{startDate && endDate
|
|
260
|
+
? `${formatDate(startDate)} - ${formatDate(endDate)}`
|
|
261
|
+
: startDate
|
|
262
|
+
? `${formatDate(startDate)} - Select end date`
|
|
263
|
+
: placeholder}
|
|
264
|
+
</span>
|
|
265
|
+
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
|
|
266
|
+
<path
|
|
267
|
+
fill-rule="evenodd"
|
|
268
|
+
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
|
|
269
|
+
clip-rule="evenodd"
|
|
270
|
+
/>
|
|
271
|
+
</svg>
|
|
272
|
+
</button>
|
|
273
|
+
|
|
274
|
+
{#if startDate || endDate}
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
class="absolute top-1/2 right-10 -translate-y-1/2 text-gray-400 hover:text-gray-500"
|
|
278
|
+
onclick={clearDates}
|
|
279
|
+
aria-label="Clear dates"
|
|
280
|
+
>
|
|
281
|
+
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
282
|
+
<path
|
|
283
|
+
fill-rule="evenodd"
|
|
284
|
+
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"
|
|
285
|
+
clip-rule="evenodd"
|
|
286
|
+
/>
|
|
287
|
+
</svg>
|
|
288
|
+
</button>
|
|
289
|
+
{/if}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{#if isOpen}
|
|
293
|
+
<div
|
|
294
|
+
class="ring-opacity-5 absolute z-10 mt-1 w-full origin-top-left rounded-md bg-white p-4 shadow-lg ring-1 ring-black focus:outline-none"
|
|
295
|
+
>
|
|
296
|
+
<div class="mb-2 flex items-center justify-between">
|
|
297
|
+
{#if viewMode === 'days'}
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
aria-label="Previous month"
|
|
301
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
302
|
+
onclick={prevMonth}
|
|
303
|
+
>
|
|
304
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
305
|
+
<path
|
|
306
|
+
fill-rule="evenodd"
|
|
307
|
+
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"
|
|
308
|
+
clip-rule="evenodd"
|
|
309
|
+
/>
|
|
310
|
+
</svg>
|
|
311
|
+
</button>
|
|
312
|
+
<button
|
|
313
|
+
type="button"
|
|
314
|
+
class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-700 hover:bg-gray-100"
|
|
315
|
+
onclick={showMonths}
|
|
316
|
+
>
|
|
317
|
+
{getMonthName(viewDate.getMonth())}
|
|
318
|
+
{viewDate.getFullYear()}
|
|
319
|
+
</button>
|
|
320
|
+
<button
|
|
321
|
+
type="button"
|
|
322
|
+
aria-label="Next month"
|
|
323
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
324
|
+
onclick={nextMonth}
|
|
325
|
+
>
|
|
326
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
327
|
+
<path
|
|
328
|
+
fill-rule="evenodd"
|
|
329
|
+
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"
|
|
330
|
+
clip-rule="evenodd"
|
|
331
|
+
/>
|
|
332
|
+
</svg>
|
|
333
|
+
</button>
|
|
334
|
+
{:else if viewMode === 'months'}
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
aria-label="Previous year"
|
|
338
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
339
|
+
onclick={() =>
|
|
340
|
+
(viewDate = new Date(viewDate.getFullYear() - 1, viewDate.getMonth(), 1))}
|
|
341
|
+
>
|
|
342
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
343
|
+
<path
|
|
344
|
+
fill-rule="evenodd"
|
|
345
|
+
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"
|
|
346
|
+
clip-rule="evenodd"
|
|
347
|
+
/>
|
|
348
|
+
</svg>
|
|
349
|
+
</button>
|
|
350
|
+
<button
|
|
351
|
+
type="button"
|
|
352
|
+
aria-label="Current year"
|
|
353
|
+
class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-700"
|
|
354
|
+
onclick={showYears}
|
|
355
|
+
>
|
|
356
|
+
{viewDate.getFullYear()}
|
|
357
|
+
</button>
|
|
358
|
+
<button
|
|
359
|
+
type="button"
|
|
360
|
+
aria-label="Next year"
|
|
361
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
362
|
+
onclick={() =>
|
|
363
|
+
(viewDate = new Date(viewDate.getFullYear() + 1, viewDate.getMonth(), 1))}
|
|
364
|
+
>
|
|
365
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
366
|
+
<path
|
|
367
|
+
fill-rule="evenodd"
|
|
368
|
+
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"
|
|
369
|
+
clip-rule="evenodd"
|
|
370
|
+
/>
|
|
371
|
+
</svg>
|
|
372
|
+
</button>
|
|
373
|
+
{:else if viewMode === 'years'}
|
|
374
|
+
<button
|
|
375
|
+
type="button"
|
|
376
|
+
aria-label="Previous year"
|
|
377
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
378
|
+
onclick={() =>
|
|
379
|
+
(viewDate = new Date(viewDate.getFullYear() - 12, viewDate.getMonth(), 1))}
|
|
380
|
+
>
|
|
381
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
382
|
+
<path
|
|
383
|
+
fill-rule="evenodd"
|
|
384
|
+
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"
|
|
385
|
+
clip-rule="evenodd"
|
|
386
|
+
/>
|
|
387
|
+
</svg>
|
|
388
|
+
</button>
|
|
389
|
+
<button
|
|
390
|
+
type="button"
|
|
391
|
+
aria-label="Current year range"
|
|
392
|
+
class="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-700"
|
|
393
|
+
>
|
|
394
|
+
{viewDate.getFullYear() - 6} - {viewDate.getFullYear() + 5}
|
|
395
|
+
</button>
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
aria-label="Next year"
|
|
399
|
+
class="inline-flex items-center rounded-md p-1 text-sm text-gray-500 hover:bg-gray-100"
|
|
400
|
+
onclick={() =>
|
|
401
|
+
(viewDate = new Date(viewDate.getFullYear() + 12, viewDate.getMonth(), 1))}
|
|
402
|
+
>
|
|
403
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
404
|
+
<path
|
|
405
|
+
fill-rule="evenodd"
|
|
406
|
+
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"
|
|
407
|
+
clip-rule="evenodd"
|
|
408
|
+
/>
|
|
409
|
+
</svg>
|
|
410
|
+
</button>
|
|
411
|
+
{/if}
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
{#if viewMode === 'days'}
|
|
415
|
+
<div class="mb-1 grid grid-cols-7 gap-1 text-center text-xs font-medium text-gray-500">
|
|
416
|
+
<div>Su</div>
|
|
417
|
+
<div>Mo</div>
|
|
418
|
+
<div>Tu</div>
|
|
419
|
+
<div>We</div>
|
|
420
|
+
<div>Th</div>
|
|
421
|
+
<div>Fr</div>
|
|
422
|
+
<div>Sa</div>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="grid grid-cols-7 gap-1">
|
|
425
|
+
{#each getDaysInMonth(viewDate) as { date, isCurrentMonth, isToday, isSelected, isInRange, isDisabled }}
|
|
426
|
+
<button
|
|
427
|
+
type="button"
|
|
428
|
+
class={cn(
|
|
429
|
+
'flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium',
|
|
430
|
+
isDisabled ? 'cursor-not-allowed text-gray-300' : 'hover:bg-gray-100',
|
|
431
|
+
isSelected ? 'bg-primary-500 hover:bg-primary-600 text-white' : '',
|
|
432
|
+
isInRange && !isSelected ? 'bg-primary-100 text-primary-800' : '',
|
|
433
|
+
!isCurrentMonth && !isSelected && !isInRange ? 'text-gray-400' : '',
|
|
434
|
+
isToday && !isSelected ? 'border-primary-500 border' : ''
|
|
435
|
+
)}
|
|
436
|
+
onclick={() => handleDateClick(date)}
|
|
437
|
+
onmouseenter={() => handleDateHover(date)}
|
|
438
|
+
disabled={isDisabled}
|
|
439
|
+
>
|
|
440
|
+
{date.getDate()}
|
|
441
|
+
</button>
|
|
442
|
+
{/each}
|
|
443
|
+
</div>
|
|
444
|
+
{:else if viewMode === 'months'}
|
|
445
|
+
<div class="grid grid-cols-3 gap-2">
|
|
446
|
+
{#each Array(12).fill(0) as _, month}
|
|
447
|
+
<button
|
|
448
|
+
type="button"
|
|
449
|
+
class={cn(
|
|
450
|
+
'flex items-center justify-center rounded-md px-2 py-1 text-sm font-medium',
|
|
451
|
+
viewDate.getMonth() === month
|
|
452
|
+
? 'bg-primary-500 text-white'
|
|
453
|
+
: 'text-gray-700 hover:bg-gray-100'
|
|
454
|
+
)}
|
|
455
|
+
onclick={() => selectMonth(month)}
|
|
456
|
+
>
|
|
457
|
+
{getMonthName(month).substring(0, 3)}
|
|
458
|
+
</button>
|
|
459
|
+
{/each}
|
|
460
|
+
</div>
|
|
461
|
+
{:else if viewMode === 'years'}
|
|
462
|
+
<div class="grid grid-cols-3 gap-2">
|
|
463
|
+
{#each Array(12).fill(0) as _, i}
|
|
464
|
+
{@const year = viewDate.getFullYear() - 6 + i}
|
|
465
|
+
<button
|
|
466
|
+
type="button"
|
|
467
|
+
class={cn(
|
|
468
|
+
'flex items-center justify-center rounded-md px-2 py-1 text-sm font-medium',
|
|
469
|
+
viewDate.getFullYear() === year
|
|
470
|
+
? 'bg-primary-500 text-white'
|
|
471
|
+
: 'text-gray-700 hover:bg-gray-100'
|
|
472
|
+
)}
|
|
473
|
+
onclick={() => selectYear(year)}
|
|
474
|
+
>
|
|
475
|
+
{year}
|
|
476
|
+
</button>
|
|
477
|
+
{/each}
|
|
478
|
+
</div>
|
|
479
|
+
{/if}
|
|
480
|
+
|
|
481
|
+
{#if startDate || endDate}
|
|
482
|
+
<div class="mt-4 flex justify-between border-t border-gray-200 pt-3 text-xs text-gray-500">
|
|
483
|
+
<div>
|
|
484
|
+
{startDate ? `${startLabel}: ${formatDate(startDate)}` : ''}
|
|
485
|
+
</div>
|
|
486
|
+
<div>
|
|
487
|
+
{endDate ? `${endLabel}: ${formatDate(endDate)}` : ''}
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
{/if}
|
|
491
|
+
</div>
|
|
492
|
+
{/if}
|
|
493
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext } from 'svelte';
|
|
3
|
+
import { cn } from '../helper/cls.js';
|
|
4
|
+
import type { FormProps } from '../index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
form,
|
|
8
|
+
class: className = '',
|
|
9
|
+
method = 'POST',
|
|
10
|
+
action,
|
|
11
|
+
enctype,
|
|
12
|
+
autocomplete,
|
|
13
|
+
novalidate = false,
|
|
14
|
+
children,
|
|
15
|
+
...restProps
|
|
16
|
+
}: FormProps<any> = $props();
|
|
17
|
+
|
|
18
|
+
const { enhance, delayed } = form;
|
|
19
|
+
setContext('form', form);
|
|
20
|
+
|
|
21
|
+
const formClasses = $derived(cn('space-y-4', className));
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<form
|
|
25
|
+
{method}
|
|
26
|
+
{action}
|
|
27
|
+
{enctype}
|
|
28
|
+
{autocomplete}
|
|
29
|
+
{novalidate}
|
|
30
|
+
class={formClasses}
|
|
31
|
+
use:enhance
|
|
32
|
+
{...restProps}
|
|
33
|
+
>
|
|
34
|
+
{@render children?.()}
|
|
35
|
+
</form>
|
|
36
|
+
|
|
37
|
+
{#if $delayed}
|
|
38
|
+
<div class="fixed inset-0 bg-black/20 backdrop-blur-sm"></div>
|
|
39
|
+
{/if}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { Size } from '../variants.js';
|
|
4
|
+
import type { InputProps } from '../index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
name,
|
|
8
|
+
id = name,
|
|
9
|
+
type = 'text',
|
|
10
|
+
label,
|
|
11
|
+
placeholder,
|
|
12
|
+
disabled = false,
|
|
13
|
+
class: className = '',
|
|
14
|
+
size = Size.BASE,
|
|
15
|
+
value = $bindable(),
|
|
16
|
+
errors = [],
|
|
17
|
+
...restProps
|
|
18
|
+
}: InputProps = $props();
|
|
19
|
+
|
|
20
|
+
const BASIC_TYPES = ['text', 'email', 'password', 'number', 'tel', 'url', 'date', 'textarea'];
|
|
21
|
+
const inputClasses = $derived(
|
|
22
|
+
cn(
|
|
23
|
+
'transition-colors',
|
|
24
|
+
{
|
|
25
|
+
'border rounded-lg shadow-sm w-full bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2':
|
|
26
|
+
BASIC_TYPES.includes(type),
|
|
27
|
+
'w-full bg-white px-3 py-2 text-sm resize-y min-h-[100px]': type === 'textarea',
|
|
28
|
+
'border-red-300 focus:border-red-500 focus:ring-red-500': errors.length,
|
|
29
|
+
'opacity-50 cursor-not-allowed': disabled,
|
|
30
|
+
...(BASIC_TYPES.includes(type)
|
|
31
|
+
? {
|
|
32
|
+
'h-8 text-sm': size === Size.SM,
|
|
33
|
+
'h-10 text-base': size === Size.BASE,
|
|
34
|
+
'h-12 text-lg': size === Size.LG
|
|
35
|
+
}
|
|
36
|
+
: {}),
|
|
37
|
+
'border-gray-300 focus:border-primary-500 focus:ring-primary-500': !errors.length
|
|
38
|
+
},
|
|
39
|
+
className
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const inputCommonProps = $derived({
|
|
44
|
+
id,
|
|
45
|
+
name,
|
|
46
|
+
type,
|
|
47
|
+
placeholder,
|
|
48
|
+
disabled,
|
|
49
|
+
class: inputClasses,
|
|
50
|
+
'aria-invalid': !!errors.length,
|
|
51
|
+
'aria-describedby': errors.length ? `${name}-errors` : undefined,
|
|
52
|
+
...restProps
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<div class="w-full">
|
|
57
|
+
{@render Label('mb-2')}
|
|
58
|
+
{#if type === 'textarea'}
|
|
59
|
+
<textarea {...inputCommonProps} bind:value></textarea>
|
|
60
|
+
{:else}
|
|
61
|
+
<input {...inputCommonProps} bind:value />
|
|
62
|
+
{/if}
|
|
63
|
+
{#if errors.length}
|
|
64
|
+
{#each errors as error (error)}
|
|
65
|
+
<p id="{name}-errors" class="mt-1 text-sm text-red-600" role="alert">
|
|
66
|
+
{error}
|
|
67
|
+
</p>
|
|
68
|
+
{/each}
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{#snippet Label(extraClass = '')}
|
|
73
|
+
{#if label}
|
|
74
|
+
<label
|
|
75
|
+
for={id}
|
|
76
|
+
class={cn(
|
|
77
|
+
'block w-full text-sm font-medium',
|
|
78
|
+
{
|
|
79
|
+
'text-gray-700': !errors.length,
|
|
80
|
+
'text-red-600': errors.length
|
|
81
|
+
},
|
|
82
|
+
extraClass
|
|
83
|
+
)}>{label}</label
|
|
84
|
+
>
|
|
85
|
+
{/if}
|
|
86
|
+
{/snippet}
|