@nqmcreative/ui 0.2.2 → 0.3.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 +15 -15
- package/cli/index.mjs +81 -42
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/toast.svelte.d.ts +3 -0
- package/dist/core/trigger.d.ts +6 -0
- package/dist/core/trigger.js +13 -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 +62 -11
- package/dist/styles/matte/Button.svelte.d.ts +14 -2
- package/dist/styles/matte/Card.svelte +9 -4
- 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/Combobox.svelte +7 -2
- package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
- package/dist/styles/matte/CommandPalette.svelte +38 -7
- package/dist/styles/matte/CommandPalette.svelte.d.ts +8 -0
- package/dist/styles/matte/DatePicker.svelte +2 -2
- package/dist/styles/matte/Drawer.svelte +7 -2
- package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
- package/dist/styles/matte/Dropzone.svelte +1 -1
- package/dist/styles/matte/FeatureCard.svelte +1 -1
- package/dist/styles/matte/Footer.svelte +6 -2
- package/dist/styles/matte/Footer.svelte.d.ts +2 -0
- package/dist/styles/matte/Input.svelte +4 -4
- package/dist/styles/matte/Link.svelte +9 -0
- package/dist/styles/matte/Link.svelte.d.ts +4 -0
- package/dist/styles/matte/MenuItem.svelte +3 -2
- package/dist/styles/matte/Modal.svelte +8 -3
- 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/PricingCard.svelte +19 -11
- 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/SegmentedControl.svelte +17 -3
- package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/matte/Select.svelte +1 -1
- package/dist/styles/matte/Sidebar.svelte +76 -26
- package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
- package/dist/styles/matte/Stat.svelte +8 -1
- package/dist/styles/matte/Stat.svelte.d.ts +3 -0
- package/dist/styles/matte/StatsBand.svelte +1 -1
- package/dist/styles/matte/Steps.svelte +7 -0
- package/dist/styles/matte/Steps.svelte.d.ts +3 -0
- package/dist/styles/matte/Table.svelte +154 -98
- 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/Testimonial.svelte +2 -2
- package/dist/styles/matte/Textarea.svelte +1 -1
- 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 +1 -3
- package/dist/styles/matte/index.js +0 -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/Combobox.svelte +5 -0
- package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
- package/dist/styles/paper/CommandPalette.svelte +31 -2
- package/dist/styles/paper/CommandPalette.svelte.d.ts +8 -0
- package/dist/styles/paper/Drawer.svelte +5 -0
- package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
- package/dist/styles/paper/Footer.svelte +6 -2
- package/dist/styles/paper/Footer.svelte.d.ts +2 -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 +14 -1
- 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/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/SegmentedControl.svelte +16 -2
- package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/paper/Sidebar.svelte +76 -26
- package/dist/styles/paper/Sidebar.svelte.d.ts +15 -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/Toaster.svelte +3 -0
- package/dist/styles/paper/fonts.css +34 -7
- package/dist/styles/paper/icon.d.ts +21 -0
- package/dist/styles/paper/icon.js +21 -0
- package/dist/styles/paper/index.d.ts +1 -3
- package/dist/styles/paper/index.js +0 -2
- package/dist/styles/paper/theme.css +11 -8
- 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/Combobox.svelte +5 -0
- package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
- package/dist/styles/sprout/CommandPalette.svelte +31 -2
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +8 -0
- package/dist/styles/sprout/Drawer.svelte +5 -0
- package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
- package/dist/styles/sprout/Footer.svelte +6 -2
- package/dist/styles/sprout/Footer.svelte.d.ts +2 -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 +14 -1
- 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/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/SegmentedControl.svelte +16 -2
- package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/sprout/Sidebar.svelte +76 -26
- package/dist/styles/sprout/Sidebar.svelte.d.ts +15 -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/Toaster.svelte +3 -0
- package/dist/styles/sprout/fonts.css +7 -8
- package/dist/styles/sprout/icon.d.ts +21 -0
- package/dist/styles/sprout/icon.js +21 -0
- package/dist/styles/sprout/index.d.ts +1 -3
- package/dist/styles/sprout/index.js +0 -2
- package/dist/styles/sprout/theme.css +6 -6
- package/package.json +2 -26
- package/registry.json +3 -36
- 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,8 @@
|
|
|
2
2
|
export interface NavItem {
|
|
3
3
|
label: string;
|
|
4
4
|
href?: string;
|
|
5
|
+
/** Leading icon, 16px. */
|
|
6
|
+
icon?: Snippet;
|
|
5
7
|
/** Marks the current page — also sets `aria-current`. */
|
|
6
8
|
active?: boolean;
|
|
7
9
|
disabled?: boolean;
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
<script lang="ts">
|
|
16
18
|
import type { Snippet } from 'svelte';
|
|
17
19
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
20
|
+
import { iconMd } from './icon.js';
|
|
18
21
|
import { focusRing, toneRing, toneText, type Tone } from '../../core/tones.js';
|
|
19
22
|
import Drawer from './Drawer.svelte';
|
|
20
23
|
import Dropdown from './Dropdown.svelte';
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
? toneText[tone]
|
|
83
86
|
: 'text-text-secondary hover:text-text'}"
|
|
84
87
|
>
|
|
88
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
85
89
|
{item.label}
|
|
86
90
|
<svg class="size-3" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
87
91
|
<path
|
|
@@ -94,7 +98,12 @@
|
|
|
94
98
|
</button>
|
|
95
99
|
{/snippet}
|
|
96
100
|
{#each item.items as child (child.label)}
|
|
97
|
-
<MenuItem
|
|
101
|
+
<MenuItem
|
|
102
|
+
href={child.href}
|
|
103
|
+
disabled={child.disabled}
|
|
104
|
+
selected={child.active}
|
|
105
|
+
icon={child.icon}
|
|
106
|
+
>
|
|
98
107
|
{child.label}
|
|
99
108
|
</MenuItem>
|
|
100
109
|
{/each}
|
|
@@ -109,6 +118,7 @@
|
|
|
109
118
|
? 'pointer-events-none opacity-50'
|
|
110
119
|
: ''}"
|
|
111
120
|
>
|
|
121
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
112
122
|
{item.label}
|
|
113
123
|
{#if item.badge !== undefined}
|
|
114
124
|
<span class="font-mono text-[11px] text-text-muted">{item.badge}</span>
|
|
@@ -154,7 +164,10 @@
|
|
|
154
164
|
? toneText[tone]
|
|
155
165
|
: 'text-text-secondary'} {item.disabled ? 'pointer-events-none opacity-50' : ''}"
|
|
156
166
|
>
|
|
157
|
-
|
|
167
|
+
<span class="flex min-w-0 items-center gap-2.5">
|
|
168
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
169
|
+
{item.label}
|
|
170
|
+
</span>
|
|
158
171
|
{#if item.badge !== undefined}
|
|
159
172
|
<span class="font-mono text-[11px] text-text-muted">{item.badge}</span>
|
|
160
173
|
{/if}
|
|
@@ -166,9 +179,10 @@
|
|
|
166
179
|
<a
|
|
167
180
|
href={child.href}
|
|
168
181
|
onclick={() => (menuOpen = false)}
|
|
169
|
-
class="
|
|
182
|
+
class="flex items-center gap-2.5 py-2 font-sans text-sm text-text-muted"
|
|
170
183
|
>
|
|
171
|
-
{child.
|
|
184
|
+
{#if child.icon}<span class={iconMd}>{@render child.icon()}</span
|
|
185
|
+
>{/if}{child.label}
|
|
172
186
|
</a>
|
|
173
187
|
</li>
|
|
174
188
|
{/each}
|
|
@@ -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',
|
|
@@ -45,7 +49,7 @@
|
|
|
45
49
|
: 'border border-hairline'} {className}"
|
|
46
50
|
{...rest}
|
|
47
51
|
>
|
|
48
|
-
<div class="flex flex-col gap-4 p-
|
|
52
|
+
<div class="flex flex-col gap-4 p-5 {featured ? toneSoft[tone] : ''}">
|
|
49
53
|
<div class="flex items-start justify-between gap-3">
|
|
50
54
|
<h3 class="font-heading text-lg font-medium tracking-tight {featured ? '' : 'text-text'}">
|
|
51
55
|
{name}
|
|
@@ -80,17 +84,21 @@
|
|
|
80
84
|
</div>
|
|
81
85
|
|
|
82
86
|
{#if features.length}
|
|
83
|
-
<ul class="flex flex-1 flex-col gap-3 border-t border-hairline p-
|
|
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}
|
|
@@ -102,6 +110,6 @@
|
|
|
102
110
|
{/if}
|
|
103
111
|
|
|
104
112
|
{#if action}
|
|
105
|
-
<div class="p-
|
|
113
|
+
<div class="p-5 pt-2">{@render action()}</div>
|
|
106
114
|
{/if}
|
|
107
115
|
</div>
|
|
@@ -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}
|
|
@@ -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 = '',
|
|
@@ -41,7 +48,12 @@
|
|
|
41
48
|
|
|
42
49
|
const sizes: Record<SegmentedSize, string> = {
|
|
43
50
|
sm: 'h-8 px-3 text-[13px]',
|
|
44
|
-
md: 'h-
|
|
51
|
+
md: 'h-9 px-3.5 text-sm'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const squares: Record<SegmentedSize, string> = {
|
|
55
|
+
sm: 'size-8 text-[13px]',
|
|
56
|
+
md: 'size-9 text-sm'
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
function select(option: SegmentOption) {
|
|
@@ -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;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<select
|
|
36
36
|
bind:value
|
|
37
37
|
aria-invalid={invalid ? 'true' : undefined}
|
|
38
|
-
class="h-
|
|
38
|
+
class="h-10 w-full appearance-none border bg-bg py-0 pr-10 pl-3.5 font-sans text-[15px] text-text transition-colors duration-150 ease-brand-out focus:outline-none disabled:pointer-events-none disabled:opacity-50
|
|
39
39
|
{invalid ? 'border-danger' : `border-hairline ${toneFocusBorder[tone]}`} {className}"
|
|
40
40
|
{...rest}
|
|
41
41
|
>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export type SidebarVariant = 'plain' | 'filled' | 'floating';
|
|
5
|
+
|
|
2
6
|
export interface SidebarItem {
|
|
3
7
|
/** Stable id used for the active state. Falls back to `href`. */
|
|
4
8
|
id?: string;
|
|
@@ -6,10 +10,21 @@
|
|
|
6
10
|
href?: string;
|
|
7
11
|
disabled?: boolean;
|
|
8
12
|
badge?: string | number;
|
|
13
|
+
/**
|
|
14
|
+
* Leading glyph, 16px. The one thing a collapsed rail still shows, so
|
|
15
|
+
* an item that has to survive `collapsed` needs one.
|
|
16
|
+
*/
|
|
17
|
+
icon?: Snippet;
|
|
9
18
|
/** Nested links — the parent becomes a collapsible group. */
|
|
10
19
|
items?: SidebarItem[];
|
|
11
20
|
/** Open the group on first render. */
|
|
12
21
|
open?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* `false` pins a group open: the parent stops being a button and the
|
|
24
|
+
* children are always on show. For navigation a reader should not be
|
|
25
|
+
* able to lose.
|
|
26
|
+
*/
|
|
27
|
+
collapsible?: boolean;
|
|
13
28
|
}
|
|
14
29
|
|
|
15
30
|
export interface SidebarSection {
|
|
@@ -20,8 +35,8 @@
|
|
|
20
35
|
</script>
|
|
21
36
|
|
|
22
37
|
<script lang="ts">
|
|
23
|
-
import type { Snippet } from 'svelte';
|
|
24
38
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
39
|
+
import { iconMd } from './icon.js';
|
|
25
40
|
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
26
41
|
|
|
27
42
|
interface Props {
|
|
@@ -32,6 +47,8 @@
|
|
|
32
47
|
collapsed?: boolean;
|
|
33
48
|
/** Show the collapse button. */
|
|
34
49
|
collapsible?: boolean;
|
|
50
|
+
/** Surface treatment: bare, the framed default, or a detached card. */
|
|
51
|
+
variant?: SidebarVariant;
|
|
35
52
|
tone?: Tone;
|
|
36
53
|
header?: Snippet;
|
|
37
54
|
footer?: Snippet;
|
|
@@ -44,6 +61,7 @@
|
|
|
44
61
|
value = $bindable(''),
|
|
45
62
|
collapsed = $bindable(false),
|
|
46
63
|
collapsible = true,
|
|
64
|
+
variant = 'filled',
|
|
47
65
|
tone = 'brand',
|
|
48
66
|
header,
|
|
49
67
|
footer,
|
|
@@ -59,7 +77,8 @@
|
|
|
59
77
|
let opened = $state<Record<string, boolean>>({});
|
|
60
78
|
|
|
61
79
|
const isOpen = (item: SidebarItem) =>
|
|
62
|
-
|
|
80
|
+
item.collapsible === false ||
|
|
81
|
+
(opened[keyOf(item)] ?? (item.open || item.items!.some((child) => keyOf(child) === value)));
|
|
63
82
|
|
|
64
83
|
function toggle(item: SidebarItem) {
|
|
65
84
|
opened = { ...opened, [keyOf(item)]: !isOpen(item) };
|
|
@@ -71,13 +90,50 @@
|
|
|
71
90
|
onnavigate?.(item);
|
|
72
91
|
}
|
|
73
92
|
|
|
93
|
+
const surfaces: Record<SidebarVariant, string> = {
|
|
94
|
+
plain: 'h-full bg-transparent',
|
|
95
|
+
filled: 'h-full border-r border-hairline bg-bg',
|
|
96
|
+
floating: 'm-3 h-[calc(100%-1.5rem)] border border-hairline bg-bg'
|
|
97
|
+
};
|
|
98
|
+
|
|
74
99
|
const row =
|
|
75
100
|
'flex w-full items-center gap-3 px-3 py-2 text-left font-sans text-sm transition-colors duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-40';
|
|
76
101
|
</script>
|
|
77
102
|
|
|
103
|
+
<!-- Nested links. One copy, rendered by both kinds of group. -->
|
|
104
|
+
{#snippet subitems(item: SidebarItem)}
|
|
105
|
+
<div class="ml-5 flex flex-col gap-0.5 border-l border-hairline pl-1">
|
|
106
|
+
{#each item.items as child (keyOf(child))}
|
|
107
|
+
<a
|
|
108
|
+
href={child.href}
|
|
109
|
+
onclick={() => go(child)}
|
|
110
|
+
aria-current={value === keyOf(child) ? 'page' : undefined}
|
|
111
|
+
class="{row} {focusRing} {toneRing[tone]} {value === keyOf(child)
|
|
112
|
+
? `${toneSoft[tone]} font-medium`
|
|
113
|
+
: 'text-text-muted hover:bg-bg-inset hover:text-text'} {child.disabled
|
|
114
|
+
? 'pointer-events-none opacity-40'
|
|
115
|
+
: ''}"
|
|
116
|
+
>
|
|
117
|
+
{@render glyph(child)}
|
|
118
|
+
<span class="min-w-0 flex-1 truncate">{child.label}</span>
|
|
119
|
+
{#if child.badge !== undefined}
|
|
120
|
+
<span class="shrink-0 font-mono text-[11px]">{child.badge}</span>
|
|
121
|
+
{/if}
|
|
122
|
+
</a>
|
|
123
|
+
{/each}
|
|
124
|
+
</div>
|
|
125
|
+
{/snippet}
|
|
126
|
+
|
|
127
|
+
<!-- One box for every glyph, so rows line up whether or not an item has one. -->
|
|
128
|
+
{#snippet glyph(item: SidebarItem)}
|
|
129
|
+
{#if item.icon}
|
|
130
|
+
<span class={iconMd}>{@render item.icon()}</span>
|
|
131
|
+
{/if}
|
|
132
|
+
{/snippet}
|
|
133
|
+
|
|
78
134
|
<aside
|
|
79
|
-
class="flex
|
|
80
|
-
{collapsed ? 'w-16' : 'w-60'} {className}"
|
|
135
|
+
class="flex shrink-0 flex-col transition-[width] duration-200 ease-brand-out
|
|
136
|
+
{surfaces[variant]} {collapsed ? 'w-16' : 'w-60'} {className}"
|
|
81
137
|
>
|
|
82
138
|
{#if header}
|
|
83
139
|
<div class="flex h-16 shrink-0 items-center gap-3 border-b border-hairline px-3">
|
|
@@ -97,16 +153,25 @@
|
|
|
97
153
|
{/if}
|
|
98
154
|
|
|
99
155
|
{#each section.items as item (keyOf(item))}
|
|
100
|
-
{#if item.items?.length}
|
|
156
|
+
{#if item.items?.length && item.collapsible === false}
|
|
157
|
+
<div class="{row} {collapsed ? 'justify-center' : ''} font-medium text-text-muted">
|
|
158
|
+
{@render glyph(item)}
|
|
159
|
+
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
160
|
+
</div>
|
|
161
|
+
{#if isOpen(item) && !collapsed}
|
|
162
|
+
{@render subitems(item)}
|
|
163
|
+
{/if}
|
|
164
|
+
{:else if item.items?.length}
|
|
101
165
|
<button
|
|
102
166
|
type="button"
|
|
103
167
|
onclick={() => toggle(item)}
|
|
104
168
|
aria-expanded={isOpen(item)}
|
|
105
169
|
title={collapsed ? item.label : undefined}
|
|
106
|
-
class="{row} {focusRing} {toneRing[
|
|
170
|
+
class="{row} {collapsed ? 'justify-center' : ''} {focusRing} {toneRing[
|
|
107
171
|
tone
|
|
108
172
|
]} text-text-secondary hover:bg-bg-inset hover:text-text"
|
|
109
173
|
>
|
|
174
|
+
{@render glyph(item)}
|
|
110
175
|
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
111
176
|
{#if !collapsed}
|
|
112
177
|
<svg
|
|
@@ -128,25 +193,7 @@
|
|
|
128
193
|
</button>
|
|
129
194
|
|
|
130
195
|
{#if isOpen(item) && !collapsed}
|
|
131
|
-
|
|
132
|
-
{#each item.items as child (keyOf(child))}
|
|
133
|
-
<a
|
|
134
|
-
href={child.href}
|
|
135
|
-
onclick={() => go(child)}
|
|
136
|
-
aria-current={value === keyOf(child) ? 'page' : undefined}
|
|
137
|
-
class="{row} {focusRing} {toneRing[tone]} {value === keyOf(child)
|
|
138
|
-
? `${toneSoft[tone]} font-medium`
|
|
139
|
-
: 'text-text-muted hover:bg-bg-inset hover:text-text'} {child.disabled
|
|
140
|
-
? 'pointer-events-none opacity-40'
|
|
141
|
-
: ''}"
|
|
142
|
-
>
|
|
143
|
-
<span class="min-w-0 flex-1 truncate">{child.label}</span>
|
|
144
|
-
{#if child.badge !== undefined}
|
|
145
|
-
<span class="shrink-0 font-mono text-[11px]">{child.badge}</span>
|
|
146
|
-
{/if}
|
|
147
|
-
</a>
|
|
148
|
-
{/each}
|
|
149
|
-
</div>
|
|
196
|
+
{@render subitems(item)}
|
|
150
197
|
{/if}
|
|
151
198
|
{:else}
|
|
152
199
|
<a
|
|
@@ -154,12 +201,15 @@
|
|
|
154
201
|
onclick={() => go(item)}
|
|
155
202
|
aria-current={value === keyOf(item) ? 'page' : undefined}
|
|
156
203
|
title={collapsed ? item.label : undefined}
|
|
157
|
-
class="{row} {focusRing} {toneRing[
|
|
204
|
+
class="{row} {collapsed ? 'justify-center' : ''} {focusRing} {toneRing[
|
|
205
|
+
tone
|
|
206
|
+
]} {value === keyOf(item)
|
|
158
207
|
? `${toneSoft[tone]} font-medium`
|
|
159
208
|
: 'text-text-secondary hover:bg-bg-inset hover:text-text'} {item.disabled
|
|
160
209
|
? 'pointer-events-none opacity-40'
|
|
161
210
|
: ''}"
|
|
162
211
|
>
|
|
212
|
+
{@render glyph(item)}
|
|
163
213
|
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
164
214
|
{#if item.badge !== undefined && !collapsed}
|
|
165
215
|
<span class="shrink-0 font-mono text-[11px] text-text-muted">{item.badge}</span>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type SidebarVariant = 'plain' | 'filled' | 'floating';
|
|
1
3
|
export interface SidebarItem {
|
|
2
4
|
/** Stable id used for the active state. Falls back to `href`. */
|
|
3
5
|
id?: string;
|
|
@@ -5,17 +7,27 @@ export interface SidebarItem {
|
|
|
5
7
|
href?: string;
|
|
6
8
|
disabled?: boolean;
|
|
7
9
|
badge?: string | number;
|
|
10
|
+
/**
|
|
11
|
+
* Leading glyph, 16px. The one thing a collapsed rail still shows, so
|
|
12
|
+
* an item that has to survive `collapsed` needs one.
|
|
13
|
+
*/
|
|
14
|
+
icon?: Snippet;
|
|
8
15
|
/** Nested links — the parent becomes a collapsible group. */
|
|
9
16
|
items?: SidebarItem[];
|
|
10
17
|
/** Open the group on first render. */
|
|
11
18
|
open?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* `false` pins a group open: the parent stops being a button and the
|
|
21
|
+
* children are always on show. For navigation a reader should not be
|
|
22
|
+
* able to lose.
|
|
23
|
+
*/
|
|
24
|
+
collapsible?: boolean;
|
|
12
25
|
}
|
|
13
26
|
export interface SidebarSection {
|
|
14
27
|
/** Small heading above a run of items. */
|
|
15
28
|
label?: string;
|
|
16
29
|
items: SidebarItem[];
|
|
17
30
|
}
|
|
18
|
-
import type { Snippet } from 'svelte';
|
|
19
31
|
import { type Tone } from '../../core/tones.js';
|
|
20
32
|
interface Props {
|
|
21
33
|
sections: SidebarSection[];
|
|
@@ -25,6 +37,8 @@ interface Props {
|
|
|
25
37
|
collapsed?: boolean;
|
|
26
38
|
/** Show the collapse button. */
|
|
27
39
|
collapsible?: boolean;
|
|
40
|
+
/** Surface treatment: bare, the framed default, or a detached card. */
|
|
41
|
+
variant?: SidebarVariant;
|
|
28
42
|
tone?: Tone;
|
|
29
43
|
header?: Snippet;
|
|
30
44
|
footer?: Snippet;
|
|
@@ -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
|
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div class="grid {cols} {bordered ? 'gap-px border border-hairline bg-hairline' : 'gap-10'}">
|
|
43
43
|
{#each stats as stat (stat.label)}
|
|
44
|
-
<div class={bordered ? 'bg-bg p-
|
|
44
|
+
<div class={bordered ? 'bg-bg p-5' : ''}>
|
|
45
45
|
<Stat
|
|
46
46
|
label={stat.label}
|
|
47
47
|
value={stat.value}
|
|
@@ -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;
|