@nqmcreative/ui 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -15
- package/cli/index.mjs +328 -112
- package/dist/core/color.d.ts +24 -0
- package/dist/core/color.js +39 -0
- package/dist/core/index.d.ts +8 -1
- package/dist/core/index.js +5 -1
- package/dist/core/locale.svelte.d.ts +8 -0
- package/dist/core/locale.svelte.js +14 -0
- package/dist/core/number.d.ts +22 -0
- package/dist/core/number.js +33 -0
- package/dist/core/pin.d.ts +23 -0
- package/dist/core/pin.js +38 -0
- package/dist/core/tags.d.ts +37 -0
- package/dist/core/tags.js +51 -0
- package/dist/core/time.d.ts +30 -0
- package/dist/core/time.js +78 -0
- package/dist/core/toast.svelte.d.ts +3 -0
- package/dist/styles/matte/AccordionItem.svelte +5 -0
- package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/matte/Avatar.svelte +15 -0
- package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
- package/dist/styles/matte/Badge.svelte +5 -0
- package/dist/styles/matte/Badge.svelte.d.ts +2 -0
- package/dist/styles/matte/Breadcrumb.svelte +11 -4
- package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/matte/Button.svelte +58 -7
- package/dist/styles/matte/Button.svelte.d.ts +14 -2
- package/dist/styles/matte/Card.svelte +6 -1
- package/dist/styles/matte/Card.svelte.d.ts +2 -0
- package/dist/styles/matte/CheckboxGroup.svelte +6 -0
- package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/matte/ColorInput.svelte +110 -0
- package/dist/styles/matte/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/matte/Combobox.svelte +5 -0
- package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
- package/dist/styles/matte/CommandPalette.svelte +6 -0
- package/dist/styles/matte/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/matte/CurrencyInput.svelte +108 -0
- package/dist/styles/matte/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/matte/Drawer.svelte +5 -0
- package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
- package/dist/styles/matte/FileInput.svelte +194 -0
- package/dist/styles/matte/FileInput.svelte.d.ts +32 -0
- package/dist/styles/matte/Footer.svelte +6 -2
- package/dist/styles/matte/Footer.svelte.d.ts +2 -0
- package/dist/styles/matte/InputAddon.svelte +36 -0
- package/dist/styles/matte/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/matte/Link.svelte +9 -0
- package/dist/styles/matte/Link.svelte.d.ts +4 -0
- package/dist/styles/matte/MenuItem.svelte +2 -1
- package/dist/styles/matte/Modal.svelte +5 -0
- package/dist/styles/matte/Modal.svelte.d.ts +2 -0
- package/dist/styles/matte/MultiSelect.svelte +7 -1
- package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/matte/Navbar.svelte +18 -4
- package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
- package/dist/styles/matte/PinInput.svelte +137 -0
- package/dist/styles/matte/PinInput.svelte.d.ts +25 -0
- package/dist/styles/matte/PricingCard.svelte +16 -8
- package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/matte/RadioGroup.svelte +6 -0
- package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/matte/SearchInput.svelte +87 -0
- package/dist/styles/matte/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/matte/SegmentedControl.svelte +16 -2
- package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/matte/Sidebar.svelte +2 -1
- package/dist/styles/matte/Stat.svelte +8 -1
- package/dist/styles/matte/Stat.svelte.d.ts +3 -0
- package/dist/styles/matte/Steps.svelte +7 -0
- package/dist/styles/matte/Steps.svelte.d.ts +3 -0
- package/dist/styles/matte/Table.svelte +153 -97
- package/dist/styles/matte/Table.svelte.d.ts +12 -0
- package/dist/styles/matte/Tabs.svelte +5 -0
- package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
- package/dist/styles/matte/TagsInput.svelte +173 -0
- package/dist/styles/matte/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/matte/TimeInput.svelte +129 -0
- package/dist/styles/matte/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/matte/Toaster.svelte +3 -0
- package/dist/styles/matte/icon.d.ts +21 -0
- package/dist/styles/matte/icon.js +21 -0
- package/dist/styles/matte/index.d.ts +8 -2
- package/dist/styles/matte/index.js +8 -2
- package/dist/styles/paper/AccordionItem.svelte +5 -0
- package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/paper/Avatar.svelte +15 -0
- package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
- package/dist/styles/paper/Badge.svelte +5 -0
- package/dist/styles/paper/Badge.svelte.d.ts +2 -0
- package/dist/styles/paper/Breadcrumb.svelte +8 -4
- package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/paper/Button.svelte +58 -7
- package/dist/styles/paper/Button.svelte.d.ts +14 -2
- package/dist/styles/paper/Card.svelte +6 -1
- package/dist/styles/paper/Card.svelte.d.ts +2 -0
- package/dist/styles/paper/CheckboxGroup.svelte +6 -0
- package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/paper/ColorInput.svelte +112 -0
- package/dist/styles/paper/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/paper/Combobox.svelte +5 -0
- package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
- package/dist/styles/paper/CommandPalette.svelte +6 -0
- package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/paper/CurrencyInput.svelte +108 -0
- package/dist/styles/paper/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/paper/Drawer.svelte +5 -0
- package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
- package/dist/styles/paper/FileInput.svelte +204 -0
- package/dist/styles/paper/FileInput.svelte.d.ts +32 -0
- package/dist/styles/paper/Footer.svelte +6 -2
- package/dist/styles/paper/Footer.svelte.d.ts +2 -0
- package/dist/styles/paper/InputAddon.svelte +36 -0
- package/dist/styles/paper/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/paper/Link.svelte +9 -0
- package/dist/styles/paper/Link.svelte.d.ts +4 -0
- package/dist/styles/paper/MenuItem.svelte +2 -1
- package/dist/styles/paper/Modal.svelte +5 -0
- package/dist/styles/paper/Modal.svelte.d.ts +2 -0
- package/dist/styles/paper/MultiSelect.svelte +7 -1
- package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/paper/Navbar.svelte +18 -4
- package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
- package/dist/styles/paper/PinInput.svelte +137 -0
- package/dist/styles/paper/PinInput.svelte.d.ts +25 -0
- package/dist/styles/paper/PricingCard.svelte +23 -15
- package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/paper/RadioGroup.svelte +6 -0
- package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/paper/SearchInput.svelte +92 -0
- package/dist/styles/paper/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/paper/SegmentedControl.svelte +16 -2
- package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/paper/Sidebar.svelte +2 -1
- package/dist/styles/paper/Stat.svelte +8 -1
- package/dist/styles/paper/Stat.svelte.d.ts +3 -0
- package/dist/styles/paper/Steps.svelte +7 -0
- package/dist/styles/paper/Steps.svelte.d.ts +3 -0
- package/dist/styles/paper/Table.svelte +153 -97
- package/dist/styles/paper/Table.svelte.d.ts +12 -0
- package/dist/styles/paper/Tabs.svelte +5 -0
- package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
- package/dist/styles/paper/TagsInput.svelte +178 -0
- package/dist/styles/paper/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/paper/TimeInput.svelte +155 -0
- package/dist/styles/paper/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/paper/Toaster.svelte +3 -0
- package/dist/styles/paper/icon.d.ts +21 -0
- package/dist/styles/paper/icon.js +21 -0
- package/dist/styles/paper/index.d.ts +8 -2
- package/dist/styles/paper/index.js +8 -2
- package/dist/styles/sprout/AccordionItem.svelte +5 -0
- package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/sprout/Avatar.svelte +15 -0
- package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
- package/dist/styles/sprout/Badge.svelte +5 -0
- package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
- package/dist/styles/sprout/Breadcrumb.svelte +8 -4
- package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/sprout/Button.svelte +58 -7
- package/dist/styles/sprout/Button.svelte.d.ts +14 -2
- package/dist/styles/sprout/Card.svelte +6 -1
- package/dist/styles/sprout/Card.svelte.d.ts +2 -0
- package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
- package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/sprout/ColorInput.svelte +112 -0
- package/dist/styles/sprout/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/sprout/Combobox.svelte +5 -0
- package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
- package/dist/styles/sprout/CommandPalette.svelte +6 -0
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/sprout/CurrencyInput.svelte +108 -0
- package/dist/styles/sprout/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/sprout/Drawer.svelte +5 -0
- package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
- package/dist/styles/sprout/FileInput.svelte +205 -0
- package/dist/styles/sprout/FileInput.svelte.d.ts +32 -0
- package/dist/styles/sprout/Footer.svelte +6 -2
- package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
- package/dist/styles/sprout/InputAddon.svelte +37 -0
- package/dist/styles/sprout/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/sprout/Link.svelte +9 -0
- package/dist/styles/sprout/Link.svelte.d.ts +4 -0
- package/dist/styles/sprout/MenuItem.svelte +2 -1
- package/dist/styles/sprout/Modal.svelte +5 -0
- package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
- package/dist/styles/sprout/MultiSelect.svelte +7 -1
- package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/sprout/Navbar.svelte +18 -4
- package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
- package/dist/styles/sprout/PinInput.svelte +138 -0
- package/dist/styles/sprout/PinInput.svelte.d.ts +25 -0
- package/dist/styles/sprout/PricingCard.svelte +23 -15
- package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/sprout/RadioGroup.svelte +6 -0
- package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/sprout/SearchInput.svelte +92 -0
- package/dist/styles/sprout/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/sprout/SegmentedControl.svelte +16 -2
- package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/sprout/Sidebar.svelte +2 -1
- package/dist/styles/sprout/Stat.svelte +8 -1
- package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
- package/dist/styles/sprout/Steps.svelte +7 -0
- package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
- package/dist/styles/sprout/Table.svelte +153 -97
- package/dist/styles/sprout/Table.svelte.d.ts +12 -0
- package/dist/styles/sprout/Tabs.svelte +5 -0
- package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
- package/dist/styles/sprout/TagsInput.svelte +179 -0
- package/dist/styles/sprout/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/sprout/TimeInput.svelte +155 -0
- package/dist/styles/sprout/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/sprout/Toaster.svelte +3 -0
- package/dist/styles/sprout/icon.d.ts +21 -0
- package/dist/styles/sprout/icon.js +21 -0
- package/dist/styles/sprout/index.d.ts +8 -2
- package/dist/styles/sprout/index.js +8 -2
- package/package.json +98 -26
- package/registry.json +143 -35
- package/dist/styles/matte/NumberInput.svelte +0 -96
- package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/matte/PasswordInput.svelte +0 -95
- package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
- package/dist/styles/paper/NumberInput.svelte +0 -96
- package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/paper/PasswordInput.svelte +0 -100
- package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
- package/dist/styles/sprout/NumberInput.svelte +0 -97
- package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/sprout/PasswordInput.svelte +0 -101
- package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
3
|
+
import { caretIndex, clearFrom, fillFrom, sanitisePin, type PinMode } from '../../core/pin.js';
|
|
4
|
+
import { toneFocusBorder, type Tone } from '../../core/tones.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
/** Bindable code. Shorter than `length` means still incomplete. */
|
|
8
|
+
value?: string;
|
|
9
|
+
/** How many boxes. */
|
|
10
|
+
length?: number;
|
|
11
|
+
/** What a box accepts. */
|
|
12
|
+
mode?: PinMode;
|
|
13
|
+
/** Render the characters as dots. */
|
|
14
|
+
mask?: boolean;
|
|
15
|
+
/** Draw a gap after this many boxes — `3` gives `123 456`. */
|
|
16
|
+
groupAfter?: number;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
invalid?: boolean;
|
|
19
|
+
tone?: Tone;
|
|
20
|
+
/** `name` for a plain form submit — one hidden field holding the code. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** Fires once the last box is filled. */
|
|
23
|
+
oncomplete?: (value: string) => void;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
value = $bindable(''),
|
|
29
|
+
length = 6,
|
|
30
|
+
mode = 'numeric',
|
|
31
|
+
mask = false,
|
|
32
|
+
groupAfter = 0,
|
|
33
|
+
disabled = false,
|
|
34
|
+
invalid = false,
|
|
35
|
+
tone = 'brand',
|
|
36
|
+
name,
|
|
37
|
+
oncomplete,
|
|
38
|
+
class: className = ''
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const t = useLocale();
|
|
42
|
+
|
|
43
|
+
let boxes: HTMLInputElement[] = $state([]);
|
|
44
|
+
|
|
45
|
+
const chars = $derived(Array.from({ length }, (_, i) => value[i] ?? ''));
|
|
46
|
+
|
|
47
|
+
function focusAt(index: number) {
|
|
48
|
+
boxes[Math.max(0, Math.min(length - 1, index))]?.focus();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The DOM is written back on every keystroke, not only when the value
|
|
53
|
+
* changed: a refused character leaves the box holding text the state never
|
|
54
|
+
* accepted, and nothing else would clear it.
|
|
55
|
+
*/
|
|
56
|
+
function sync(box: HTMLInputElement, index: number) {
|
|
57
|
+
box.value = value[index] ?? '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function commit(next: string, from: number) {
|
|
61
|
+
const was = value;
|
|
62
|
+
value = next;
|
|
63
|
+
if (value.length === length && was.length < length) oncomplete?.(value);
|
|
64
|
+
// Land on the first empty box, or the one after whatever was just typed.
|
|
65
|
+
focusAt(Math.min(Math.max(from, caretIndex(value, length)), length - 1));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onInput(event: Event & { currentTarget: HTMLInputElement }, index: number) {
|
|
69
|
+
const typed = sanitisePin(event.currentTarget.value, mode);
|
|
70
|
+
if (!typed) {
|
|
71
|
+
sync(event.currentTarget, index);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const next = fillFrom(value, index, typed, length, mode);
|
|
75
|
+
commit(next, index + typed.length);
|
|
76
|
+
for (const [at, box] of boxes.entries()) sync(box, at);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function onKeydown(event: KeyboardEvent, index: number) {
|
|
80
|
+
if (event.key === 'Backspace') {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
// An empty box deletes the one before it, the way a caret would.
|
|
83
|
+
const target = value[index] ? index : index - 1;
|
|
84
|
+
if (target < 0) return;
|
|
85
|
+
value = clearFrom(value, target);
|
|
86
|
+
for (const [at, box] of boxes.entries()) sync(box, at);
|
|
87
|
+
focusAt(target);
|
|
88
|
+
} else if (event.key === 'ArrowLeft') {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
focusAt(index - 1);
|
|
91
|
+
} else if (event.key === 'ArrowRight') {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
focusAt(index + 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function onPaste(event: ClipboardEvent, index: number) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
const text = event.clipboardData?.getData('text') ?? '';
|
|
100
|
+
const clean = sanitisePin(text, mode);
|
|
101
|
+
if (!clean) return;
|
|
102
|
+
commit(fillFrom(value, index, clean, length, mode), index + clean.length);
|
|
103
|
+
for (const [at, box] of boxes.entries()) sync(box, at);
|
|
104
|
+
}
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<div class="flex items-center gap-2 {className}">
|
|
108
|
+
{#each chars as char, index (index)}
|
|
109
|
+
{#if groupAfter && index > 0 && index % groupAfter === 0}
|
|
110
|
+
<span class="h-px w-2 bg-hairline-strong" aria-hidden="true"></span>
|
|
111
|
+
{/if}
|
|
112
|
+
<input
|
|
113
|
+
bind:this={boxes[index]}
|
|
114
|
+
value={char}
|
|
115
|
+
{disabled}
|
|
116
|
+
type={mask ? 'password' : 'text'}
|
|
117
|
+
inputmode={mode === 'numeric' ? 'numeric' : 'text'}
|
|
118
|
+
autocomplete={index === 0 ? 'one-time-code' : 'off'}
|
|
119
|
+
autocapitalize="characters"
|
|
120
|
+
spellcheck="false"
|
|
121
|
+
maxlength={length}
|
|
122
|
+
aria-label="{t.current.digit} {index + 1}"
|
|
123
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
124
|
+
oninput={(event) => onInput(event, index)}
|
|
125
|
+
onkeydown={(event) => onKeydown(event, index)}
|
|
126
|
+
onpaste={(event) => onPaste(event, index)}
|
|
127
|
+
onfocus={(event) => event.currentTarget.select()}
|
|
128
|
+
class="size-11 border bg-bg text-center font-mono text-base text-text transition-colors duration-150 ease-brand-out focus:outline-none
|
|
129
|
+
{invalid ? 'border-danger' : `border-hairline ${toneFocusBorder[tone]}`}
|
|
130
|
+
{disabled ? 'pointer-events-none opacity-50' : ''}"
|
|
131
|
+
/>
|
|
132
|
+
{/each}
|
|
133
|
+
|
|
134
|
+
{#if name}
|
|
135
|
+
<input type="hidden" {name} {value} />
|
|
136
|
+
{/if}
|
|
137
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type PinMode } from '../../core/pin.js';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Bindable code. Shorter than `length` means still incomplete. */
|
|
5
|
+
value?: string;
|
|
6
|
+
/** How many boxes. */
|
|
7
|
+
length?: number;
|
|
8
|
+
/** What a box accepts. */
|
|
9
|
+
mode?: PinMode;
|
|
10
|
+
/** Render the characters as dots. */
|
|
11
|
+
mask?: boolean;
|
|
12
|
+
/** Draw a gap after this many boxes — `3` gives `123 456`. */
|
|
13
|
+
groupAfter?: number;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
invalid?: boolean;
|
|
16
|
+
tone?: Tone;
|
|
17
|
+
/** `name` for a plain form submit — one hidden field holding the code. */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** Fires once the last box is filled. */
|
|
20
|
+
oncomplete?: (value: string) => void;
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const PinInput: import("svelte").Component<Props, {}, "value">;
|
|
24
|
+
type PinInput = ReturnType<typeof PinInput>;
|
|
25
|
+
export default PinInput;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { toneBorder, toneFill, toneSoft, toneText, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { iconMd } from './icon.js';
|
|
5
6
|
|
|
6
7
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
7
8
|
name: string;
|
|
@@ -12,6 +13,8 @@
|
|
|
12
13
|
description?: string;
|
|
13
14
|
/** One line each. Rendered with a tick. */
|
|
14
15
|
features?: string[];
|
|
16
|
+
/** Replaces the tick beside every feature. */
|
|
17
|
+
featureIcon?: Snippet;
|
|
15
18
|
/** Pill in the top corner, e.g. "Most popular". */
|
|
16
19
|
badge?: string;
|
|
17
20
|
/** Thicker border and a tinted header — for the plan you want chosen. */
|
|
@@ -29,6 +32,7 @@
|
|
|
29
32
|
period,
|
|
30
33
|
description,
|
|
31
34
|
features = [],
|
|
35
|
+
featureIcon,
|
|
32
36
|
badge,
|
|
33
37
|
featured = false,
|
|
34
38
|
tone = 'brand',
|
|
@@ -83,14 +87,18 @@
|
|
|
83
87
|
<ul class="flex flex-1 flex-col gap-3 border-t border-hairline p-5">
|
|
84
88
|
{#each features as feature (feature)}
|
|
85
89
|
<li class="flex items-start gap-3 font-sans text-sm text-text-secondary">
|
|
86
|
-
|
|
87
|
-
class="mt-0.5
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
{#if featureIcon}
|
|
91
|
+
<span class="mt-0.5 {iconMd}">{@render featureIcon()}</span>
|
|
92
|
+
{:else}
|
|
93
|
+
<svg
|
|
94
|
+
class="mt-0.5 size-4 shrink-0 {toneText[tone]}"
|
|
95
|
+
viewBox="0 0 16 16"
|
|
96
|
+
fill="none"
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
>
|
|
99
|
+
<path d="m3 8 3.2 3.2L13 4.4" stroke="currentColor" stroke-width="1.6" />
|
|
100
|
+
</svg>
|
|
101
|
+
{/if}
|
|
94
102
|
<span class="leading-snug">{feature}</span>
|
|
95
103
|
</li>
|
|
96
104
|
{/each}
|
|
@@ -10,6 +10,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
|
10
10
|
description?: string;
|
|
11
11
|
/** One line each. Rendered with a tick. */
|
|
12
12
|
features?: string[];
|
|
13
|
+
/** Replaces the tick beside every feature. */
|
|
14
|
+
featureIcon?: Snippet;
|
|
13
15
|
/** Pill in the top corner, e.g. "Most popular". */
|
|
14
16
|
badge?: string;
|
|
15
17
|
/** Thicker border and a tinted header — for the plan you want chosen. */
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
import type { HTMLFieldsetAttributes } from 'svelte/elements';
|
|
4
4
|
import Radio from './Radio.svelte';
|
|
5
5
|
import type { Tone } from '../../core/tones.js';
|
|
6
|
+
import { iconLg } from './icon.js';
|
|
6
7
|
|
|
7
8
|
export interface RadioOption {
|
|
8
9
|
value: string;
|
|
9
10
|
label: string;
|
|
11
|
+
/** Leading icon, 20px. Drawn in `boxed` mode only. */
|
|
12
|
+
icon?: Snippet;
|
|
10
13
|
description?: string;
|
|
11
14
|
disabled?: boolean;
|
|
12
15
|
}
|
|
@@ -60,6 +63,9 @@
|
|
|
60
63
|
{value === option.value ? 'border-text bg-bg-alt' : 'border-hairline hover:border-hairline-strong'}
|
|
61
64
|
{option.disabled ? 'pointer-events-none opacity-50' : ''}"
|
|
62
65
|
>
|
|
66
|
+
{#if option.icon}
|
|
67
|
+
<span class={iconLg}>{@render option.icon()}</span>
|
|
68
|
+
{/if}
|
|
63
69
|
<Radio
|
|
64
70
|
bind:group={value}
|
|
65
71
|
value={option.value}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import type { Tone } from '../../core/tones.js';
|
|
6
|
+
import Input, { type InputSize } from './Input.svelte';
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'type'> {
|
|
9
|
+
value?: string;
|
|
10
|
+
size?: InputSize;
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
invalid?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Milliseconds to wait after the last keystroke before `onsearch` fires.
|
|
15
|
+
* `0` fires on every one.
|
|
16
|
+
*/
|
|
17
|
+
debounce?: number;
|
|
18
|
+
/** The debounced value. The bound `value` still updates immediately. */
|
|
19
|
+
onsearch?: (value: string) => void;
|
|
20
|
+
onclear?: () => void;
|
|
21
|
+
/** Replaces the magnifier. */
|
|
22
|
+
icon?: Snippet;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
value = $bindable(''),
|
|
28
|
+
size = 'md',
|
|
29
|
+
tone = 'brand',
|
|
30
|
+
invalid = false,
|
|
31
|
+
disabled = false,
|
|
32
|
+
debounce = 0,
|
|
33
|
+
onsearch,
|
|
34
|
+
onclear,
|
|
35
|
+
icon,
|
|
36
|
+
class: className = '',
|
|
37
|
+
...rest
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
const t = useLocale();
|
|
41
|
+
|
|
42
|
+
// The timer is cleared by the effect's own teardown, so a keystroke that
|
|
43
|
+
// lands mid-wait restarts the clock rather than queueing a second call.
|
|
44
|
+
$effect(() => {
|
|
45
|
+
const next = value;
|
|
46
|
+
if (!onsearch) return;
|
|
47
|
+
if (debounce <= 0) {
|
|
48
|
+
onsearch(next);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const timer = setTimeout(() => onsearch(next), debounce);
|
|
52
|
+
return () => clearTimeout(timer);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function clear() {
|
|
56
|
+
value = '';
|
|
57
|
+
onclear?.();
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Input bind:value type="search" {size} {tone} {invalid} {disabled} class={className} {...rest}>
|
|
62
|
+
{#snippet prefix()}
|
|
63
|
+
{#if icon}
|
|
64
|
+
{@render icon()}
|
|
65
|
+
{:else}
|
|
66
|
+
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
67
|
+
<circle cx="9" cy="9" r="5.5" stroke="currentColor" stroke-width="1.5" />
|
|
68
|
+
<path d="m13 13 3.5 3.5" stroke="currentColor" stroke-width="1.5" />
|
|
69
|
+
</svg>
|
|
70
|
+
{/if}
|
|
71
|
+
{/snippet}
|
|
72
|
+
|
|
73
|
+
{#snippet suffix()}
|
|
74
|
+
{#if value}
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
onclick={clear}
|
|
78
|
+
aria-label={t.current.clearSearch}
|
|
79
|
+
class="-mr-1 p-1 text-text-muted transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
80
|
+
>
|
|
81
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
82
|
+
<path d="m2 2 10 10M12 2 2 12" stroke="currentColor" stroke-width="1.5" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
{/if}
|
|
86
|
+
{/snippet}
|
|
87
|
+
</Input>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Tone } from '../../core/tones.js';
|
|
4
|
+
import { type InputSize } from './Input.svelte';
|
|
5
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'type'> {
|
|
6
|
+
value?: string;
|
|
7
|
+
size?: InputSize;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Milliseconds to wait after the last keystroke before `onsearch` fires.
|
|
12
|
+
* `0` fires on every one.
|
|
13
|
+
*/
|
|
14
|
+
debounce?: number;
|
|
15
|
+
/** The debounced value. The bound `value` still updates immediately. */
|
|
16
|
+
onsearch?: (value: string) => void;
|
|
17
|
+
onclear?: () => void;
|
|
18
|
+
/** Replaces the magnifier. */
|
|
19
|
+
icon?: Snippet;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const SearchInput: import("svelte").Component<Props, {}, "value">;
|
|
23
|
+
type SearchInput = ReturnType<typeof SearchInput>;
|
|
24
|
+
export default SearchInput;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
4
|
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { iconMd } from './icon.js';
|
|
4
6
|
|
|
5
7
|
export interface SegmentOption {
|
|
6
8
|
value: string;
|
|
7
9
|
label: string;
|
|
10
|
+
/** Leading icon, 16px. */
|
|
11
|
+
icon?: Snippet;
|
|
8
12
|
disabled?: boolean;
|
|
9
13
|
}
|
|
10
14
|
|
|
@@ -20,6 +24,8 @@
|
|
|
20
24
|
tone?: Tone;
|
|
21
25
|
/** Spread the segments evenly across the full width. */
|
|
22
26
|
fullWidth?: boolean;
|
|
27
|
+
/** Icons only — each option's `label` becomes its accessible name. */
|
|
28
|
+
iconOnly?: boolean;
|
|
23
29
|
label?: string;
|
|
24
30
|
disabled?: boolean;
|
|
25
31
|
onchange?: (value: string) => void;
|
|
@@ -32,6 +38,7 @@
|
|
|
32
38
|
size = 'md',
|
|
33
39
|
tone = 'brand',
|
|
34
40
|
fullWidth = false,
|
|
41
|
+
iconOnly = false,
|
|
35
42
|
label,
|
|
36
43
|
disabled = false,
|
|
37
44
|
class: className = '',
|
|
@@ -44,6 +51,11 @@
|
|
|
44
51
|
md: 'h-9 px-3.5 text-sm'
|
|
45
52
|
};
|
|
46
53
|
|
|
54
|
+
const squares: Record<SegmentedSize, string> = {
|
|
55
|
+
sm: 'size-8 text-[13px]',
|
|
56
|
+
md: 'size-9 text-sm'
|
|
57
|
+
};
|
|
58
|
+
|
|
47
59
|
function select(option: SegmentOption) {
|
|
48
60
|
if (option.disabled || disabled) return;
|
|
49
61
|
value = option.value;
|
|
@@ -74,14 +86,16 @@
|
|
|
74
86
|
type="button"
|
|
75
87
|
role="radio"
|
|
76
88
|
aria-checked={value === option.value}
|
|
89
|
+
aria-label={iconOnly ? option.label : undefined}
|
|
77
90
|
disabled={option.disabled || disabled}
|
|
78
91
|
tabindex={value === option.value ? 0 : -1}
|
|
79
92
|
onclick={() => select(option)}
|
|
80
93
|
class="inline-flex items-center justify-center font-sans whitespace-nowrap transition-colors duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-40
|
|
81
|
-
{sizes[size]} {focusRing} {toneRing[tone]}
|
|
94
|
+
{iconOnly ? squares[size] : sizes[size]} {focusRing} {toneRing[tone]}
|
|
82
95
|
{value === option.value ? `${toneSoft[tone]} font-medium` : 'text-text-secondary hover:text-text'}"
|
|
83
96
|
>
|
|
84
|
-
{option.
|
|
97
|
+
{#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
|
|
98
|
+
{#if !iconOnly}{option.label}{/if}
|
|
85
99
|
</button>
|
|
86
100
|
{/each}
|
|
87
101
|
{#if name}<input type="hidden" {name} {value} />{/if}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
3
|
import { type Tone } from '../../core/tones.js';
|
|
3
4
|
export interface SegmentOption {
|
|
4
5
|
value: string;
|
|
5
6
|
label: string;
|
|
7
|
+
/** Leading icon, 16px. */
|
|
8
|
+
icon?: Snippet;
|
|
6
9
|
disabled?: boolean;
|
|
7
10
|
}
|
|
8
11
|
export type SegmentedSize = 'sm' | 'md';
|
|
@@ -16,6 +19,8 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> {
|
|
|
16
19
|
tone?: Tone;
|
|
17
20
|
/** Spread the segments evenly across the full width. */
|
|
18
21
|
fullWidth?: boolean;
|
|
22
|
+
/** Icons only — each option's `label` becomes its accessible name. */
|
|
23
|
+
iconOnly?: boolean;
|
|
19
24
|
label?: string;
|
|
20
25
|
disabled?: boolean;
|
|
21
26
|
onchange?: (value: string) => void;
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
<script lang="ts">
|
|
38
38
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
39
|
+
import { iconMd } from './icon.js';
|
|
39
40
|
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
40
41
|
|
|
41
42
|
interface Props {
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
<!-- One box for every glyph, so rows line up whether or not an item has one. -->
|
|
127
128
|
{#snippet glyph(item: SidebarItem)}
|
|
128
129
|
{#if item.icon}
|
|
129
|
-
<span class=
|
|
130
|
+
<span class={iconMd}>{@render item.icon()}</span>
|
|
130
131
|
{/if}
|
|
131
132
|
{/snippet}
|
|
132
133
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
4
|
import { toneText, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { iconMd } from './icon.js';
|
|
4
6
|
|
|
5
7
|
export type StatTrend = 'up' | 'down' | 'flat';
|
|
6
8
|
|
|
@@ -11,6 +13,8 @@
|
|
|
11
13
|
delta?: string;
|
|
12
14
|
trend?: StatTrend;
|
|
13
15
|
hint?: string;
|
|
16
|
+
/** Leading icon on the label row. */
|
|
17
|
+
icon?: Snippet;
|
|
14
18
|
/** Colour of the value itself. Leave unset for plain `text`. */
|
|
15
19
|
tone?: Tone;
|
|
16
20
|
}
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
delta,
|
|
22
26
|
trend = 'flat',
|
|
23
27
|
hint,
|
|
28
|
+
icon,
|
|
24
29
|
tone,
|
|
25
30
|
class: className = '',
|
|
26
31
|
...rest
|
|
@@ -31,7 +36,9 @@
|
|
|
31
36
|
</script>
|
|
32
37
|
|
|
33
38
|
<div class="flex flex-col gap-1 font-sans {className}" {...rest}>
|
|
34
|
-
<span class="font-mono text-xs tracking-wide text-text-muted uppercase"
|
|
39
|
+
<span class="flex items-center gap-2 font-mono text-xs tracking-wide text-text-muted uppercase"
|
|
40
|
+
>{#if icon}<span class={iconMd}>{@render icon()}</span>{/if}{label}</span
|
|
41
|
+
>
|
|
35
42
|
<div class="flex items-baseline gap-2">
|
|
36
43
|
<span
|
|
37
44
|
class="font-heading text-3xl font-medium tracking-tight {tone ? toneText[tone] : 'text-text'}"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
3
|
import { type Tone } from '../../core/tones.js';
|
|
3
4
|
export type StatTrend = 'up' | 'down' | 'flat';
|
|
@@ -8,6 +9,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
|
8
9
|
delta?: string;
|
|
9
10
|
trend?: StatTrend;
|
|
10
11
|
hint?: string;
|
|
12
|
+
/** Leading icon on the label row. */
|
|
13
|
+
icon?: Snippet;
|
|
11
14
|
/** Colour of the value itself. Leave unset for plain `text`. */
|
|
12
15
|
tone?: Tone;
|
|
13
16
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
2
4
|
export interface StepItem {
|
|
3
5
|
label: string;
|
|
4
6
|
description?: string;
|
|
7
|
+
/** Replaces the step number. A finished step still shows its check. */
|
|
8
|
+
icon?: Snippet;
|
|
5
9
|
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
6
10
|
error?: boolean;
|
|
7
11
|
disabled?: boolean;
|
|
@@ -10,6 +14,7 @@
|
|
|
10
14
|
|
|
11
15
|
<script lang="ts">
|
|
12
16
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import { iconMd } from './icon.js';
|
|
13
18
|
import { focusRing, toneFill, toneRing, toneText, type Tone } from '../../core/tones.js';
|
|
14
19
|
|
|
15
20
|
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
@@ -67,6 +72,8 @@
|
|
|
67
72
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
68
73
|
<path d="m2 7 3.2 3.2L12 3.6" stroke="currentColor" stroke-width="1.8" />
|
|
69
74
|
</svg>
|
|
75
|
+
{:else if item.icon}
|
|
76
|
+
<span class={iconMd}>{@render item.icon()}</span>
|
|
70
77
|
{:else}
|
|
71
78
|
{index + 1}
|
|
72
79
|
{/if}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
export interface StepItem {
|
|
2
3
|
label: string;
|
|
3
4
|
description?: string;
|
|
5
|
+
/** Replaces the step number. A finished step still shows its check. */
|
|
6
|
+
icon?: Snippet;
|
|
4
7
|
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
5
8
|
error?: boolean;
|
|
6
9
|
disabled?: boolean;
|