@noahbaumgartner/svelte-ui 0.0.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/README.md +65 -0
- package/dist/components/alert-dialog/AlertDialog.svelte +161 -0
- package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +20 -0
- package/dist/components/avatar/Avatar.svelte +85 -0
- package/dist/components/avatar/Avatar.svelte.d.ts +12 -0
- package/dist/components/badge/Badge.svelte +49 -0
- package/dist/components/badge/Badge.svelte.d.ts +12 -0
- package/dist/components/breadcrumb/Breadcrumb.svelte +37 -0
- package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +32 -0
- package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +10 -0
- package/dist/components/breadcrumb/BreadcrumbItem.svelte +71 -0
- package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +12 -0
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +18 -0
- package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +18 -0
- package/dist/components/button/Button.svelte +247 -0
- package/dist/components/button/Button.svelte.d.ts +24 -0
- package/dist/components/button-group/ButtonGroup.svelte +89 -0
- package/dist/components/button-group/ButtonGroup.svelte.d.ts +12 -0
- package/dist/components/calendar/Calendar.svelte +401 -0
- package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
- package/dist/components/calendar/dates.d.ts +12 -0
- package/dist/components/calendar/dates.js +48 -0
- package/dist/components/card/Card.svelte +55 -0
- package/dist/components/card/Card.svelte.d.ts +10 -0
- package/dist/components/card/CardAction.svelte +24 -0
- package/dist/components/card/CardAction.svelte.d.ts +9 -0
- package/dist/components/card/CardContent.svelte +20 -0
- package/dist/components/card/CardContent.svelte.d.ts +8 -0
- package/dist/components/card/CardDescription.svelte +23 -0
- package/dist/components/card/CardDescription.svelte.d.ts +8 -0
- package/dist/components/card/CardFooter.svelte +28 -0
- package/dist/components/card/CardFooter.svelte.d.ts +9 -0
- package/dist/components/card/CardHeader.svelte +29 -0
- package/dist/components/card/CardHeader.svelte.d.ts +9 -0
- package/dist/components/card/CardTitle.svelte +23 -0
- package/dist/components/card/CardTitle.svelte.d.ts +8 -0
- package/dist/components/checkbox/Checkbox.svelte +93 -0
- package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -0
- package/dist/components/color-picker/ColorPicker.svelte +233 -0
- package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -0
- package/dist/components/context-menu/ContextMenu.svelte +201 -0
- package/dist/components/context-menu/ContextMenu.svelte.d.ts +14 -0
- package/dist/components/context-menu/ContextMenuItem.svelte +105 -0
- package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +14 -0
- package/dist/components/context-menu/context.d.ts +14 -0
- package/dist/components/context-menu/context.js +2 -0
- package/dist/components/dialog/Dialog.svelte +225 -0
- package/dist/components/dialog/Dialog.svelte.d.ts +22 -0
- package/dist/components/field/Field.svelte +78 -0
- package/dist/components/field/Field.svelte.d.ts +11 -0
- package/dist/components/field/FieldContent.svelte +25 -0
- package/dist/components/field/FieldContent.svelte.d.ts +9 -0
- package/dist/components/field/FieldDescription.svelte +34 -0
- package/dist/components/field/FieldDescription.svelte.d.ts +9 -0
- package/dist/components/field/FieldError.svelte +45 -0
- package/dist/components/field/FieldError.svelte.d.ts +11 -0
- package/dist/components/field/FieldGroup.svelte +30 -0
- package/dist/components/field/FieldGroup.svelte.d.ts +9 -0
- package/dist/components/field/FieldLegend.svelte +34 -0
- package/dist/components/field/FieldLegend.svelte.d.ts +10 -0
- package/dist/components/field/FieldSeparator.svelte +31 -0
- package/dist/components/field/FieldSeparator.svelte.d.ts +9 -0
- package/dist/components/field/FieldSet.svelte +33 -0
- package/dist/components/field/FieldSet.svelte.d.ts +9 -0
- package/dist/components/input/Input.svelte +459 -0
- package/dist/components/input/Input.svelte.d.ts +10 -0
- package/dist/components/input-group/InputGroup.svelte +88 -0
- package/dist/components/input-group/InputGroup.svelte.d.ts +9 -0
- package/dist/components/input-group/InputGroupAddon.svelte +88 -0
- package/dist/components/input-group/InputGroupAddon.svelte.d.ts +11 -0
- package/dist/components/kbd/Kbd.svelte +46 -0
- package/dist/components/kbd/Kbd.svelte.d.ts +8 -0
- package/dist/components/kbd/KbdGroup.svelte +27 -0
- package/dist/components/kbd/KbdGroup.svelte.d.ts +8 -0
- package/dist/components/label/Label.svelte +48 -0
- package/dist/components/label/Label.svelte.d.ts +9 -0
- package/dist/components/link/Link.svelte +40 -0
- package/dist/components/link/Link.svelte.d.ts +10 -0
- package/dist/components/navbar/Navbar.svelte +266 -0
- package/dist/components/navbar/Navbar.svelte.d.ts +26 -0
- package/dist/components/navigation-menu/NavigationMenu.svelte +95 -0
- package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +12 -0
- package/dist/components/navigation-menu/NavigationMenuItem.svelte +257 -0
- package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +15 -0
- package/dist/components/navigation-menu/NavigationMenuLink.svelte +91 -0
- package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +16 -0
- package/dist/components/navigation-menu/context.d.ts +15 -0
- package/dist/components/navigation-menu/context.js +2 -0
- package/dist/components/popover/Popover.svelte +295 -0
- package/dist/components/popover/Popover.svelte.d.ts +34 -0
- package/dist/components/radio-group/RadioGroup.svelte +86 -0
- package/dist/components/radio-group/RadioGroup.svelte.d.ts +16 -0
- package/dist/components/radio-group/RadioGroupItem.svelte +95 -0
- package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +7 -0
- package/dist/components/radio-group/context.d.ts +9 -0
- package/dist/components/radio-group/context.js +2 -0
- package/dist/components/separator/Separator.svelte +41 -0
- package/dist/components/separator/Separator.svelte.d.ts +10 -0
- package/dist/components/sidebar/Sidebar.svelte +180 -0
- package/dist/components/sidebar/Sidebar.svelte.d.ts +14 -0
- package/dist/components/sidebar/SidebarContent.svelte +30 -0
- package/dist/components/sidebar/SidebarContent.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarFooter.svelte +23 -0
- package/dist/components/sidebar/SidebarFooter.svelte.d.ts +8 -0
- package/dist/components/sidebar/SidebarGroup.svelte +55 -0
- package/dist/components/sidebar/SidebarGroup.svelte.d.ts +11 -0
- package/dist/components/sidebar/SidebarHeader.svelte +23 -0
- package/dist/components/sidebar/SidebarHeader.svelte.d.ts +8 -0
- package/dist/components/sidebar/SidebarInset.svelte +25 -0
- package/dist/components/sidebar/SidebarInset.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarMenu.svelte +27 -0
- package/dist/components/sidebar/SidebarMenu.svelte.d.ts +9 -0
- package/dist/components/sidebar/SidebarMenuItem.svelte +221 -0
- package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +23 -0
- package/dist/components/sidebar/SidebarProvider.svelte +62 -0
- package/dist/components/sidebar/SidebarProvider.svelte.d.ts +11 -0
- package/dist/components/sidebar/SidebarSeparator.svelte +5 -0
- package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +18 -0
- package/dist/components/sidebar/SidebarTrigger.svelte +24 -0
- package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +7 -0
- package/dist/components/sidebar/context.d.ts +15 -0
- package/dist/components/sidebar/context.js +2 -0
- package/dist/components/skeleton/Skeleton.svelte +30 -0
- package/dist/components/skeleton/Skeleton.svelte.d.ts +5 -0
- package/dist/components/spinner/Spinner.svelte +72 -0
- package/dist/components/spinner/Spinner.svelte.d.ts +9 -0
- package/dist/components/switch/Switch.svelte +88 -0
- package/dist/components/switch/Switch.svelte.d.ts +5 -0
- package/dist/components/table/Table.svelte +276 -0
- package/dist/components/table/Table.svelte.d.ts +60 -0
- package/dist/components/textarea/Textarea.svelte +57 -0
- package/dist/components/textarea/Textarea.svelte.d.ts +4 -0
- package/dist/components/time-picker/TimePicker.svelte +162 -0
- package/dist/components/time-picker/TimePicker.svelte.d.ts +11 -0
- package/dist/components/toaster/Toaster.svelte +151 -0
- package/dist/components/toaster/Toaster.svelte.d.ts +7 -0
- package/dist/components/toaster/toast.svelte.d.ts +28 -0
- package/dist/components/toaster/toast.svelte.js +22 -0
- package/dist/components/toggle/Toggle.svelte +31 -0
- package/dist/components/toggle/Toggle.svelte.d.ts +8 -0
- package/dist/components/tooltip/Tooltip.svelte +224 -0
- package/dist/components/tooltip/Tooltip.svelte.d.ts +27 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +66 -0
- package/dist/styles/tokens.css +50 -0
- package/package.json +88 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
|
|
5
|
+
import Button from '../button/Button.svelte';
|
|
6
|
+
import {
|
|
7
|
+
addDays,
|
|
8
|
+
addMonths,
|
|
9
|
+
formatDate,
|
|
10
|
+
formatMonth,
|
|
11
|
+
formatWeek,
|
|
12
|
+
parseDate,
|
|
13
|
+
parseMonth,
|
|
14
|
+
parseWeek,
|
|
15
|
+
sameDay,
|
|
16
|
+
startOfWeek
|
|
17
|
+
} from './dates.js';
|
|
18
|
+
|
|
19
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> & {
|
|
20
|
+
/** Picks a day (`YYYY-MM-DD`), an ISO week (`YYYY-Www`) or a month (`YYYY-MM`), matching the input types. */
|
|
21
|
+
mode?: 'date' | 'week' | 'month';
|
|
22
|
+
/** Bindable, in the format of the mode. */
|
|
23
|
+
value?: string;
|
|
24
|
+
/** Earliest selectable value, in the format of the mode. */
|
|
25
|
+
min?: string;
|
|
26
|
+
/** Latest selectable value, in the format of the mode. */
|
|
27
|
+
max?: string;
|
|
28
|
+
/** BCP 47 locale for month and weekday names. Defaults to the browser's. */
|
|
29
|
+
locale?: string;
|
|
30
|
+
onselect?: (value: string) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
mode = 'date',
|
|
35
|
+
value = $bindable(''),
|
|
36
|
+
min,
|
|
37
|
+
max,
|
|
38
|
+
locale,
|
|
39
|
+
onselect,
|
|
40
|
+
class: className,
|
|
41
|
+
...rest
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
const today = new Date();
|
|
45
|
+
|
|
46
|
+
function parse(input: string | undefined) {
|
|
47
|
+
if (mode === 'week') return parseWeek(input);
|
|
48
|
+
if (mode === 'month') return parseMonth(input);
|
|
49
|
+
return parseDate(input);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function format(date: Date) {
|
|
53
|
+
if (mode === 'week') return formatWeek(date);
|
|
54
|
+
if (mode === 'month') return formatMonth(date);
|
|
55
|
+
return formatDate(date);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function outOfRange(date: Date) {
|
|
59
|
+
const formatted = format(date);
|
|
60
|
+
return (!!min && formatted < min) || (!!max && formatted > max);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let selected = $derived(parse(value));
|
|
64
|
+
/** Day or month holding the roving focus; its month (or year) is the one shown. */
|
|
65
|
+
let focused = $state(
|
|
66
|
+
parse(value) ?? new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
|
67
|
+
);
|
|
68
|
+
let gridEl: HTMLElement | undefined = $state();
|
|
69
|
+
|
|
70
|
+
let title = $derived(
|
|
71
|
+
mode === 'month'
|
|
72
|
+
? String(focused.getFullYear())
|
|
73
|
+
: new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(focused)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
let weekdays = $derived(
|
|
77
|
+
Array.from({ length: 7 }, (_, i) =>
|
|
78
|
+
new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(new Date(2024, 0, 1 + i))
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
let weeks = $derived.by(() => {
|
|
83
|
+
const start = startOfWeek(new Date(focused.getFullYear(), focused.getMonth(), 1));
|
|
84
|
+
return Array.from({ length: 6 }, (_, w) =>
|
|
85
|
+
Array.from({ length: 7 }, (_, d) => addDays(start, w * 7 + d))
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let months = $derived(
|
|
90
|
+
Array.from({ length: 12 }, (_, m) => new Date(focused.getFullYear(), m, 1))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const dayLabel = $derived(new Intl.DateTimeFormat(locale, { dateStyle: 'full' }));
|
|
94
|
+
const monthLabel = $derived(new Intl.DateTimeFormat(locale, { month: 'short' }));
|
|
95
|
+
const monthFullLabel = $derived(
|
|
96
|
+
new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' })
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
function select(date: Date) {
|
|
100
|
+
if (outOfRange(date)) return;
|
|
101
|
+
focused = date;
|
|
102
|
+
value = format(date);
|
|
103
|
+
onselect?.(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function page(direction: 1 | -1) {
|
|
107
|
+
focused =
|
|
108
|
+
mode === 'month'
|
|
109
|
+
? new Date(focused.getFullYear() + direction, focused.getMonth(), 1)
|
|
110
|
+
: addMonths(focused, direction);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function move(date: Date) {
|
|
114
|
+
focused = date;
|
|
115
|
+
await tick();
|
|
116
|
+
gridEl?.querySelector<HTMLElement>('[tabindex="0"]')?.focus();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
120
|
+
const columns = mode === 'month' ? 3 : 7;
|
|
121
|
+
const unit =
|
|
122
|
+
mode === 'month' ? (n: number) => addMonths(focused, n) : (n: number) => addDays(focused, n);
|
|
123
|
+
const target = {
|
|
124
|
+
ArrowLeft: unit(-1),
|
|
125
|
+
ArrowRight: unit(1),
|
|
126
|
+
ArrowUp: unit(-columns),
|
|
127
|
+
ArrowDown: unit(columns),
|
|
128
|
+
Home: mode === 'month' ? new Date(focused.getFullYear(), 0, 1) : startOfWeek(focused),
|
|
129
|
+
End:
|
|
130
|
+
mode === 'month'
|
|
131
|
+
? new Date(focused.getFullYear(), 11, 1)
|
|
132
|
+
: addDays(startOfWeek(focused), 6),
|
|
133
|
+
PageUp:
|
|
134
|
+
mode === 'month'
|
|
135
|
+
? new Date(focused.getFullYear() - 1, focused.getMonth(), 1)
|
|
136
|
+
: addMonths(focused, -1),
|
|
137
|
+
PageDown:
|
|
138
|
+
mode === 'month'
|
|
139
|
+
? new Date(focused.getFullYear() + 1, focused.getMonth(), 1)
|
|
140
|
+
: addMonths(focused, 1)
|
|
141
|
+
}[event.key];
|
|
142
|
+
|
|
143
|
+
if (target) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
move(target);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<div {...rest} class={['calendar', `calendar--${mode}`, className]}>
|
|
151
|
+
<div class="calendar-header">
|
|
152
|
+
<Button
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="sm"
|
|
155
|
+
icon={ChevronLeft}
|
|
156
|
+
label={mode === 'month' ? 'Previous year' : 'Previous month'}
|
|
157
|
+
onclick={() => page(-1)}
|
|
158
|
+
/>
|
|
159
|
+
<span class="calendar-title" aria-live="polite">{title}</span>
|
|
160
|
+
<Button
|
|
161
|
+
variant="ghost"
|
|
162
|
+
size="sm"
|
|
163
|
+
icon={ChevronRight}
|
|
164
|
+
label={mode === 'month' ? 'Next year' : 'Next month'}
|
|
165
|
+
onclick={() => page(1)}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{#if mode === 'month'}
|
|
170
|
+
<div class="calendar-months" role="group" aria-label={title} bind:this={gridEl}>
|
|
171
|
+
{#each months as month (month.getMonth())}
|
|
172
|
+
{@const current = month.getMonth() === focused.getMonth()}
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
class={[
|
|
176
|
+
'calendar-cell',
|
|
177
|
+
{
|
|
178
|
+
'calendar-cell--selected': sameDay(month, selected),
|
|
179
|
+
'calendar-cell--today':
|
|
180
|
+
month.getMonth() === today.getMonth() && month.getFullYear() === today.getFullYear()
|
|
181
|
+
}
|
|
182
|
+
]}
|
|
183
|
+
tabindex={current ? 0 : -1}
|
|
184
|
+
aria-label={monthFullLabel.format(month)}
|
|
185
|
+
aria-pressed={sameDay(month, selected)}
|
|
186
|
+
disabled={outOfRange(month)}
|
|
187
|
+
onclick={() => select(month)}
|
|
188
|
+
onkeydown={handleKeydown}
|
|
189
|
+
>
|
|
190
|
+
{monthLabel.format(month)}
|
|
191
|
+
</button>
|
|
192
|
+
{/each}
|
|
193
|
+
</div>
|
|
194
|
+
{:else}
|
|
195
|
+
<table
|
|
196
|
+
class="calendar-grid"
|
|
197
|
+
role="grid"
|
|
198
|
+
aria-label={title}
|
|
199
|
+
bind:this={gridEl}
|
|
200
|
+
onkeydown={handleKeydown}
|
|
201
|
+
>
|
|
202
|
+
<thead>
|
|
203
|
+
<tr>
|
|
204
|
+
{#if mode === 'week'}<th class="calendar-weeknumber" scope="col"
|
|
205
|
+
><span class="calendar-hidden">Week</span></th
|
|
206
|
+
>{/if}
|
|
207
|
+
{#each weekdays as weekday (weekday)}
|
|
208
|
+
<th scope="col">{weekday}</th>
|
|
209
|
+
{/each}
|
|
210
|
+
</tr>
|
|
211
|
+
</thead>
|
|
212
|
+
<tbody>
|
|
213
|
+
{#each weeks as week (formatDate(week[0]))}
|
|
214
|
+
{@const weekValue = formatWeek(week[0])}
|
|
215
|
+
<tr class={{ 'calendar-row--selected': mode === 'week' && weekValue === value }}>
|
|
216
|
+
{#if mode === 'week'}
|
|
217
|
+
<th class="calendar-weeknumber" scope="row">{weekValue.slice(-2)}</th>
|
|
218
|
+
{/if}
|
|
219
|
+
{#each week as day (formatDate(day))}
|
|
220
|
+
<td
|
|
221
|
+
role="gridcell"
|
|
222
|
+
aria-selected={mode === 'week' ? weekValue === value : sameDay(day, selected)}
|
|
223
|
+
>
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
class={[
|
|
227
|
+
'calendar-cell',
|
|
228
|
+
{
|
|
229
|
+
'calendar-cell--outside': day.getMonth() !== focused.getMonth(),
|
|
230
|
+
'calendar-cell--selected': mode === 'date' && sameDay(day, selected),
|
|
231
|
+
'calendar-cell--today': sameDay(day, today)
|
|
232
|
+
}
|
|
233
|
+
]}
|
|
234
|
+
tabindex={sameDay(day, focused) ? 0 : -1}
|
|
235
|
+
aria-label={dayLabel.format(day)}
|
|
236
|
+
disabled={outOfRange(day)}
|
|
237
|
+
onclick={() => select(day)}
|
|
238
|
+
>
|
|
239
|
+
{day.getDate()}
|
|
240
|
+
</button>
|
|
241
|
+
</td>
|
|
242
|
+
{/each}
|
|
243
|
+
</tr>
|
|
244
|
+
{/each}
|
|
245
|
+
</tbody>
|
|
246
|
+
</table>
|
|
247
|
+
{/if}
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<style>
|
|
251
|
+
.calendar {
|
|
252
|
+
display: flex;
|
|
253
|
+
flex-direction: column;
|
|
254
|
+
gap: 8px;
|
|
255
|
+
width: fit-content;
|
|
256
|
+
font-size: 13px;
|
|
257
|
+
color: var(--color-text);
|
|
258
|
+
user-select: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.calendar-header {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
gap: 8px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.calendar-title {
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
text-transform: capitalize;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.calendar-grid {
|
|
274
|
+
border-collapse: collapse;
|
|
275
|
+
border-spacing: 0;
|
|
276
|
+
table-layout: fixed;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.calendar-grid th {
|
|
280
|
+
height: 28px;
|
|
281
|
+
padding: 0;
|
|
282
|
+
font-size: 12px;
|
|
283
|
+
font-weight: 400;
|
|
284
|
+
color: var(--color-text-muted);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.calendar-grid td {
|
|
288
|
+
width: 32px;
|
|
289
|
+
padding: 1px 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.calendar-weeknumber {
|
|
293
|
+
width: 28px;
|
|
294
|
+
padding-right: 4px;
|
|
295
|
+
font-variant-numeric: tabular-nums;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.calendar-hidden {
|
|
299
|
+
position: absolute;
|
|
300
|
+
width: 1px;
|
|
301
|
+
height: 1px;
|
|
302
|
+
overflow: hidden;
|
|
303
|
+
clip-path: inset(50%);
|
|
304
|
+
white-space: nowrap;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.calendar-months {
|
|
308
|
+
display: grid;
|
|
309
|
+
grid-template-columns: repeat(3, 1fr);
|
|
310
|
+
gap: 4px;
|
|
311
|
+
width: 224px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.calendar-cell {
|
|
315
|
+
display: flex;
|
|
316
|
+
align-items: center;
|
|
317
|
+
justify-content: center;
|
|
318
|
+
width: 32px;
|
|
319
|
+
height: 32px;
|
|
320
|
+
padding: 0;
|
|
321
|
+
font-family: inherit;
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
font-variant-numeric: tabular-nums;
|
|
324
|
+
color: var(--color-text);
|
|
325
|
+
background-color: transparent;
|
|
326
|
+
border: none;
|
|
327
|
+
border-radius: 8px;
|
|
328
|
+
outline: none;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
transition:
|
|
331
|
+
background-color 200ms ease,
|
|
332
|
+
color 200ms ease;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.calendar-months .calendar-cell {
|
|
336
|
+
width: 100%;
|
|
337
|
+
height: 40px;
|
|
338
|
+
text-transform: capitalize;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.calendar-cell:hover:not(:disabled) {
|
|
342
|
+
background-color: var(--color-surface);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.calendar-cell:focus-visible {
|
|
346
|
+
outline: 2px solid var(--color-accent);
|
|
347
|
+
outline-offset: -2px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.calendar-cell--outside {
|
|
351
|
+
color: var(--color-text-faint);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.calendar-cell--today {
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
text-decoration: underline;
|
|
357
|
+
text-underline-offset: 4px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.calendar-cell:disabled {
|
|
361
|
+
cursor: not-allowed;
|
|
362
|
+
opacity: 0.35;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.calendar-cell--selected,
|
|
366
|
+
.calendar-cell--selected:hover:not(:disabled),
|
|
367
|
+
.calendar-row--selected .calendar-cell,
|
|
368
|
+
.calendar-row--selected .calendar-cell:hover:not(:disabled) {
|
|
369
|
+
color: var(--color-accent-foreground);
|
|
370
|
+
background-color: var(--color-accent);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.calendar-cell--selected:focus-visible,
|
|
374
|
+
.calendar-row--selected .calendar-cell:focus-visible {
|
|
375
|
+
outline-color: var(--color-accent-foreground);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* Week mode: a row reads as one pill */
|
|
379
|
+
.calendar--week tbody tr:hover .calendar-cell:not(:disabled) {
|
|
380
|
+
background-color: var(--color-surface);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.calendar--week tbody tr.calendar-row--selected .calendar-cell {
|
|
384
|
+
background-color: var(--color-accent);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.calendar--week .calendar-cell {
|
|
388
|
+
width: 100%;
|
|
389
|
+
border-radius: 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.calendar--week td:nth-child(2) .calendar-cell {
|
|
393
|
+
border-start-start-radius: 8px;
|
|
394
|
+
border-end-start-radius: 8px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.calendar--week td:last-child .calendar-cell {
|
|
398
|
+
border-start-end-radius: 8px;
|
|
399
|
+
border-end-end-radius: 8px;
|
|
400
|
+
}
|
|
401
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'onselect'> & {
|
|
3
|
+
/** Picks a day (`YYYY-MM-DD`), an ISO week (`YYYY-Www`) or a month (`YYYY-MM`), matching the input types. */
|
|
4
|
+
mode?: 'date' | 'week' | 'month';
|
|
5
|
+
/** Bindable, in the format of the mode. */
|
|
6
|
+
value?: string;
|
|
7
|
+
/** Earliest selectable value, in the format of the mode. */
|
|
8
|
+
min?: string;
|
|
9
|
+
/** Latest selectable value, in the format of the mode. */
|
|
10
|
+
max?: string;
|
|
11
|
+
/** BCP 47 locale for month and weekday names. Defaults to the browser's. */
|
|
12
|
+
locale?: string;
|
|
13
|
+
onselect?: (value: string) => void;
|
|
14
|
+
};
|
|
15
|
+
declare const Calendar: import("svelte").Component<Props, {}, "value">;
|
|
16
|
+
type Calendar = ReturnType<typeof Calendar>;
|
|
17
|
+
export default Calendar;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function formatDate(date: Date): string;
|
|
2
|
+
export declare function parseDate(value: string | undefined): Date | undefined;
|
|
3
|
+
export declare function formatMonth(date: Date): string;
|
|
4
|
+
export declare function parseMonth(value: string | undefined): Date | undefined;
|
|
5
|
+
/** ISO 8601 week, as used by `<input type="week">`: weeks start on Monday. */
|
|
6
|
+
export declare function formatWeek(date: Date): string;
|
|
7
|
+
/** Monday of the given ISO week. */
|
|
8
|
+
export declare function parseWeek(value: string | undefined): Date | undefined;
|
|
9
|
+
export declare function addDays(date: Date, days: number): Date;
|
|
10
|
+
export declare function addMonths(date: Date, months: number): Date;
|
|
11
|
+
export declare function startOfWeek(date: Date): Date;
|
|
12
|
+
export declare function sameDay(a: Date | undefined, b: Date | undefined): boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
2
|
+
export function formatDate(date) {
|
|
3
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
4
|
+
}
|
|
5
|
+
export function parseDate(value) {
|
|
6
|
+
const match = value?.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
7
|
+
return match ? new Date(+match[1], +match[2] - 1, +match[3]) : undefined;
|
|
8
|
+
}
|
|
9
|
+
export function formatMonth(date) {
|
|
10
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`;
|
|
11
|
+
}
|
|
12
|
+
export function parseMonth(value) {
|
|
13
|
+
const match = value?.match(/^(\d{4})-(\d{2})$/);
|
|
14
|
+
return match ? new Date(+match[1], +match[2] - 1, 1) : undefined;
|
|
15
|
+
}
|
|
16
|
+
/** ISO 8601 week, as used by `<input type="week">`: weeks start on Monday. */
|
|
17
|
+
export function formatWeek(date) {
|
|
18
|
+
const thursday = new Date(date);
|
|
19
|
+
thursday.setDate(date.getDate() + 3 - ((date.getDay() + 6) % 7));
|
|
20
|
+
const dayOfYear = Math.round((thursday.getTime() - new Date(thursday.getFullYear(), 0, 1).getTime()) / 86400000);
|
|
21
|
+
const week = Math.floor(dayOfYear / 7) + 1;
|
|
22
|
+
return `${thursday.getFullYear()}-W${pad(week)}`;
|
|
23
|
+
}
|
|
24
|
+
/** Monday of the given ISO week. */
|
|
25
|
+
export function parseWeek(value) {
|
|
26
|
+
const match = value?.match(/^(\d{4})-W(\d{2})$/);
|
|
27
|
+
if (!match)
|
|
28
|
+
return undefined;
|
|
29
|
+
const jan4 = new Date(+match[1], 0, 4);
|
|
30
|
+
return addDays(jan4, -((jan4.getDay() + 6) % 7) + (+match[2] - 1) * 7);
|
|
31
|
+
}
|
|
32
|
+
export function addDays(date, days) {
|
|
33
|
+
const result = new Date(date);
|
|
34
|
+
result.setDate(result.getDate() + days);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function addMonths(date, months) {
|
|
38
|
+
const result = new Date(date.getFullYear(), date.getMonth() + months, 1);
|
|
39
|
+
const lastDay = new Date(result.getFullYear(), result.getMonth() + 1, 0).getDate();
|
|
40
|
+
result.setDate(Math.min(date.getDate(), lastDay));
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
export function startOfWeek(date) {
|
|
44
|
+
return addDays(date, -((date.getDay() + 6) % 7));
|
|
45
|
+
}
|
|
46
|
+
export function sameDay(a, b) {
|
|
47
|
+
return !!a && !!b && formatDate(a) === formatDate(b);
|
|
48
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
variant?: 'outline' | 'secondary';
|
|
7
|
+
/** CardHeader, CardContent and CardFooter; an `<img>` first renders edge to edge. */
|
|
8
|
+
children: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { variant = 'outline', class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div {...rest} class={['card', `card--${variant}`, className]}>
|
|
15
|
+
{@render children()}
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
.card {
|
|
20
|
+
--card-spacing: 14px;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: var(--card-spacing);
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
padding-block: var(--card-spacing);
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
font-size: 13px;
|
|
28
|
+
color: var(--color-text);
|
|
29
|
+
border-radius: 14px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.card--outline {
|
|
33
|
+
background-color: var(--color-bg);
|
|
34
|
+
border: 1px solid var(--color-border);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.card--secondary {
|
|
38
|
+
background-color: var(--color-surface);
|
|
39
|
+
border: 1px solid transparent;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.card:has(> :global(img:first-child)) {
|
|
43
|
+
padding-top: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.card:has(> :global(.card-footer)) {
|
|
47
|
+
padding-bottom: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.card > :global(img) {
|
|
51
|
+
display: block;
|
|
52
|
+
width: 100%;
|
|
53
|
+
object-fit: cover;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
variant?: 'outline' | 'secondary';
|
|
5
|
+
/** CardHeader, CardContent and CardFooter; an `<img>` first renders edge to edge. */
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const Card: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Card = ReturnType<typeof Card>;
|
|
10
|
+
export default Card;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
/** E.g. a Button or Badge, placed top right in the CardHeader. */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div {...rest} class={['card-action', className]}>
|
|
14
|
+
{@render children()}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.card-action {
|
|
19
|
+
grid-column: 2;
|
|
20
|
+
grid-row: 1 / span 2;
|
|
21
|
+
align-self: start;
|
|
22
|
+
justify-self: end;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
/** E.g. a Button or Badge, placed top right in the CardHeader. */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const CardAction: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type CardAction = ReturnType<typeof CardAction>;
|
|
9
|
+
export default CardAction;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div {...rest} class={['card-content', className]}>
|
|
13
|
+
{@render children()}
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
.card-content {
|
|
18
|
+
padding-inline: var(--card-spacing);
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const CardContent: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type CardContent = ReturnType<typeof CardContent>;
|
|
8
|
+
export default CardContent;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> & {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<p {...rest} class={['card-description', className]}>
|
|
13
|
+
{@render children()}
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
.card-description {
|
|
18
|
+
margin: 0;
|
|
19
|
+
font-size: 13px;
|
|
20
|
+
line-height: 1.5;
|
|
21
|
+
color: var(--color-text-muted);
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> & {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const CardDescription: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type CardDescription = ReturnType<typeof CardDescription>;
|
|
8
|
+
export default CardDescription;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
/** Buttons or secondary content, primary Button first. */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div {...rest} class={['card-footer', className]}>
|
|
14
|
+
{@render children()}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.card-footer {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 4px;
|
|
23
|
+
margin-top: auto;
|
|
24
|
+
padding: var(--card-spacing);
|
|
25
|
+
background-color: color-mix(in srgb, var(--color-surface) 50%, transparent);
|
|
26
|
+
border-top: 1px solid var(--color-border);
|
|
27
|
+
}
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
/** Buttons or secondary content, primary Button first. */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const CardFooter: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type CardFooter = ReturnType<typeof CardFooter>;
|
|
9
|
+
export default CardFooter;
|