@nqmcreative/ui 0.2.1 → 0.2.3
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 +7 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/trigger.d.ts +6 -0
- package/dist/core/trigger.js +13 -0
- package/dist/styles/matte/Button.svelte +4 -4
- package/dist/styles/matte/Card.svelte +3 -3
- package/dist/styles/matte/Combobox.svelte +2 -2
- package/dist/styles/matte/CommandPalette.svelte +32 -7
- package/dist/styles/matte/CommandPalette.svelte.d.ts +5 -0
- package/dist/styles/matte/DatePicker.svelte +2 -2
- package/dist/styles/matte/Drawer.svelte +2 -2
- package/dist/styles/matte/Dropzone.svelte +1 -1
- package/dist/styles/matte/FeatureCard.svelte +1 -1
- package/dist/styles/matte/Input.svelte +4 -4
- package/dist/styles/matte/MenuItem.svelte +1 -1
- package/dist/styles/matte/Modal.svelte +3 -3
- package/dist/styles/matte/NumberInput.svelte +2 -2
- package/dist/styles/matte/PasswordInput.svelte +2 -2
- package/dist/styles/matte/PricingCard.svelte +3 -3
- package/dist/styles/matte/SegmentedControl.svelte +1 -1
- package/dist/styles/matte/Select.svelte +1 -1
- package/dist/styles/matte/Sidebar.svelte +75 -26
- package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
- package/dist/styles/matte/StatsBand.svelte +1 -1
- package/dist/styles/matte/Table.svelte +3 -3
- package/dist/styles/matte/Testimonial.svelte +2 -2
- package/dist/styles/matte/Textarea.svelte +1 -1
- package/dist/styles/matte/index.d.ts +1 -1
- package/dist/styles/paper/CommandPalette.svelte +25 -2
- package/dist/styles/paper/CommandPalette.svelte.d.ts +5 -0
- package/dist/styles/paper/Modal.svelte +9 -1
- package/dist/styles/paper/Sidebar.svelte +75 -26
- package/dist/styles/paper/Sidebar.svelte.d.ts +15 -1
- package/dist/styles/paper/fonts.css +34 -7
- package/dist/styles/paper/index.d.ts +1 -1
- package/dist/styles/paper/theme.css +11 -8
- package/dist/styles/sprout/Accordion.svelte +17 -0
- package/dist/styles/sprout/Accordion.svelte.d.ts +8 -0
- package/dist/styles/sprout/AccordionItem.svelte +46 -0
- package/dist/styles/sprout/AccordionItem.svelte.d.ts +14 -0
- package/dist/styles/sprout/Alert.svelte +100 -0
- package/dist/styles/sprout/Alert.svelte.d.ts +18 -0
- package/dist/styles/sprout/Avatar.svelte +60 -0
- package/dist/styles/sprout/Avatar.svelte.d.ts +16 -0
- package/dist/styles/sprout/AvatarGroup.svelte +17 -0
- package/dist/styles/sprout/AvatarGroup.svelte.d.ts +8 -0
- package/dist/styles/sprout/Badge.svelte +58 -0
- package/dist/styles/sprout/Badge.svelte.d.ts +16 -0
- package/dist/styles/sprout/Breadcrumb.svelte +49 -0
- package/dist/styles/sprout/Breadcrumb.svelte.d.ts +14 -0
- package/dist/styles/sprout/Button.svelte +105 -0
- package/dist/styles/sprout/Button.svelte.d.ts +21 -0
- package/dist/styles/sprout/CTASection.svelte +77 -0
- package/dist/styles/sprout/CTASection.svelte.d.ts +18 -0
- package/dist/styles/sprout/Calendar.svelte +202 -0
- package/dist/styles/sprout/Calendar.svelte.d.ts +22 -0
- package/dist/styles/sprout/Card.svelte +82 -0
- package/dist/styles/sprout/Card.svelte.d.ts +21 -0
- package/dist/styles/sprout/Checkbox.svelte +76 -0
- package/dist/styles/sprout/Checkbox.svelte.d.ts +15 -0
- package/dist/styles/sprout/CheckboxGroup.svelte +112 -0
- package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/styles/sprout/Combobox.svelte +267 -0
- package/dist/styles/sprout/Combobox.svelte.d.ts +29 -0
- package/dist/styles/sprout/CommandPalette.svelte +233 -0
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +37 -0
- package/dist/styles/sprout/ConfirmDialog.svelte +97 -0
- package/dist/styles/sprout/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/styles/sprout/ContextMenu.svelte +89 -0
- package/dist/styles/sprout/ContextMenu.svelte.d.ts +15 -0
- package/dist/styles/sprout/DatePicker.svelte +193 -0
- package/dist/styles/sprout/DatePicker.svelte.d.ts +23 -0
- package/dist/styles/sprout/Divider.svelte +30 -0
- package/dist/styles/sprout/Divider.svelte.d.ts +10 -0
- package/dist/styles/sprout/Drawer.svelte +166 -0
- package/dist/styles/sprout/Drawer.svelte.d.ts +20 -0
- package/dist/styles/sprout/Dropdown.svelte +95 -0
- package/dist/styles/sprout/Dropdown.svelte.d.ts +23 -0
- package/dist/styles/sprout/Dropzone.svelte +211 -0
- package/dist/styles/sprout/Dropzone.svelte.d.ts +29 -0
- package/dist/styles/sprout/EmptyState.svelte +71 -0
- package/dist/styles/sprout/EmptyState.svelte.d.ts +18 -0
- package/dist/styles/sprout/FeatureCard.svelte +61 -0
- package/dist/styles/sprout/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/sprout/FeatureGrid.svelte +62 -0
- package/dist/styles/sprout/FeatureGrid.svelte.d.ts +16 -0
- package/dist/styles/sprout/Field.svelte +42 -0
- package/dist/styles/sprout/Field.svelte.d.ts +16 -0
- package/dist/styles/sprout/Footer.svelte +91 -0
- package/dist/styles/sprout/Footer.svelte.d.ts +25 -0
- package/dist/styles/sprout/HeroSection.svelte +86 -0
- package/dist/styles/sprout/HeroSection.svelte.d.ts +21 -0
- package/dist/styles/sprout/Input.svelte +61 -0
- package/dist/styles/sprout/Input.svelte.d.ts +18 -0
- package/dist/styles/sprout/InputGroup.svelte +20 -0
- package/dist/styles/sprout/InputGroup.svelte.d.ts +11 -0
- package/dist/styles/sprout/Kbd.svelte +18 -0
- package/dist/styles/sprout/Kbd.svelte.d.ts +8 -0
- package/dist/styles/sprout/Label.svelte +16 -0
- package/dist/styles/sprout/Label.svelte.d.ts +9 -0
- package/dist/styles/sprout/Link.svelte +44 -0
- package/dist/styles/sprout/Link.svelte.d.ts +16 -0
- package/dist/styles/sprout/LocaleProvider.svelte +24 -0
- package/dist/styles/sprout/LocaleProvider.svelte.d.ts +10 -0
- package/dist/styles/sprout/LogoCloud.svelte +61 -0
- package/dist/styles/sprout/LogoCloud.svelte.d.ts +19 -0
- package/dist/styles/sprout/MenuItem.svelte +64 -0
- package/dist/styles/sprout/MenuItem.svelte.d.ts +18 -0
- package/dist/styles/sprout/MenuSeparator.svelte +22 -0
- package/dist/styles/sprout/MenuSeparator.svelte.d.ts +8 -0
- package/dist/styles/sprout/Modal.svelte +127 -0
- package/dist/styles/sprout/Modal.svelte.d.ts +21 -0
- package/dist/styles/sprout/MultiSelect.svelte +288 -0
- package/dist/styles/sprout/MultiSelect.svelte.d.ts +31 -0
- package/dist/styles/sprout/Navbar.svelte +201 -0
- package/dist/styles/sprout/Navbar.svelte.d.ts +33 -0
- package/dist/styles/sprout/NumberInput.svelte +97 -0
- package/dist/styles/sprout/NumberInput.svelte.d.ts +15 -0
- package/dist/styles/sprout/Pagination.svelte +84 -0
- package/dist/styles/sprout/Pagination.svelte.d.ts +15 -0
- package/dist/styles/sprout/PasswordInput.svelte +101 -0
- package/dist/styles/sprout/PasswordInput.svelte.d.ts +12 -0
- package/dist/styles/sprout/Popover.svelte +90 -0
- package/dist/styles/sprout/Popover.svelte.d.ts +19 -0
- package/dist/styles/sprout/PricingCard.svelte +109 -0
- package/dist/styles/sprout/PricingCard.svelte.d.ts +25 -0
- package/dist/styles/sprout/Progress.svelte +59 -0
- package/dist/styles/sprout/Progress.svelte.d.ts +17 -0
- package/dist/styles/sprout/Radio.svelte +56 -0
- package/dist/styles/sprout/Radio.svelte.d.ts +15 -0
- package/dist/styles/sprout/RadioGroup.svelte +96 -0
- package/dist/styles/sprout/RadioGroup.svelte.d.ts +27 -0
- package/dist/styles/sprout/SegmentedControl.svelte +89 -0
- package/dist/styles/sprout/SegmentedControl.svelte.d.ts +25 -0
- package/dist/styles/sprout/Select.svelte +62 -0
- package/dist/styles/sprout/Select.svelte.d.ts +20 -0
- package/dist/styles/sprout/Sidebar.svelte +255 -0
- package/dist/styles/sprout/Sidebar.svelte.d.ts +50 -0
- package/dist/styles/sprout/Skeleton.svelte +46 -0
- package/dist/styles/sprout/Skeleton.svelte.d.ts +13 -0
- package/dist/styles/sprout/Slider.svelte +80 -0
- package/dist/styles/sprout/Slider.svelte.d.ts +19 -0
- package/dist/styles/sprout/Spinner.svelte +34 -0
- package/dist/styles/sprout/Spinner.svelte.d.ts +13 -0
- package/dist/styles/sprout/Stat.svelte +51 -0
- package/dist/styles/sprout/Stat.svelte.d.ts +16 -0
- package/dist/styles/sprout/StatsBand.svelte +64 -0
- package/dist/styles/sprout/StatsBand.svelte.d.ts +20 -0
- package/dist/styles/sprout/Steps.svelte +143 -0
- package/dist/styles/sprout/Steps.svelte.d.ts +24 -0
- package/dist/styles/sprout/Switch.svelte +65 -0
- package/dist/styles/sprout/Switch.svelte.d.ts +14 -0
- package/dist/styles/sprout/Table.svelte +218 -0
- package/dist/styles/sprout/Table.svelte.d.ts +66 -0
- package/dist/styles/sprout/Tabs.svelte +103 -0
- package/dist/styles/sprout/Tabs.svelte.d.ts +23 -0
- package/dist/styles/sprout/Testimonial.svelte +65 -0
- package/dist/styles/sprout/Testimonial.svelte.d.ts +19 -0
- package/dist/styles/sprout/Textarea.svelte +55 -0
- package/dist/styles/sprout/Textarea.svelte.d.ts +12 -0
- package/dist/styles/sprout/ThemeToggle.svelte +131 -0
- package/dist/styles/sprout/ThemeToggle.svelte.d.ts +16 -0
- package/dist/styles/sprout/Toaster.svelte +133 -0
- package/dist/styles/sprout/Toaster.svelte.d.ts +8 -0
- package/dist/styles/sprout/Tooltip.svelte +34 -0
- package/dist/styles/sprout/Tooltip.svelte.d.ts +12 -0
- package/dist/styles/sprout/fonts.css +77 -0
- package/dist/styles/sprout/index.d.ts +109 -0
- package/dist/styles/sprout/index.js +83 -0
- package/dist/styles/sprout/lift.d.ts +40 -0
- package/dist/styles/sprout/lift.js +36 -0
- package/dist/styles/sprout/theme.css +255 -0
- package/package.json +269 -3
- package/registry.json +139 -66
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
addMonths,
|
|
4
|
+
fromISO,
|
|
5
|
+
isOutOfRange,
|
|
6
|
+
isSameDay,
|
|
7
|
+
monthGrid,
|
|
8
|
+
monthLabel,
|
|
9
|
+
toISO,
|
|
10
|
+
today,
|
|
11
|
+
weekdayNames
|
|
12
|
+
} from '../../core/date.js';
|
|
13
|
+
import { calendarKeyMove } from '../../core/calendar.js';
|
|
14
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
15
|
+
import {
|
|
16
|
+
focusRing,
|
|
17
|
+
toneFill,
|
|
18
|
+
toneRing,
|
|
19
|
+
toneSoft,
|
|
20
|
+
toneText,
|
|
21
|
+
type Tone
|
|
22
|
+
} from '../../core/tones.js';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Bindable `YYYY-MM-DD`, or `''` for nothing selected. */
|
|
26
|
+
value?: string;
|
|
27
|
+
/** Bindable `YYYY-MM-DD` inside the month on show. */
|
|
28
|
+
month?: string;
|
|
29
|
+
/** Inclusive bounds, `YYYY-MM-DD`. */
|
|
30
|
+
min?: string;
|
|
31
|
+
max?: string;
|
|
32
|
+
/** Return true to grey out a specific day — weekends, holidays. */
|
|
33
|
+
isDisabled?: (date: Date) => boolean;
|
|
34
|
+
/** 1 = Monday (default), 0 = Sunday. */
|
|
35
|
+
weekStart?: 0 | 1;
|
|
36
|
+
tone?: Tone;
|
|
37
|
+
/** Show the "Today" shortcut under the grid. */
|
|
38
|
+
showToday?: boolean;
|
|
39
|
+
onselect?: (value: string) => void;
|
|
40
|
+
class?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
value = $bindable(''),
|
|
45
|
+
month = $bindable(''),
|
|
46
|
+
min,
|
|
47
|
+
max,
|
|
48
|
+
isDisabled,
|
|
49
|
+
weekStart = 1,
|
|
50
|
+
tone = 'brand',
|
|
51
|
+
showToday = true,
|
|
52
|
+
onselect,
|
|
53
|
+
class: className = ''
|
|
54
|
+
}: Props = $props();
|
|
55
|
+
|
|
56
|
+
const t = useLocale();
|
|
57
|
+
|
|
58
|
+
const selected = $derived(fromISO(value));
|
|
59
|
+
const now = today();
|
|
60
|
+
|
|
61
|
+
let view = $state(fromISO(month) ?? fromISO(value) ?? today());
|
|
62
|
+
/** The day the arrow keys are on — not necessarily the selected one. */
|
|
63
|
+
let focused = $state(fromISO(value) ?? today());
|
|
64
|
+
let grid: HTMLElement | null = $state(null);
|
|
65
|
+
|
|
66
|
+
// Follow the bound `month`/`value` when the parent changes them.
|
|
67
|
+
$effect(() => {
|
|
68
|
+
const next = fromISO(month) ?? fromISO(value);
|
|
69
|
+
if (next && (next.getMonth() !== view.getMonth() || next.getFullYear() !== view.getFullYear()))
|
|
70
|
+
view = next;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const days = $derived(monthGrid(view, weekStart));
|
|
74
|
+
const weekdays = $derived(weekdayNames(t.current.dateLocale, weekStart));
|
|
75
|
+
|
|
76
|
+
function blocked(date: Date) {
|
|
77
|
+
return isOutOfRange(date, min, max) || (isDisabled?.(date) ?? false);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function show(next: Date) {
|
|
81
|
+
view = next;
|
|
82
|
+
month = toISO(new Date(next.getFullYear(), next.getMonth(), 1));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function pick(date: Date) {
|
|
86
|
+
if (blocked(date)) return;
|
|
87
|
+
value = toISO(date);
|
|
88
|
+
focused = date;
|
|
89
|
+
if (date.getMonth() !== view.getMonth()) show(date);
|
|
90
|
+
onselect?.(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function moveFocus(next: Date) {
|
|
94
|
+
focused = next;
|
|
95
|
+
if (next.getMonth() !== view.getMonth() || next.getFullYear() !== view.getFullYear())
|
|
96
|
+
show(next);
|
|
97
|
+
queueMicrotask(() => grid?.querySelector<HTMLElement>('[data-focused="true"]')?.focus());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onKeydown(event: KeyboardEvent) {
|
|
101
|
+
const next = calendarKeyMove(event.key, focused, weekStart, event.shiftKey);
|
|
102
|
+
if (next) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
moveFocus(next);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
pick(focused);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const arrow =
|
|
114
|
+
'inline-flex size-8 items-center justify-center rounded-xl text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text';
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<div class="flex w-[18rem] flex-col gap-3 p-3 font-sans {className}">
|
|
118
|
+
<div class="flex items-center justify-between gap-2">
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onclick={() => show(addMonths(view, -1))}
|
|
122
|
+
aria-label={t.current.previousMonth}
|
|
123
|
+
class="{arrow} {focusRing} {toneRing[tone]}"
|
|
124
|
+
>
|
|
125
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
126
|
+
<path d="m10 4-4 4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
127
|
+
</svg>
|
|
128
|
+
</button>
|
|
129
|
+
|
|
130
|
+
<span aria-live="polite" class="text-sm font-semibold text-text">
|
|
131
|
+
{monthLabel(view, t.current.dateLocale)}
|
|
132
|
+
</span>
|
|
133
|
+
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
onclick={() => show(addMonths(view, 1))}
|
|
137
|
+
aria-label={t.current.nextMonth}
|
|
138
|
+
class="{arrow} {focusRing} {toneRing[tone]}"
|
|
139
|
+
>
|
|
140
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
141
|
+
<path d="m6 4 4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
142
|
+
</svg>
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="grid grid-cols-7 gap-0.5">
|
|
147
|
+
{#each weekdays as name, i (i)}
|
|
148
|
+
<span class="pb-1 text-center text-[11px] font-medium text-text-muted">{name}</span>
|
|
149
|
+
{/each}
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- Roving tabindex: the grid itself is never a tab stop, the focused day is. -->
|
|
153
|
+
<div
|
|
154
|
+
bind:this={grid}
|
|
155
|
+
role="grid"
|
|
156
|
+
tabindex="-1"
|
|
157
|
+
aria-label={t.current.selectDate}
|
|
158
|
+
onkeydown={onKeydown}
|
|
159
|
+
class="grid grid-cols-7 gap-0.5 focus:outline-none"
|
|
160
|
+
>
|
|
161
|
+
{#each days as day (toISO(day))}
|
|
162
|
+
{@const outside = day.getMonth() !== view.getMonth()}
|
|
163
|
+
{@const off = blocked(day)}
|
|
164
|
+
{@const isSelected = isSameDay(day, selected)}
|
|
165
|
+
{@const isFocused = isSameDay(day, focused)}
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
role="gridcell"
|
|
169
|
+
data-focused={isFocused}
|
|
170
|
+
tabindex={isFocused ? 0 : -1}
|
|
171
|
+
disabled={off}
|
|
172
|
+
aria-selected={isSelected}
|
|
173
|
+
aria-current={isSameDay(day, now) ? 'date' : undefined}
|
|
174
|
+
onclick={() => pick(day)}
|
|
175
|
+
class="flex h-9 items-center justify-center rounded-xl text-[13px] tabular-nums transition-colors duration-100 disabled:pointer-events-none disabled:opacity-30 {focusRing} {toneRing[
|
|
176
|
+
tone
|
|
177
|
+
]}
|
|
178
|
+
{isSelected
|
|
179
|
+
? `${toneFill[tone]} font-semibold text-text-inverse`
|
|
180
|
+
: outside
|
|
181
|
+
? 'text-text-muted/60 hover:bg-bg-inset'
|
|
182
|
+
: 'text-text-secondary hover:bg-bg-inset hover:text-text'}
|
|
183
|
+
{!isSelected && isSameDay(day, now) ? `${toneSoft[tone]} font-semibold` : ''}"
|
|
184
|
+
>
|
|
185
|
+
{day.getDate()}
|
|
186
|
+
</button>
|
|
187
|
+
{/each}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{#if showToday}
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onclick={() => pick(now)}
|
|
194
|
+
disabled={blocked(now)}
|
|
195
|
+
class="self-start rounded px-1 text-[13px] font-medium transition-colors duration-150 disabled:pointer-events-none disabled:opacity-40 {toneText[
|
|
196
|
+
tone
|
|
197
|
+
]} hover:underline hover:underline-offset-2 {focusRing} {toneRing[tone]}"
|
|
198
|
+
>
|
|
199
|
+
{t.current.today}
|
|
200
|
+
</button>
|
|
201
|
+
{/if}
|
|
202
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Tone } from '../../core/tones.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Bindable `YYYY-MM-DD`, or `''` for nothing selected. */
|
|
4
|
+
value?: string;
|
|
5
|
+
/** Bindable `YYYY-MM-DD` inside the month on show. */
|
|
6
|
+
month?: string;
|
|
7
|
+
/** Inclusive bounds, `YYYY-MM-DD`. */
|
|
8
|
+
min?: string;
|
|
9
|
+
max?: string;
|
|
10
|
+
/** Return true to grey out a specific day — weekends, holidays. */
|
|
11
|
+
isDisabled?: (date: Date) => boolean;
|
|
12
|
+
/** 1 = Monday (default), 0 = Sunday. */
|
|
13
|
+
weekStart?: 0 | 1;
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
/** Show the "Today" shortcut under the grid. */
|
|
16
|
+
showToday?: boolean;
|
|
17
|
+
onselect?: (value: string) => void;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const Calendar: import("svelte").Component<Props, {}, "month" | "value">;
|
|
21
|
+
type Calendar = ReturnType<typeof Calendar>;
|
|
22
|
+
export default Calendar;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { toneBorderLeft, toneHoverBorder, toneSurface, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { soft, softOnHover } from './lift.js';
|
|
6
|
+
|
|
7
|
+
export type CardVariant = 'outline' | 'filled' | 'tinted';
|
|
8
|
+
|
|
9
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
variant?: CardVariant;
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
/** Draws a tone-coloured rule down the left edge. */
|
|
13
|
+
accent?: boolean;
|
|
14
|
+
/** Lifts the card on hover — use for clickable cards. */
|
|
15
|
+
interactive?: boolean;
|
|
16
|
+
padded?: boolean;
|
|
17
|
+
title?: string;
|
|
18
|
+
eyebrow?: string;
|
|
19
|
+
header?: Snippet;
|
|
20
|
+
footer?: Snippet;
|
|
21
|
+
children: Snippet;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
variant = 'outline',
|
|
26
|
+
tone = 'brand',
|
|
27
|
+
accent = false,
|
|
28
|
+
interactive = false,
|
|
29
|
+
padded = true,
|
|
30
|
+
title,
|
|
31
|
+
eyebrow,
|
|
32
|
+
header,
|
|
33
|
+
footer,
|
|
34
|
+
class: className = '',
|
|
35
|
+
children,
|
|
36
|
+
...rest
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
// The default card is the one the style is named for: cream, a hairline, and
|
|
40
|
+
// the lip. `filled` and `tinted` stay flat on purpose — a tinted panel that
|
|
41
|
+
// also floats is two claims about the same box.
|
|
42
|
+
const variants = $derived({
|
|
43
|
+
outline: `border border-hairline bg-bg ${soft}`,
|
|
44
|
+
filled: 'border border-hairline bg-bg-alt',
|
|
45
|
+
tinted: `border border-transparent ${toneSurface[tone]}`
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div
|
|
50
|
+
class="flex flex-col rounded-[28px] {variants[variant]}
|
|
51
|
+
{accent ? `border-l-4 ${toneBorderLeft[tone]}` : ''}
|
|
52
|
+
{interactive
|
|
53
|
+
? `transition-[box-shadow,transform,border-color] duration-150 ease-brand-out hover:-translate-y-0.5 ${softOnHover} ${toneHoverBorder[tone]}`
|
|
54
|
+
: ''}
|
|
55
|
+
{className}"
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
{#if header || title || eyebrow}
|
|
59
|
+
<div class="flex flex-col gap-1 {padded ? 'px-6 pt-6' : ''}">
|
|
60
|
+
{#if header}
|
|
61
|
+
{@render header()}
|
|
62
|
+
{:else}
|
|
63
|
+
{#if eyebrow}
|
|
64
|
+
<span class="font-sans text-xs font-semibold text-text-muted">{eyebrow}</span>
|
|
65
|
+
{/if}
|
|
66
|
+
{#if title}
|
|
67
|
+
<h3 class="font-heading text-base font-semibold text-text">{title}</h3>
|
|
68
|
+
{/if}
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
72
|
+
|
|
73
|
+
<div class="flex-1 {padded ? (header || title || eyebrow ? 'px-6 pt-3 pb-6' : 'p-6') : ''}">
|
|
74
|
+
{@render children()}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{#if footer}
|
|
78
|
+
<div class="rounded-b-[28px] border-t border-hairline bg-bg-alt {padded ? 'px-6 py-4' : ''}">
|
|
79
|
+
{@render footer()}
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
82
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
export type CardVariant = 'outline' | 'filled' | 'tinted';
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: CardVariant;
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
/** Draws a tone-coloured rule down the left edge. */
|
|
9
|
+
accent?: boolean;
|
|
10
|
+
/** Lifts the card on hover — use for clickable cards. */
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
padded?: boolean;
|
|
13
|
+
title?: string;
|
|
14
|
+
eyebrow?: string;
|
|
15
|
+
header?: Snippet;
|
|
16
|
+
footer?: Snippet;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const Card: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Card = ReturnType<typeof Card>;
|
|
21
|
+
export default Card;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
+
import { peerFocusRing, tonePeerChecked, tonePeerFocus, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { edge } from './lift.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLInputAttributes, 'type' | 'size'> {
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
/** Renders the dash state — visual only, `checked` stays false. */
|
|
10
|
+
indeterminate?: boolean;
|
|
11
|
+
label?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
tone?: Tone;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
checked = $bindable(false),
|
|
19
|
+
indeterminate = false,
|
|
20
|
+
label,
|
|
21
|
+
description,
|
|
22
|
+
tone = 'brand',
|
|
23
|
+
disabled = false,
|
|
24
|
+
class: className = '',
|
|
25
|
+
children,
|
|
26
|
+
...rest
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
let el: HTMLInputElement | null = $state(null);
|
|
30
|
+
|
|
31
|
+
$effect(() => {
|
|
32
|
+
if (el) el.indeterminate = indeterminate;
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<label
|
|
37
|
+
class="group inline-flex items-start gap-2.5 font-sans text-sm
|
|
38
|
+
{disabled ? 'pointer-events-none opacity-50' : 'cursor-pointer'} {className}"
|
|
39
|
+
>
|
|
40
|
+
<input bind:this={el} type="checkbox" bind:checked {disabled} class="peer sr-only" {...rest} />
|
|
41
|
+
<span
|
|
42
|
+
class="mt-0.5 flex size-[18px] shrink-0 items-center justify-center rounded border border-hairline-strong bg-bg {edge} transition-colors duration-150 ease-brand-out
|
|
43
|
+
{tonePeerChecked[tone]} {peerFocusRing} {tonePeerFocus[tone]}"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
>
|
|
46
|
+
{#if indeterminate}
|
|
47
|
+
<span class="h-0.5 w-2.5 rounded-full bg-text-muted"></span>
|
|
48
|
+
{:else}
|
|
49
|
+
<svg
|
|
50
|
+
class="size-3 text-text-inverse opacity-0 transition-opacity duration-150 group-has-[:checked]:opacity-100"
|
|
51
|
+
viewBox="0 0 12 12"
|
|
52
|
+
fill="none"
|
|
53
|
+
>
|
|
54
|
+
<path
|
|
55
|
+
d="m2 6 2.6 2.6L10 3.2"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
stroke-width="2"
|
|
58
|
+
stroke-linecap="round"
|
|
59
|
+
stroke-linejoin="round"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
{/if}
|
|
63
|
+
</span>
|
|
64
|
+
{#if label || description || children}
|
|
65
|
+
<span class="flex flex-col gap-0.5">
|
|
66
|
+
{#if children}
|
|
67
|
+
{@render children()}
|
|
68
|
+
{:else if label}
|
|
69
|
+
<span class="leading-snug text-text">{label}</span>
|
|
70
|
+
{/if}
|
|
71
|
+
{#if description}
|
|
72
|
+
<span class="text-[13px] leading-snug text-text-muted">{description}</span>
|
|
73
|
+
{/if}
|
|
74
|
+
</span>
|
|
75
|
+
{/if}
|
|
76
|
+
</label>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'type' | 'size'> {
|
|
5
|
+
checked?: boolean;
|
|
6
|
+
/** Renders the dash state — visual only, `checked` stays false. */
|
|
7
|
+
indeterminate?: boolean;
|
|
8
|
+
label?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
|
|
14
|
+
type Checkbox = ReturnType<typeof Checkbox>;
|
|
15
|
+
export default Checkbox;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLFieldsetAttributes } from 'svelte/elements';
|
|
4
|
+
import Checkbox from './Checkbox.svelte';
|
|
5
|
+
import { toneBorder, toneSurface, type Tone } from '../../core/tones.js';
|
|
6
|
+
import { edge } from './lift.js';
|
|
7
|
+
|
|
8
|
+
export interface CheckboxOption {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props extends Omit<HTMLFieldsetAttributes, 'onchange'> {
|
|
16
|
+
/** Bindable — the `value` of every checked option. */
|
|
17
|
+
value?: string[];
|
|
18
|
+
options?: CheckboxOption[];
|
|
19
|
+
legend?: string;
|
|
20
|
+
hint?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
tone?: Tone;
|
|
24
|
+
orientation?: 'vertical' | 'horizontal';
|
|
25
|
+
/** Wraps each option in a bordered, clickable card. */
|
|
26
|
+
boxed?: boolean;
|
|
27
|
+
/** Cap on how many may be checked at once. `0` means no limit. */
|
|
28
|
+
max?: number;
|
|
29
|
+
onchange?: (value: string[]) => void;
|
|
30
|
+
/** Pass your own `Checkbox`es instead of `options`. */
|
|
31
|
+
children?: Snippet;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
value = $bindable([]),
|
|
36
|
+
options,
|
|
37
|
+
legend,
|
|
38
|
+
hint,
|
|
39
|
+
error,
|
|
40
|
+
required = false,
|
|
41
|
+
tone = 'brand',
|
|
42
|
+
orientation = 'vertical',
|
|
43
|
+
boxed = false,
|
|
44
|
+
max = 0,
|
|
45
|
+
onchange,
|
|
46
|
+
class: className = '',
|
|
47
|
+
children,
|
|
48
|
+
...rest
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
const full = $derived(max > 0 && value.length >= max);
|
|
52
|
+
|
|
53
|
+
function toggle(option: CheckboxOption) {
|
|
54
|
+
value = value.includes(option.value)
|
|
55
|
+
? value.filter((v) => v !== option.value)
|
|
56
|
+
: [...value, option.value];
|
|
57
|
+
onchange?.(value);
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<fieldset class="flex flex-col gap-3 border-0 p-0 {className}" {...rest}>
|
|
62
|
+
{#if legend}
|
|
63
|
+
<legend class="mb-1 font-sans text-sm font-medium text-text">
|
|
64
|
+
{legend}
|
|
65
|
+
{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span>{/if}
|
|
66
|
+
</legend>
|
|
67
|
+
{/if}
|
|
68
|
+
|
|
69
|
+
<div class="flex gap-2.5 {orientation === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col'}">
|
|
70
|
+
{#if options}
|
|
71
|
+
{#each options as option (option.value)}
|
|
72
|
+
{@const checked = value.includes(option.value)}
|
|
73
|
+
{@const disabled = option.disabled || (full && !checked)}
|
|
74
|
+
{#if boxed}
|
|
75
|
+
<label
|
|
76
|
+
class="flex flex-1 cursor-pointer items-start gap-3 rounded-[20px] border p-4 {edge} transition-colors duration-150 ease-brand-out
|
|
77
|
+
{checked
|
|
78
|
+
? `${toneBorder[tone]} ${toneSurface[tone]}`
|
|
79
|
+
: 'border-hairline bg-bg hover:border-hairline-strong'}
|
|
80
|
+
{disabled ? 'pointer-events-none opacity-50' : ''}"
|
|
81
|
+
>
|
|
82
|
+
<Checkbox
|
|
83
|
+
{checked}
|
|
84
|
+
{disabled}
|
|
85
|
+
{tone}
|
|
86
|
+
label={option.label}
|
|
87
|
+
description={option.description}
|
|
88
|
+
onchange={() => toggle(option)}
|
|
89
|
+
/>
|
|
90
|
+
</label>
|
|
91
|
+
{:else}
|
|
92
|
+
<Checkbox
|
|
93
|
+
{checked}
|
|
94
|
+
{disabled}
|
|
95
|
+
{tone}
|
|
96
|
+
label={option.label}
|
|
97
|
+
description={option.description}
|
|
98
|
+
onchange={() => toggle(option)}
|
|
99
|
+
/>
|
|
100
|
+
{/if}
|
|
101
|
+
{/each}
|
|
102
|
+
{:else if children}
|
|
103
|
+
{@render children()}
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{#if error}
|
|
108
|
+
<p class="font-sans text-[13px] text-danger">{error}</p>
|
|
109
|
+
{:else if hint}
|
|
110
|
+
<p class="font-sans text-[13px] text-text-muted">{hint}</p>
|
|
111
|
+
{/if}
|
|
112
|
+
</fieldset>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLFieldsetAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
export interface CheckboxOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface Props extends Omit<HTMLFieldsetAttributes, 'onchange'> {
|
|
11
|
+
/** Bindable — the `value` of every checked option. */
|
|
12
|
+
value?: string[];
|
|
13
|
+
options?: CheckboxOption[];
|
|
14
|
+
legend?: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
tone?: Tone;
|
|
19
|
+
orientation?: 'vertical' | 'horizontal';
|
|
20
|
+
/** Wraps each option in a bordered, clickable card. */
|
|
21
|
+
boxed?: boolean;
|
|
22
|
+
/** Cap on how many may be checked at once. `0` means no limit. */
|
|
23
|
+
max?: number;
|
|
24
|
+
onchange?: (value: string[]) => void;
|
|
25
|
+
/** Pass your own `Checkbox`es instead of `options`. */
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
}
|
|
28
|
+
declare const CheckboxGroup: import("svelte").Component<Props, {}, "value">;
|
|
29
|
+
type CheckboxGroup = ReturnType<typeof CheckboxGroup>;
|
|
30
|
+
export default CheckboxGroup;
|