@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
4
|
import Spinner from './Spinner.svelte';
|
|
5
|
+
import { iconMd, iconSm } from './icon.js';
|
|
5
6
|
import {
|
|
6
7
|
focusRing,
|
|
7
8
|
toneBorder,
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
|
|
18
19
|
export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
19
20
|
|
|
20
|
-
interface Props extends HTMLButtonAttributes {
|
|
21
|
+
interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
|
|
21
22
|
variant?: ButtonVariant;
|
|
22
23
|
tone?: Tone;
|
|
23
24
|
size?: ButtonSize;
|
|
@@ -28,7 +29,19 @@
|
|
|
28
29
|
loading?: boolean;
|
|
29
30
|
/** Stretch to the container's full width. */
|
|
30
31
|
block?: boolean;
|
|
31
|
-
|
|
32
|
+
/** Leading icon. The spinner takes its place while the button is busy. */
|
|
33
|
+
icon?: Snippet;
|
|
34
|
+
/** Trailing icon. */
|
|
35
|
+
iconEnd?: Snippet;
|
|
36
|
+
/** Square button with no label — pass an `aria-label`. */
|
|
37
|
+
iconOnly?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Return a promise — a save, a submit, a fetch — and the button spins and
|
|
40
|
+
* locks itself until it settles, without any state in the parent. It
|
|
41
|
+
* unlocks on a rejection too, so catch failures inside the handler.
|
|
42
|
+
*/
|
|
43
|
+
onclick?: (event: MouseEvent) => unknown;
|
|
44
|
+
children?: Snippet;
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
let {
|
|
@@ -38,6 +51,10 @@
|
|
|
38
51
|
href,
|
|
39
52
|
loading = false,
|
|
40
53
|
block = false,
|
|
54
|
+
icon,
|
|
55
|
+
iconEnd,
|
|
56
|
+
iconOnly = false,
|
|
57
|
+
onclick,
|
|
41
58
|
disabled = false,
|
|
42
59
|
type = 'button',
|
|
43
60
|
class: className = '',
|
|
@@ -55,6 +72,13 @@
|
|
|
55
72
|
xl: 'h-12 px-8 text-base'
|
|
56
73
|
};
|
|
57
74
|
|
|
75
|
+
const squares: Record<ButtonSize, string> = {
|
|
76
|
+
sm: 'size-8',
|
|
77
|
+
md: 'size-10',
|
|
78
|
+
lg: 'size-11',
|
|
79
|
+
xl: 'size-12'
|
|
80
|
+
};
|
|
81
|
+
|
|
58
82
|
const variantClass = $derived.by(() => {
|
|
59
83
|
switch (variant) {
|
|
60
84
|
case 'primary':
|
|
@@ -72,7 +96,24 @@
|
|
|
72
96
|
});
|
|
73
97
|
|
|
74
98
|
const isLink = $derived(variant === 'link');
|
|
75
|
-
|
|
99
|
+
|
|
100
|
+
/** Set by an `onclick` that returned a promise, on top of the `loading` prop. */
|
|
101
|
+
let pending = $state(false);
|
|
102
|
+
const busy = $derived(loading || pending);
|
|
103
|
+
const inert = $derived(disabled || busy);
|
|
104
|
+
|
|
105
|
+
const iconClass = $derived(size === 'sm' ? iconSm : iconMd);
|
|
106
|
+
|
|
107
|
+
async function handleClick(event: MouseEvent) {
|
|
108
|
+
const result = onclick?.(event);
|
|
109
|
+
// Anything else is a plain handler: nothing to wait for, nothing to lock.
|
|
110
|
+
if (!(result instanceof Promise)) return;
|
|
111
|
+
pending = true;
|
|
112
|
+
// The handler owns its own errors — catch them there to show a message.
|
|
113
|
+
// All the button needs to know is that the work has settled.
|
|
114
|
+
await result.catch(() => {});
|
|
115
|
+
pending = false;
|
|
116
|
+
}
|
|
76
117
|
</script>
|
|
77
118
|
|
|
78
119
|
<svelte:element
|
|
@@ -81,14 +122,24 @@
|
|
|
81
122
|
type={href ? undefined : type}
|
|
82
123
|
disabled={href ? undefined : inert}
|
|
83
124
|
aria-disabled={href && inert ? 'true' : undefined}
|
|
84
|
-
aria-busy={
|
|
125
|
+
aria-busy={busy ? 'true' : undefined}
|
|
85
126
|
role={href ? 'button' : undefined}
|
|
86
|
-
class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink
|
|
127
|
+
class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink
|
|
128
|
+
? ''
|
|
129
|
+
: iconOnly
|
|
130
|
+
? squares[size]
|
|
131
|
+
: sizes[size]}
|
|
87
132
|
{block ? 'w-full' : ''} {className}"
|
|
88
133
|
{...rest}
|
|
134
|
+
onclick={handleClick}
|
|
89
135
|
>
|
|
90
|
-
{#if
|
|
136
|
+
{#if busy}
|
|
91
137
|
<Spinner size={size === 'sm' ? 'xs' : 'sm'} label="" />
|
|
138
|
+
{:else if icon}
|
|
139
|
+
<span class={iconClass} aria-hidden="true">{@render icon()}</span>
|
|
140
|
+
{/if}
|
|
141
|
+
{@render children?.()}
|
|
142
|
+
{#if iconEnd && !iconOnly}
|
|
143
|
+
<span class={iconClass} aria-hidden="true">{@render iconEnd()}</span>
|
|
92
144
|
{/if}
|
|
93
|
-
{@render children()}
|
|
94
145
|
</svelte:element>
|
|
@@ -3,7 +3,7 @@ import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
|
3
3
|
import { type Tone } from '../../core/tones.js';
|
|
4
4
|
export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
|
|
5
5
|
export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
-
interface Props extends HTMLButtonAttributes {
|
|
6
|
+
interface Props extends Omit<HTMLButtonAttributes, 'onclick'> {
|
|
7
7
|
variant?: ButtonVariant;
|
|
8
8
|
tone?: Tone;
|
|
9
9
|
size?: ButtonSize;
|
|
@@ -14,7 +14,19 @@ interface Props extends HTMLButtonAttributes {
|
|
|
14
14
|
loading?: boolean;
|
|
15
15
|
/** Stretch to the container's full width. */
|
|
16
16
|
block?: boolean;
|
|
17
|
-
|
|
17
|
+
/** Leading icon. The spinner takes its place while the button is busy. */
|
|
18
|
+
icon?: Snippet;
|
|
19
|
+
/** Trailing icon. */
|
|
20
|
+
iconEnd?: Snippet;
|
|
21
|
+
/** Square button with no label — pass an `aria-label`. */
|
|
22
|
+
iconOnly?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Return a promise — a save, a submit, a fetch — and the button spins and
|
|
25
|
+
* locks itself until it settles, without any state in the parent. It
|
|
26
|
+
* unlocks on a rejection too, so catch failures inside the handler.
|
|
27
|
+
*/
|
|
28
|
+
onclick?: (event: MouseEvent) => unknown;
|
|
29
|
+
children?: Snippet;
|
|
18
30
|
}
|
|
19
31
|
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
20
32
|
type Button = ReturnType<typeof Button>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { toneBorderLeft, toneHoverBorder, toneSurface, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { iconLg } from './icon.js';
|
|
5
6
|
|
|
6
7
|
export type CardVariant = 'outline' | 'filled' | 'tinted';
|
|
7
8
|
|
|
@@ -15,6 +16,8 @@
|
|
|
15
16
|
padded?: boolean;
|
|
16
17
|
title?: string;
|
|
17
18
|
eyebrow?: string;
|
|
19
|
+
/** Leading icon above the title. Ignored when you pass `header`. */
|
|
20
|
+
icon?: Snippet;
|
|
18
21
|
header?: Snippet;
|
|
19
22
|
footer?: Snippet;
|
|
20
23
|
children: Snippet;
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
padded = true,
|
|
29
32
|
title,
|
|
30
33
|
eyebrow,
|
|
34
|
+
icon,
|
|
31
35
|
header,
|
|
32
36
|
footer,
|
|
33
37
|
class: className = '',
|
|
@@ -49,11 +53,12 @@
|
|
|
49
53
|
{className}"
|
|
50
54
|
{...rest}
|
|
51
55
|
>
|
|
52
|
-
{#if header || title || eyebrow}
|
|
56
|
+
{#if header || title || eyebrow || icon}
|
|
53
57
|
<div class="flex flex-col gap-1 {padded ? 'px-5 pt-5' : ''}">
|
|
54
58
|
{#if header}
|
|
55
59
|
{@render header()}
|
|
56
60
|
{:else}
|
|
61
|
+
{#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
|
|
57
62
|
{#if eyebrow}
|
|
58
63
|
<span class="font-mono text-xs tracking-wide text-text-muted uppercase">{eyebrow}</span>
|
|
59
64
|
{/if}
|
|
@@ -12,6 +12,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
|
12
12
|
padded?: boolean;
|
|
13
13
|
title?: string;
|
|
14
14
|
eyebrow?: string;
|
|
15
|
+
/** Leading icon above the title. Ignored when you pass `header`. */
|
|
16
|
+
icon?: Snippet;
|
|
15
17
|
header?: Snippet;
|
|
16
18
|
footer?: Snippet;
|
|
17
19
|
children: Snippet;
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
import type { HTMLFieldsetAttributes } from 'svelte/elements';
|
|
4
4
|
import Checkbox from './Checkbox.svelte';
|
|
5
5
|
import type { Tone } from '../../core/tones.js';
|
|
6
|
+
import { iconLg } from './icon.js';
|
|
6
7
|
|
|
7
8
|
export interface CheckboxOption {
|
|
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
|
}
|
|
@@ -76,6 +79,9 @@
|
|
|
76
79
|
{checked ? 'border-text bg-bg-alt' : 'border-hairline hover:border-hairline-strong'}
|
|
77
80
|
{disabled ? 'pointer-events-none opacity-50' : ''}"
|
|
78
81
|
>
|
|
82
|
+
{#if option.icon}
|
|
83
|
+
<span class={iconLg}>{@render option.icon()}</span>
|
|
84
|
+
{/if}
|
|
79
85
|
<Checkbox
|
|
80
86
|
{checked}
|
|
81
87
|
{disabled}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { normaliseHex } from '../../core/color.js';
|
|
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' | 'suffix' | 'type'> {
|
|
9
|
+
/** Bindable `#rrggbb`. Shorthand and alpha are normalised away on blur. */
|
|
10
|
+
value?: string;
|
|
11
|
+
size?: InputSize;
|
|
12
|
+
tone?: Tone;
|
|
13
|
+
invalid?: boolean;
|
|
14
|
+
/** Fixed choices under the field. Empty hides the row. */
|
|
15
|
+
swatches?: string[];
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
value = $bindable('#000000'),
|
|
21
|
+
size = 'md',
|
|
22
|
+
tone = 'brand',
|
|
23
|
+
invalid = false,
|
|
24
|
+
disabled = false,
|
|
25
|
+
swatches = [],
|
|
26
|
+
class: className = '',
|
|
27
|
+
...rest
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const t = useLocale();
|
|
31
|
+
|
|
32
|
+
// The text can be mid-typing (`#a2`) while the swatch needs a real colour,
|
|
33
|
+
// so the swatch follows the last value that parsed rather than the field.
|
|
34
|
+
let text = $state('');
|
|
35
|
+
let editing = $state(false);
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (!editing) text = value;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const resolved = $derived(normaliseHex(text) ?? normaliseHex(value) ?? '#000000');
|
|
42
|
+
|
|
43
|
+
// Same reason as CurrencyInput: at input time the bound `text` is one
|
|
44
|
+
// keystroke behind, so the element is the only current source.
|
|
45
|
+
function onText(event: Event & { currentTarget: HTMLInputElement }) {
|
|
46
|
+
const hex = normaliseHex(event.currentTarget.value);
|
|
47
|
+
if (hex) value = hex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function settle() {
|
|
51
|
+
editing = false;
|
|
52
|
+
value = normaliseHex(text) ?? value;
|
|
53
|
+
text = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function choose(hex: string) {
|
|
57
|
+
value = normaliseHex(hex) ?? value;
|
|
58
|
+
text = value;
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div class="flex w-full flex-col gap-2 {className}">
|
|
63
|
+
<Input
|
|
64
|
+
bind:value={text}
|
|
65
|
+
{size}
|
|
66
|
+
{tone}
|
|
67
|
+
{invalid}
|
|
68
|
+
{disabled}
|
|
69
|
+
spellcheck="false"
|
|
70
|
+
autocapitalize="none"
|
|
71
|
+
placeholder="#000000"
|
|
72
|
+
oninput={onText}
|
|
73
|
+
onfocus={() => (editing = true)}
|
|
74
|
+
onblur={settle}
|
|
75
|
+
class="[&_input]:font-mono [&_input]:uppercase"
|
|
76
|
+
{...rest}
|
|
77
|
+
>
|
|
78
|
+
{#snippet prefix()}
|
|
79
|
+
<span class="relative grid size-5 place-items-center border border-hairline">
|
|
80
|
+
<span class="absolute inset-0" style="background-color: {resolved}"></span>
|
|
81
|
+
<input
|
|
82
|
+
type="color"
|
|
83
|
+
value={resolved}
|
|
84
|
+
{disabled}
|
|
85
|
+
aria-label={t.current.pickColour}
|
|
86
|
+
oninput={(event) => choose(event.currentTarget.value)}
|
|
87
|
+
class="absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
88
|
+
/>
|
|
89
|
+
</span>
|
|
90
|
+
{/snippet}
|
|
91
|
+
</Input>
|
|
92
|
+
|
|
93
|
+
{#if swatches.length}
|
|
94
|
+
<div class="flex flex-wrap gap-1.5">
|
|
95
|
+
{#each swatches as swatch (swatch)}
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
{disabled}
|
|
99
|
+
onclick={() => choose(swatch)}
|
|
100
|
+
title={swatch}
|
|
101
|
+
aria-label={swatch}
|
|
102
|
+
aria-pressed={resolved === normaliseHex(swatch)}
|
|
103
|
+
class="size-6 border transition-transform duration-150 hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current disabled:pointer-events-none disabled:opacity-50
|
|
104
|
+
{resolved === normaliseHex(swatch) ? 'border-text' : 'border-hairline'}"
|
|
105
|
+
style="background-color: {swatch}"
|
|
106
|
+
></button>
|
|
107
|
+
{/each}
|
|
108
|
+
</div>
|
|
109
|
+
{/if}
|
|
110
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { Tone } from '../../core/tones.js';
|
|
3
|
+
import { type InputSize } from './Input.svelte';
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix' | 'type'> {
|
|
5
|
+
/** Bindable `#rrggbb`. Shorthand and alpha are normalised away on blur. */
|
|
6
|
+
value?: string;
|
|
7
|
+
size?: InputSize;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
/** Fixed choices under the field. Empty hides the row. */
|
|
11
|
+
swatches?: string[];
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const ColorInput: import("svelte").Component<Props, {}, "value">;
|
|
15
|
+
type ColorInput = ReturnType<typeof ColorInput>;
|
|
16
|
+
export default ColorInput;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import { anchored } from '../../core/actions/anchor.js';
|
|
3
4
|
import { clickOutside, portal } from '../../core/actions/dismissable.js';
|
|
4
5
|
import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
|
|
5
6
|
import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
|
|
6
7
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
8
|
+
import { iconMd } from './icon.js';
|
|
7
9
|
|
|
8
10
|
export interface ComboboxOption {
|
|
9
11
|
value: string;
|
|
10
12
|
label: string;
|
|
13
|
+
/** Leading icon, 16px. */
|
|
14
|
+
icon?: Snippet;
|
|
11
15
|
/** Second line under the label. */
|
|
12
16
|
description?: string;
|
|
13
17
|
disabled?: boolean;
|
|
@@ -234,6 +238,7 @@
|
|
|
234
238
|
{index === active ? toneSoft[tone] : 'text-text-secondary'}"
|
|
235
239
|
>
|
|
236
240
|
<span class="flex items-center gap-2">
|
|
241
|
+
{#if option.icon}<span class={iconMd}>{@render option.icon()}</span>{/if}
|
|
237
242
|
<span class="min-w-0 flex-1 truncate">{option.label}</span>
|
|
238
243
|
{#if option.value === value}
|
|
239
244
|
<svg
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import { type Tone } from '../../core/tones.js';
|
|
2
3
|
export interface ComboboxOption {
|
|
3
4
|
value: string;
|
|
4
5
|
label: string;
|
|
6
|
+
/** Leading icon, 16px. */
|
|
7
|
+
icon?: Snippet;
|
|
5
8
|
/** Second line under the label. */
|
|
6
9
|
description?: string;
|
|
7
10
|
disabled?: boolean;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
2
4
|
export interface CommandItem {
|
|
3
5
|
id: string;
|
|
4
6
|
label: string;
|
|
7
|
+
/** Leading icon, 16px. */
|
|
8
|
+
icon?: Snippet;
|
|
5
9
|
/** Second line under the label. */
|
|
6
10
|
description?: string;
|
|
7
11
|
/** Items sharing a group are rendered under one heading. */
|
|
@@ -22,6 +26,7 @@
|
|
|
22
26
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
23
27
|
import { toneSoft, type Tone } from '../../core/tones.js';
|
|
24
28
|
import { isTypingTarget } from '../../core/trigger.js';
|
|
29
|
+
import { iconMd } from './icon.js';
|
|
25
30
|
import Kbd from './Kbd.svelte';
|
|
26
31
|
|
|
27
32
|
interface Props {
|
|
@@ -218,6 +223,7 @@
|
|
|
218
223
|
{item.disabled ? 'pointer-events-none opacity-40' : ''}
|
|
219
224
|
{index === active ? toneSoft[tone] : 'text-text-secondary'}"
|
|
220
225
|
>
|
|
226
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
221
227
|
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
222
228
|
<span class="truncate">{item.label}</span>
|
|
223
229
|
{#if item.description}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
export interface CommandItem {
|
|
2
3
|
id: string;
|
|
3
4
|
label: string;
|
|
5
|
+
/** Leading icon, 16px. */
|
|
6
|
+
icon?: Snippet;
|
|
4
7
|
/** Second line under the label. */
|
|
5
8
|
description?: string;
|
|
6
9
|
/** Items sharing a group are rendered under one heading. */
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { formatGrouped, parseGrouped } from '../../core/number.js';
|
|
4
|
+
import type { Tone } from '../../core/tones.js';
|
|
5
|
+
import Input, { type InputSize } from './Input.svelte';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<
|
|
8
|
+
HTMLInputAttributes,
|
|
9
|
+
'size' | 'prefix' | 'suffix' | 'value' | 'min' | 'max'
|
|
10
|
+
> {
|
|
11
|
+
/** Bindable amount. `null` is an empty field, which is not the same as 0. */
|
|
12
|
+
value?: number | null;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Thousands separator. */
|
|
16
|
+
group?: string;
|
|
17
|
+
/** Decimal separator. */
|
|
18
|
+
decimal?: string;
|
|
19
|
+
/** Decimal places the amount settles to when the field loses focus. */
|
|
20
|
+
precision?: number;
|
|
21
|
+
/** Leading mark — `Rp`, `$`, `€`. */
|
|
22
|
+
currency?: string;
|
|
23
|
+
/** Trailing mark instead, for the locales that write it that way. */
|
|
24
|
+
unit?: string;
|
|
25
|
+
size?: InputSize;
|
|
26
|
+
tone?: Tone;
|
|
27
|
+
invalid?: boolean;
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
value = $bindable(null),
|
|
33
|
+
min,
|
|
34
|
+
max,
|
|
35
|
+
group = ',',
|
|
36
|
+
decimal = '.',
|
|
37
|
+
precision = 2,
|
|
38
|
+
currency,
|
|
39
|
+
unit,
|
|
40
|
+
size = 'md',
|
|
41
|
+
tone = 'brand',
|
|
42
|
+
invalid = false,
|
|
43
|
+
disabled = false,
|
|
44
|
+
class: className = '',
|
|
45
|
+
...rest
|
|
46
|
+
}: Props = $props();
|
|
47
|
+
|
|
48
|
+
const options = $derived({ group, decimal, precision });
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What the field shows. Two states on purpose: grouped and padded while the
|
|
52
|
+
* caret is elsewhere, and exactly what was typed while it is not — grouping
|
|
53
|
+
* a number as it is typed moves the caret out from under the fingers.
|
|
54
|
+
*/
|
|
55
|
+
let text = $state('');
|
|
56
|
+
let editing = $state(false);
|
|
57
|
+
|
|
58
|
+
$effect(() => {
|
|
59
|
+
if (editing) return;
|
|
60
|
+
text = value === null || value === undefined ? '' : formatGrouped(value, options);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Read off the element, not off `text`: the binding that feeds `text` has
|
|
64
|
+
// not flushed yet when this fires, so it still holds the previous keystroke.
|
|
65
|
+
function onInput(event: Event & { currentTarget: HTMLInputElement }) {
|
|
66
|
+
const parsed = parseGrouped(event.currentTarget.value, options);
|
|
67
|
+
value = parsed === null ? null : clamp(parsed);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function clamp(amount: number) {
|
|
71
|
+
if (min !== undefined && amount < min) return min;
|
|
72
|
+
if (max !== undefined && amount > max) return max;
|
|
73
|
+
return amount;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function settle() {
|
|
77
|
+
editing = false;
|
|
78
|
+
const parsed = parseGrouped(text, options);
|
|
79
|
+
value = parsed === null ? null : clamp(parsed);
|
|
80
|
+
text = value === null ? '' : formatGrouped(value, options);
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<Input
|
|
85
|
+
bind:value={text}
|
|
86
|
+
{size}
|
|
87
|
+
{tone}
|
|
88
|
+
{invalid}
|
|
89
|
+
{disabled}
|
|
90
|
+
inputmode="decimal"
|
|
91
|
+
oninput={onInput}
|
|
92
|
+
onfocus={() => (editing = true)}
|
|
93
|
+
onblur={settle}
|
|
94
|
+
class={className}
|
|
95
|
+
{...rest}
|
|
96
|
+
>
|
|
97
|
+
{#snippet prefix()}
|
|
98
|
+
{#if currency}
|
|
99
|
+
<span class="font-mono text-xs tracking-wide">{currency}</span>
|
|
100
|
+
{/if}
|
|
101
|
+
{/snippet}
|
|
102
|
+
|
|
103
|
+
{#snippet suffix()}
|
|
104
|
+
{#if unit}
|
|
105
|
+
<span class="font-mono text-xs tracking-wide">{unit}</span>
|
|
106
|
+
{/if}
|
|
107
|
+
{/snippet}
|
|
108
|
+
</Input>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { Tone } from '../../core/tones.js';
|
|
3
|
+
import { type InputSize } from './Input.svelte';
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix' | 'value' | 'min' | 'max'> {
|
|
5
|
+
/** Bindable amount. `null` is an empty field, which is not the same as 0. */
|
|
6
|
+
value?: number | null;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Thousands separator. */
|
|
10
|
+
group?: string;
|
|
11
|
+
/** Decimal separator. */
|
|
12
|
+
decimal?: string;
|
|
13
|
+
/** Decimal places the amount settles to when the field loses focus. */
|
|
14
|
+
precision?: number;
|
|
15
|
+
/** Leading mark — `Rp`, `$`, `€`. */
|
|
16
|
+
currency?: string;
|
|
17
|
+
/** Trailing mark instead, for the locales that write it that way. */
|
|
18
|
+
unit?: string;
|
|
19
|
+
size?: InputSize;
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
invalid?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
}
|
|
24
|
+
declare const CurrencyInput: import("svelte").Component<Props, {}, "value">;
|
|
25
|
+
type CurrencyInput = ReturnType<typeof CurrencyInput>;
|
|
26
|
+
export default CurrencyInput;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { iconLg } from './icon.js';
|
|
3
4
|
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
4
5
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
6
|
import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
|
|
@@ -15,6 +16,8 @@
|
|
|
15
16
|
size?: DrawerSize;
|
|
16
17
|
title?: string;
|
|
17
18
|
description?: string;
|
|
19
|
+
/** Leading icon beside the title. */
|
|
20
|
+
icon?: Snippet;
|
|
18
21
|
dismissible?: boolean;
|
|
19
22
|
footer?: Snippet;
|
|
20
23
|
onclose?: () => void;
|
|
@@ -27,6 +30,7 @@
|
|
|
27
30
|
size = 'md',
|
|
28
31
|
title,
|
|
29
32
|
description,
|
|
33
|
+
icon,
|
|
30
34
|
dismissible = true,
|
|
31
35
|
footer,
|
|
32
36
|
onclose,
|
|
@@ -92,6 +96,7 @@
|
|
|
92
96
|
<div class="flex h-full flex-col">
|
|
93
97
|
{#if title || dismissible}
|
|
94
98
|
<div class="flex items-start gap-4 border-b border-hairline px-5 py-4">
|
|
99
|
+
{#if icon}<span class={iconLg}>{@render icon()}</span>{/if}
|
|
95
100
|
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
96
101
|
{#if title}
|
|
97
102
|
<h2 class="font-heading text-lg font-medium tracking-tight">{title}</h2>
|