@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,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { clampToStep } from '../../core/number.js';
|
|
6
|
+
import { edge } from './lift.js';
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
9
|
+
value?: number;
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
step?: number;
|
|
13
|
+
tone?: Tone;
|
|
14
|
+
invalid?: boolean;
|
|
15
|
+
/** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
|
|
16
|
+
unit?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
value = $bindable(0),
|
|
21
|
+
min = -Infinity,
|
|
22
|
+
max = Infinity,
|
|
23
|
+
step = 1,
|
|
24
|
+
tone = 'brand',
|
|
25
|
+
invalid = false,
|
|
26
|
+
unit,
|
|
27
|
+
disabled = false,
|
|
28
|
+
class: className = '',
|
|
29
|
+
...rest
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const t = useLocale();
|
|
33
|
+
|
|
34
|
+
const clamp = (next: number) => clampToStep(next, min, max, step);
|
|
35
|
+
|
|
36
|
+
function nudge(direction: 1 | -1) {
|
|
37
|
+
value = clamp(value + direction * step);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onKeydown(event: KeyboardEvent) {
|
|
41
|
+
if (event.key === 'ArrowUp') {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
nudge(1);
|
|
44
|
+
} else if (event.key === 'ArrowDown') {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
nudge(-1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const stepper =
|
|
51
|
+
'flex size-7 shrink-0 items-center justify-center rounded text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current';
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<span
|
|
55
|
+
class="inline-flex w-full items-center rounded-xl border bg-bg pr-1 pl-3 {edge} transition-colors duration-150 ease-brand-out
|
|
56
|
+
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
57
|
+
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
58
|
+
>
|
|
59
|
+
<input
|
|
60
|
+
type="number"
|
|
61
|
+
{min}
|
|
62
|
+
{max}
|
|
63
|
+
{step}
|
|
64
|
+
{disabled}
|
|
65
|
+
{value}
|
|
66
|
+
oninput={(event) => (value = clamp(event.currentTarget.valueAsNumber))}
|
|
67
|
+
onkeydown={onKeydown}
|
|
68
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
69
|
+
class="h-10 w-full min-w-0 [appearance:textfield] bg-transparent font-sans text-sm text-text tabular-nums placeholder:text-text-muted focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
70
|
+
{...rest}
|
|
71
|
+
/>
|
|
72
|
+
{#if unit}
|
|
73
|
+
<span class="shrink-0 px-1.5 text-xs text-text-muted">{unit}</span>
|
|
74
|
+
{/if}
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class={stepper}
|
|
78
|
+
onclick={() => nudge(-1)}
|
|
79
|
+
disabled={disabled || value <= min}
|
|
80
|
+
aria-label={t.current.decrease}
|
|
81
|
+
>
|
|
82
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
83
|
+
<path d="M2.5 7h9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
84
|
+
</svg>
|
|
85
|
+
</button>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class={stepper}
|
|
89
|
+
onclick={() => nudge(1)}
|
|
90
|
+
disabled={disabled || value >= max}
|
|
91
|
+
aria-label={t.current.increase}
|
|
92
|
+
>
|
|
93
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
94
|
+
<path d="M7 2.5v9M2.5 7h9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
95
|
+
</svg>
|
|
96
|
+
</button>
|
|
97
|
+
</span>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
4
|
+
value?: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
/** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
|
|
11
|
+
unit?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const NumberInput: import("svelte").Component<Props, {}, "value">;
|
|
14
|
+
type NumberInput = ReturnType<typeof NumberInput>;
|
|
15
|
+
export default NumberInput;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { focusRing, toneRing, toneSolid, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { pageRange } from '../../core/pagination.js';
|
|
6
|
+
import { liftSm } from './lift.js';
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
9
|
+
/** Bindable, 1-based. */
|
|
10
|
+
page?: number;
|
|
11
|
+
/** Total number of pages. */
|
|
12
|
+
total: number;
|
|
13
|
+
/** Pages shown either side of the current one before collapsing to `…`. */
|
|
14
|
+
siblings?: number;
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
onchange?: (page: number) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
page = $bindable(1),
|
|
21
|
+
total,
|
|
22
|
+
siblings = 1,
|
|
23
|
+
tone = 'brand',
|
|
24
|
+
class: className = '',
|
|
25
|
+
onchange,
|
|
26
|
+
...rest
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const t = useLocale();
|
|
30
|
+
|
|
31
|
+
const pages = $derived(pageRange(page, total, siblings));
|
|
32
|
+
|
|
33
|
+
function go(next: number) {
|
|
34
|
+
const clamped = Math.min(total, Math.max(1, next));
|
|
35
|
+
if (clamped === page) return;
|
|
36
|
+
page = clamped;
|
|
37
|
+
onchange?.(clamped);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cell = `inline-flex h-9 min-w-9 items-center justify-center rounded-full px-2 font-sans text-sm font-medium tabular-nums transition-colors duration-150 ease-brand-out disabled:opacity-40 disabled:pointer-events-none ${focusRing}`;
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<nav aria-label={t.current.pagination} class="flex items-center gap-1 {className}" {...rest}>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
|
|
47
|
+
disabled={page <= 1}
|
|
48
|
+
aria-label={t.current.previousPage}
|
|
49
|
+
onclick={() => go(page - 1)}
|
|
50
|
+
>
|
|
51
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
52
|
+
<path d="m10 4-4 4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
53
|
+
</svg>
|
|
54
|
+
</button>
|
|
55
|
+
|
|
56
|
+
{#each pages as item, i (`${item}-${i}`)}
|
|
57
|
+
{#if item === '…'}
|
|
58
|
+
<span class="{cell} text-text-muted">…</span>
|
|
59
|
+
{:else}
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
aria-current={item === page ? 'page' : undefined}
|
|
63
|
+
class="{cell} {toneRing[tone]} {item === page
|
|
64
|
+
? `${toneSolid[tone]} ${liftSm[tone]}`
|
|
65
|
+
: 'text-text-secondary hover:bg-bg-inset'}"
|
|
66
|
+
onclick={() => go(item)}
|
|
67
|
+
>
|
|
68
|
+
{item}
|
|
69
|
+
</button>
|
|
70
|
+
{/if}
|
|
71
|
+
{/each}
|
|
72
|
+
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
|
|
76
|
+
disabled={page >= total}
|
|
77
|
+
aria-label={t.current.nextPage}
|
|
78
|
+
onclick={() => go(page + 1)}
|
|
79
|
+
>
|
|
80
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
81
|
+
<path d="m6 4 4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
82
|
+
</svg>
|
|
83
|
+
</button>
|
|
84
|
+
</nav>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
4
|
+
/** Bindable, 1-based. */
|
|
5
|
+
page?: number;
|
|
6
|
+
/** Total number of pages. */
|
|
7
|
+
total: number;
|
|
8
|
+
/** Pages shown either side of the current one before collapsing to `…`. */
|
|
9
|
+
siblings?: number;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
onchange?: (page: number) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const Pagination: import("svelte").Component<Props, {}, "page">;
|
|
14
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
15
|
+
export default Pagination;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { passwordScore } from '../../core/password.js';
|
|
6
|
+
import { edge } from './lift.js';
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type'> {
|
|
9
|
+
value?: string;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
invalid?: boolean;
|
|
12
|
+
/** Draws a four-segment strength meter under the field. */
|
|
13
|
+
strength?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
value = $bindable(''),
|
|
18
|
+
tone = 'brand',
|
|
19
|
+
invalid = false,
|
|
20
|
+
strength = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
class: className = '',
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
const t = useLocale();
|
|
27
|
+
|
|
28
|
+
let visible = $state(false);
|
|
29
|
+
|
|
30
|
+
const score = $derived(passwordScore(value));
|
|
31
|
+
|
|
32
|
+
const bars = ['bg-danger', 'bg-danger', 'bg-warning', 'bg-info', 'bg-success'];
|
|
33
|
+
const texts = ['text-danger', 'text-danger', 'text-warning', 'text-info', 'text-success'];
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div class="flex w-full flex-col gap-2">
|
|
37
|
+
<span
|
|
38
|
+
class="inline-flex w-full items-center gap-2 rounded-xl border bg-bg pr-1 pl-3 {edge} transition-colors duration-150 ease-brand-out
|
|
39
|
+
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
40
|
+
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
41
|
+
>
|
|
42
|
+
<input
|
|
43
|
+
bind:value
|
|
44
|
+
{disabled}
|
|
45
|
+
type={visible ? 'text' : 'password'}
|
|
46
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
47
|
+
class="h-10 w-full min-w-0 bg-transparent font-sans text-sm text-text placeholder:text-text-muted focus:outline-none"
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onclick={() => (visible = !visible)}
|
|
53
|
+
aria-label={visible ? t.current.hidePassword : t.current.showPassword}
|
|
54
|
+
aria-pressed={visible}
|
|
55
|
+
class="shrink-0 rounded p-1.5 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
56
|
+
>
|
|
57
|
+
{#if visible}
|
|
58
|
+
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
59
|
+
<path
|
|
60
|
+
d="M2.5 10S5.5 4.5 10 4.5 17.5 10 17.5 10 14.5 15.5 10 15.5 2.5 10 2.5 10Z"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
stroke-width="1.4"
|
|
63
|
+
/>
|
|
64
|
+
<circle cx="10" cy="10" r="2.4" stroke="currentColor" stroke-width="1.4" />
|
|
65
|
+
<path
|
|
66
|
+
d="m3.5 16.5 13-13"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
stroke-width="1.4"
|
|
69
|
+
stroke-linecap="round"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
{:else}
|
|
73
|
+
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
74
|
+
<path
|
|
75
|
+
d="M2.5 10S5.5 4.5 10 4.5 17.5 10 17.5 10 14.5 15.5 10 15.5 2.5 10 2.5 10Z"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
stroke-width="1.4"
|
|
78
|
+
/>
|
|
79
|
+
<circle cx="10" cy="10" r="2.4" stroke="currentColor" stroke-width="1.4" />
|
|
80
|
+
</svg>
|
|
81
|
+
{/if}
|
|
82
|
+
</button>
|
|
83
|
+
</span>
|
|
84
|
+
|
|
85
|
+
{#if strength}
|
|
86
|
+
<div class="flex items-center gap-3">
|
|
87
|
+
<div class="flex flex-1 gap-1" aria-hidden="true">
|
|
88
|
+
{#each [0, 1, 2, 3] as segment (segment)}
|
|
89
|
+
<span
|
|
90
|
+
class="h-1 flex-1 rounded-full transition-colors duration-200 {segment < score
|
|
91
|
+
? bars[score]
|
|
92
|
+
: 'bg-bg-inset'}"
|
|
93
|
+
></span>
|
|
94
|
+
{/each}
|
|
95
|
+
</div>
|
|
96
|
+
<span class="text-xs font-medium {value ? texts[score] : 'text-text-muted'}">
|
|
97
|
+
{value ? t.current.passwordStrength[score] : '—'}
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type'> {
|
|
4
|
+
value?: string;
|
|
5
|
+
tone?: Tone;
|
|
6
|
+
invalid?: boolean;
|
|
7
|
+
/** Draws a four-segment strength meter under the field. */
|
|
8
|
+
strength?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const PasswordInput: import("svelte").Component<Props, {}, "value">;
|
|
11
|
+
type PasswordInput = ReturnType<typeof PasswordInput>;
|
|
12
|
+
export default PasswordInput;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { anchored, type Placement } from '../../core/actions/anchor.js';
|
|
4
|
+
import { clickOutside, focusTrap, portal } from '../../core/actions/dismissable.js';
|
|
5
|
+
import { HOVER_GRACE, describeTrigger, popupTrigger } from '../../core/trigger.js';
|
|
6
|
+
import { soft } from './lift.js';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
trigger: Snippet;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
open?: boolean;
|
|
12
|
+
placement?: Placement;
|
|
13
|
+
offset?: number;
|
|
14
|
+
title?: string;
|
|
15
|
+
/** `'hover'` also opens on focus, for read-only content. */
|
|
16
|
+
on?: 'click' | 'hover';
|
|
17
|
+
/** Keep focus where it is — right for hover popovers and hover cards. */
|
|
18
|
+
trapFocus?: boolean;
|
|
19
|
+
class?: string;
|
|
20
|
+
triggerClass?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
trigger,
|
|
25
|
+
children,
|
|
26
|
+
open = $bindable(false),
|
|
27
|
+
placement = 'bottom',
|
|
28
|
+
offset = 8,
|
|
29
|
+
title,
|
|
30
|
+
on = 'click',
|
|
31
|
+
trapFocus = true,
|
|
32
|
+
class: className = '',
|
|
33
|
+
triggerClass = ''
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
let anchorEl: HTMLElement | null = $state(null);
|
|
37
|
+
/** Held so the panel itself can cancel the hover close it inherited. */
|
|
38
|
+
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
39
|
+
|
|
40
|
+
$effect(() => describeTrigger(anchorEl, 'dialog', open));
|
|
41
|
+
|
|
42
|
+
function onKeydown(event: KeyboardEvent) {
|
|
43
|
+
if (event.key !== 'Escape') return;
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
open = false;
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<span
|
|
51
|
+
bind:this={anchorEl}
|
|
52
|
+
use:popupTrigger={{
|
|
53
|
+
toggle: () => (open = !open),
|
|
54
|
+
open: () => (open = true),
|
|
55
|
+
close: () => (open = false),
|
|
56
|
+
on
|
|
57
|
+
}}
|
|
58
|
+
class="inline-flex {triggerClass}"
|
|
59
|
+
>
|
|
60
|
+
{@render trigger()}
|
|
61
|
+
</span>
|
|
62
|
+
|
|
63
|
+
{#if open}
|
|
64
|
+
<div
|
|
65
|
+
use:portal
|
|
66
|
+
use:anchored={{ anchor: anchorEl, placement, offset }}
|
|
67
|
+
use:clickOutside={{
|
|
68
|
+
onoutside: () => (open = false),
|
|
69
|
+
ignore: [anchorEl],
|
|
70
|
+
enabled: on === 'click'
|
|
71
|
+
}}
|
|
72
|
+
use:focusTrap={{ enabled: trapFocus, autofocus: trapFocus, restore: trapFocus }}
|
|
73
|
+
onkeydown={onKeydown}
|
|
74
|
+
onpointerenter={() => clearTimeout(closeTimer)}
|
|
75
|
+
onpointerleave={() => {
|
|
76
|
+
if (on === 'hover') closeTimer = setTimeout(() => (open = false), HOVER_GRACE);
|
|
77
|
+
}}
|
|
78
|
+
role="dialog"
|
|
79
|
+
aria-label={title}
|
|
80
|
+
tabindex="-1"
|
|
81
|
+
class="z-50 flex max-w-xs flex-col gap-2 rounded-2xl border border-hairline bg-bg p-4 font-sans {soft} {className}"
|
|
82
|
+
>
|
|
83
|
+
{#if title}
|
|
84
|
+
<p class="font-heading text-sm font-semibold text-text">{title}</p>
|
|
85
|
+
{/if}
|
|
86
|
+
<div class="text-[13px] leading-relaxed text-text-secondary">
|
|
87
|
+
{@render children()}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type Placement } from '../../core/actions/anchor.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
trigger: Snippet;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
placement?: Placement;
|
|
8
|
+
offset?: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
/** `'hover'` also opens on focus, for read-only content. */
|
|
11
|
+
on?: 'click' | 'hover';
|
|
12
|
+
/** Keep focus where it is — right for hover popovers and hover cards. */
|
|
13
|
+
trapFocus?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
triggerClass?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const Popover: import("svelte").Component<Props, {}, "open">;
|
|
18
|
+
type Popover = ReturnType<typeof Popover>;
|
|
19
|
+
export default Popover;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { toneBorder, toneSoft, toneSolid, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { edge, soft } from './lift.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
name: string;
|
|
9
|
+
/** Already formatted — the component does not do currency. */
|
|
10
|
+
price: string;
|
|
11
|
+
/** `/ month`, `per seat`, whatever follows the figure. */
|
|
12
|
+
period?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
/** One line each. Rendered with a tick. */
|
|
15
|
+
features?: string[];
|
|
16
|
+
/** Pill in the top corner, e.g. "Most popular". */
|
|
17
|
+
badge?: string;
|
|
18
|
+
/** Ring, lift and a tinted header — for the plan you want chosen. */
|
|
19
|
+
featured?: boolean;
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
/** The button. */
|
|
22
|
+
action?: Snippet;
|
|
23
|
+
/** Extra content under the feature list. */
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
name,
|
|
29
|
+
price,
|
|
30
|
+
period,
|
|
31
|
+
description,
|
|
32
|
+
features = [],
|
|
33
|
+
badge,
|
|
34
|
+
featured = false,
|
|
35
|
+
tone = 'brand',
|
|
36
|
+
action,
|
|
37
|
+
class: className = '',
|
|
38
|
+
children,
|
|
39
|
+
...rest
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<div
|
|
44
|
+
class="flex flex-col rounded-[28px] bg-bg {featured
|
|
45
|
+
? `border-2 ${soft} ${toneBorder[tone]}`
|
|
46
|
+
: `border border-hairline ${edge}`} {className}"
|
|
47
|
+
{...rest}
|
|
48
|
+
>
|
|
49
|
+
<div class="flex flex-col gap-4 p-6">
|
|
50
|
+
<div class="flex items-start justify-between gap-3">
|
|
51
|
+
<h3 class="font-heading text-base font-semibold text-text">{name}</h3>
|
|
52
|
+
{#if badge}
|
|
53
|
+
<span
|
|
54
|
+
class="shrink-0 rounded-full px-2.5 py-0.5 font-sans text-[11px] font-medium {featured
|
|
55
|
+
? toneSolid[tone]
|
|
56
|
+
: toneSoft[tone]}"
|
|
57
|
+
>
|
|
58
|
+
{badge}
|
|
59
|
+
</span>
|
|
60
|
+
{/if}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="flex items-baseline gap-1.5">
|
|
64
|
+
<span class="font-heading text-4xl font-semibold tracking-tight text-text tabular-nums">
|
|
65
|
+
{price}
|
|
66
|
+
</span>
|
|
67
|
+
{#if period}
|
|
68
|
+
<span class="font-sans text-sm text-text-muted">{period}</span>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{#if description}
|
|
73
|
+
<p class="font-sans text-sm leading-relaxed text-text-secondary">{description}</p>
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{#if features.length}
|
|
78
|
+
<ul class="flex flex-1 flex-col gap-3 border-t border-hairline p-6">
|
|
79
|
+
{#each features as feature (feature)}
|
|
80
|
+
<li class="flex items-start gap-3 font-sans text-sm text-text-secondary">
|
|
81
|
+
<span
|
|
82
|
+
class="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full {toneSoft[
|
|
83
|
+
tone
|
|
84
|
+
]}"
|
|
85
|
+
>
|
|
86
|
+
<svg class="size-2.5" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
87
|
+
<path
|
|
88
|
+
d="m3 8 3.2 3.2L13 4.4"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
stroke-width="2.4"
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
/>
|
|
94
|
+
</svg>
|
|
95
|
+
</span>
|
|
96
|
+
<span class="leading-snug">{feature}</span>
|
|
97
|
+
</li>
|
|
98
|
+
{/each}
|
|
99
|
+
</ul>
|
|
100
|
+
{/if}
|
|
101
|
+
|
|
102
|
+
{#if children}
|
|
103
|
+
<div class="px-6 pb-2">{@render children()}</div>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
{#if action}
|
|
107
|
+
<div class="p-6 pt-2">{@render action()}</div>
|
|
108
|
+
{/if}
|
|
109
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Already formatted — the component does not do currency. */
|
|
7
|
+
price: string;
|
|
8
|
+
/** `/ month`, `per seat`, whatever follows the figure. */
|
|
9
|
+
period?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
/** One line each. Rendered with a tick. */
|
|
12
|
+
features?: string[];
|
|
13
|
+
/** Pill in the top corner, e.g. "Most popular". */
|
|
14
|
+
badge?: string;
|
|
15
|
+
/** Ring, lift and a tinted header — for the plan you want chosen. */
|
|
16
|
+
featured?: boolean;
|
|
17
|
+
tone?: Tone;
|
|
18
|
+
/** The button. */
|
|
19
|
+
action?: Snippet;
|
|
20
|
+
/** Extra content under the feature list. */
|
|
21
|
+
children?: Snippet;
|
|
22
|
+
}
|
|
23
|
+
declare const PricingCard: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type PricingCard = ReturnType<typeof PricingCard>;
|
|
25
|
+
export default PricingCard;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneFill, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { percentOf } from '../../core/number.js';
|
|
5
|
+
|
|
6
|
+
export type ProgressSize = 'sm' | 'md' | 'lg';
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
value?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
size?: ProgressSize;
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Prints the percentage on the right of the label row. */
|
|
15
|
+
showValue?: boolean;
|
|
16
|
+
/** Ignores `value` and runs a looping sweep. */
|
|
17
|
+
indeterminate?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
value = 0,
|
|
22
|
+
max = 100,
|
|
23
|
+
tone = 'brand',
|
|
24
|
+
size = 'md',
|
|
25
|
+
label,
|
|
26
|
+
showValue = false,
|
|
27
|
+
indeterminate = false,
|
|
28
|
+
class: className = '',
|
|
29
|
+
...rest
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const sizes: Record<ProgressSize, string> = { sm: 'h-1.5', md: 'h-2', lg: 'h-3' };
|
|
33
|
+
const pct = $derived(percentOf(value, 0, max));
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div class="flex w-full flex-col gap-2 {className}" {...rest}>
|
|
37
|
+
{#if label || showValue}
|
|
38
|
+
<div class="flex items-baseline justify-between gap-4 font-sans text-sm">
|
|
39
|
+
{#if label}<span class="text-text-secondary">{label}</span>{/if}
|
|
40
|
+
{#if showValue}
|
|
41
|
+
<span class="text-xs font-medium text-text-muted tabular-nums">{Math.round(pct)}%</span>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
44
|
+
{/if}
|
|
45
|
+
<div
|
|
46
|
+
role="progressbar"
|
|
47
|
+
aria-valuenow={indeterminate ? undefined : Math.round(pct)}
|
|
48
|
+
aria-valuemin={0}
|
|
49
|
+
aria-valuemax={100}
|
|
50
|
+
aria-label={label}
|
|
51
|
+
class="w-full overflow-hidden rounded-full bg-bg-inset {sizes[size]}"
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
class="h-full rounded-full transition-[width] duration-300 ease-brand {toneFill[tone]}
|
|
55
|
+
{indeterminate ? 'w-1/3 animate-pulse' : ''}"
|
|
56
|
+
style={indeterminate ? undefined : `width:${pct}%`}
|
|
57
|
+
></div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
export type ProgressSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
value?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
size?: ProgressSize;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Prints the percentage on the right of the label row. */
|
|
11
|
+
showValue?: boolean;
|
|
12
|
+
/** Ignores `value` and runs a looping sweep. */
|
|
13
|
+
indeterminate?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare const Progress: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type Progress = ReturnType<typeof Progress>;
|
|
17
|
+
export default Progress;
|