@nqmcreative/ui 0.2.3 → 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/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/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/Drawer.svelte +5 -0
- package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
- package/dist/styles/matte/Footer.svelte +6 -2
- package/dist/styles/matte/Footer.svelte.d.ts +2 -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/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/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/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 +0 -2
- 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 +6 -0
- package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -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 +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/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 +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/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 +0 -2
- package/dist/styles/paper/index.js +0 -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/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/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 +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/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 +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/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 +0 -2
- package/dist/styles/sprout/index.js +0 -2
- package/package.json +2 -26
- package/registry.json +1 -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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
3
|
import { type Tone } from '../../core/tones.js';
|
|
3
4
|
export type StatTrend = 'up' | 'down' | 'flat';
|
|
@@ -8,6 +9,8 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
|
8
9
|
delta?: string;
|
|
9
10
|
trend?: StatTrend;
|
|
10
11
|
hint?: string;
|
|
12
|
+
/** Leading icon on the label row. */
|
|
13
|
+
icon?: Snippet;
|
|
11
14
|
/** Colour of the value itself. Leave unset for plain `text`. */
|
|
12
15
|
tone?: Tone;
|
|
13
16
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
2
4
|
export interface StepItem {
|
|
3
5
|
label: string;
|
|
4
6
|
description?: string;
|
|
7
|
+
/** Replaces the step number. A finished step still shows its check. */
|
|
8
|
+
icon?: Snippet;
|
|
5
9
|
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
6
10
|
error?: boolean;
|
|
7
11
|
disabled?: boolean;
|
|
@@ -10,6 +14,7 @@
|
|
|
10
14
|
|
|
11
15
|
<script lang="ts">
|
|
12
16
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import { iconMd } from './icon.js';
|
|
13
18
|
import { focusRing, toneFill, toneRing, toneText, type Tone } from '../../core/tones.js';
|
|
14
19
|
|
|
15
20
|
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
@@ -67,6 +72,8 @@
|
|
|
67
72
|
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
68
73
|
<path d="m2 7 3.2 3.2L12 3.6" stroke="currentColor" stroke-width="1.8" />
|
|
69
74
|
</svg>
|
|
75
|
+
{:else if item.icon}
|
|
76
|
+
<span class={iconMd}>{@render item.icon()}</span>
|
|
70
77
|
{:else}
|
|
71
78
|
{index + 1}
|
|
72
79
|
{/if}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
export interface StepItem {
|
|
2
3
|
label: string;
|
|
3
4
|
description?: string;
|
|
5
|
+
/** Replaces the step number. A finished step still shows its check. */
|
|
6
|
+
icon?: Snippet;
|
|
4
7
|
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
5
8
|
error?: boolean;
|
|
6
9
|
disabled?: boolean;
|
|
@@ -32,6 +32,16 @@
|
|
|
32
32
|
/** Header stays put while the body scrolls — pair with a `max-h-*` class. */
|
|
33
33
|
stickyHeader?: boolean;
|
|
34
34
|
|
|
35
|
+
/** Bar above the table — a title, a search field, a filter row. */
|
|
36
|
+
header?: Snippet;
|
|
37
|
+
/** Bar below the table — pagination, a total, a summary line. */
|
|
38
|
+
footer?: Snippet;
|
|
39
|
+
/**
|
|
40
|
+
* Shown in place of `header` while rows are ticked, after the count and the
|
|
41
|
+
* clear button. Passing it is what turns the bulk bar on.
|
|
42
|
+
*/
|
|
43
|
+
bulkActions?: Snippet<[{ selected: string[]; clear: () => void }]>;
|
|
44
|
+
|
|
35
45
|
/** Bindable. Set it to sort; leave `sortable` columns to drive it. */
|
|
36
46
|
sort?: TableSort | null;
|
|
37
47
|
/** Sort the rows here rather than refetching them server-side. Default true. */
|
|
@@ -57,6 +67,9 @@
|
|
|
57
67
|
hoverable = true,
|
|
58
68
|
compact = false,
|
|
59
69
|
stickyHeader = false,
|
|
70
|
+
header,
|
|
71
|
+
footer,
|
|
72
|
+
bulkActions,
|
|
60
73
|
sort = $bindable(null),
|
|
61
74
|
sortLocally = true,
|
|
62
75
|
onsort,
|
|
@@ -106,109 +119,152 @@
|
|
|
106
119
|
selected = toggleKey(selected, keyOf(row));
|
|
107
120
|
onselect?.(selected);
|
|
108
121
|
}
|
|
122
|
+
|
|
123
|
+
function clearSelection() {
|
|
124
|
+
selected = [];
|
|
125
|
+
onselect?.(selected);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The bulk bar takes the header's place, so only one of the two is up. */
|
|
129
|
+
const bulkBar = $derived(selectable && bulkActions !== undefined && selected.length > 0);
|
|
109
130
|
</script>
|
|
110
131
|
|
|
111
|
-
<div class="w-full overflow-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
<div class="flex w-full flex-col overflow-hidden border border-hairline {className}" {...rest}>
|
|
133
|
+
{#if bulkBar}
|
|
134
|
+
<div
|
|
135
|
+
class="flex flex-wrap items-center gap-3 border-b border-hairline bg-bg-inset px-3.5 py-2.5"
|
|
136
|
+
>
|
|
137
|
+
<span class="font-mono text-xs font-medium tracking-wide text-text uppercase">
|
|
138
|
+
{selected.length}
|
|
139
|
+
{t.current.selected}
|
|
140
|
+
</span>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onclick={clearSelection}
|
|
144
|
+
class="font-mono text-xs tracking-wide text-text-muted uppercase transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
116
145
|
>
|
|
117
|
-
{
|
|
118
|
-
</
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
{t.current.clear}
|
|
147
|
+
</button>
|
|
148
|
+
<div class="ml-auto flex flex-wrap items-center gap-2">
|
|
149
|
+
{@render bulkActions?.({ selected, clear: clearSelection })}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
{:else if header}
|
|
153
|
+
<div
|
|
154
|
+
class="flex flex-wrap items-center gap-3 border-b border-hairline px-3.5 py-2.5 font-sans text-sm text-text-secondary"
|
|
155
|
+
>
|
|
156
|
+
{@render header()}
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
159
|
+
<div class="min-h-0 w-full flex-1 overflow-auto">
|
|
160
|
+
<table class="w-full border-collapse font-sans text-[15px]">
|
|
161
|
+
{#if caption}
|
|
162
|
+
<caption
|
|
163
|
+
class="border-b border-hairline px-3.5 py-2.5 text-left font-sans text-sm text-text-muted"
|
|
164
|
+
>
|
|
165
|
+
{caption}
|
|
166
|
+
</caption>
|
|
167
|
+
{/if}
|
|
168
|
+
<thead class={stickyHeader ? 'sticky top-0 z-10' : ''}>
|
|
169
|
+
<tr class="border-b border-hairline bg-bg-alt">
|
|
170
|
+
{#if selectable}
|
|
171
|
+
<th scope="col" class="w-10 {pad}">
|
|
172
|
+
<Checkbox
|
|
173
|
+
checked={allSelected}
|
|
174
|
+
indeterminate={someSelected}
|
|
175
|
+
{tone}
|
|
176
|
+
aria-label={t.current.selectAllRows}
|
|
177
|
+
onchange={toggleAll}
|
|
178
|
+
/>
|
|
179
|
+
</th>
|
|
180
|
+
{/if}
|
|
181
|
+
{#each columns as column (column.key)}
|
|
182
|
+
<th
|
|
183
|
+
scope="col"
|
|
184
|
+
aria-sort={sort?.key === column.key
|
|
185
|
+
? sort.direction === 'asc'
|
|
186
|
+
? 'ascending'
|
|
187
|
+
: 'descending'
|
|
188
|
+
: undefined}
|
|
189
|
+
class="font-mono text-xs font-medium tracking-wide text-text-muted uppercase {pad} {aligns[
|
|
190
|
+
column.align ?? 'left'
|
|
191
|
+
]} {column.class ?? ''}"
|
|
192
|
+
>
|
|
193
|
+
{#if column.sortable}
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
onclick={() => toggleSort(column)}
|
|
197
|
+
class="inline-flex items-center gap-1.5 uppercase transition-colors duration-150 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current
|
|
150
198
|
{sort?.key === column.key ? 'text-text' : ''}"
|
|
151
|
-
|
|
199
|
+
>
|
|
200
|
+
{column.label}
|
|
201
|
+
<span class="text-[9px] leading-none" aria-hidden="true">
|
|
202
|
+
{#if sort?.key === column.key}
|
|
203
|
+
{sort.direction === 'asc' ? '▲' : '▼'}
|
|
204
|
+
{:else}
|
|
205
|
+
⇅
|
|
206
|
+
{/if}
|
|
207
|
+
</span>
|
|
208
|
+
</button>
|
|
209
|
+
{:else}
|
|
152
210
|
{column.label}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{:else}
|
|
157
|
-
⇅
|
|
158
|
-
{/if}
|
|
159
|
-
</span>
|
|
160
|
-
</button>
|
|
161
|
-
{:else}
|
|
162
|
-
{column.label}
|
|
163
|
-
{/if}
|
|
164
|
-
</th>
|
|
165
|
-
{/each}
|
|
166
|
-
</tr>
|
|
167
|
-
</thead>
|
|
168
|
-
<tbody>
|
|
169
|
-
{#if sorted.length === 0}
|
|
170
|
-
<tr>
|
|
171
|
-
<td
|
|
172
|
-
colspan={columns.length + (selectable ? 1 : 0)}
|
|
173
|
-
class="px-3.5 py-10 text-center text-sm text-text-muted"
|
|
174
|
-
>
|
|
175
|
-
{#if empty}{@render empty()}{:else}{t.current.noData}{/if}
|
|
176
|
-
</td>
|
|
211
|
+
{/if}
|
|
212
|
+
</th>
|
|
213
|
+
{/each}
|
|
177
214
|
</tr>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<tr
|
|
182
|
-
|
|
183
|
-
|
|
215
|
+
</thead>
|
|
216
|
+
<tbody>
|
|
217
|
+
{#if sorted.length === 0}
|
|
218
|
+
<tr>
|
|
219
|
+
<td
|
|
220
|
+
colspan={columns.length + (selectable ? 1 : 0)}
|
|
221
|
+
class="px-3.5 py-10 text-center text-sm text-text-muted"
|
|
222
|
+
>
|
|
223
|
+
{#if empty}{@render empty()}{:else}{t.current.noData}{/if}
|
|
224
|
+
</td>
|
|
225
|
+
</tr>
|
|
226
|
+
{:else}
|
|
227
|
+
{#each sorted as row, i (selectable ? keyOf(row) : i)}
|
|
228
|
+
{@const isSelected = selectable && selected.includes(keyOf(row))}
|
|
229
|
+
<tr
|
|
230
|
+
aria-selected={selectable ? isSelected : undefined}
|
|
231
|
+
class="border-b border-hairline last:border-b-0
|
|
184
232
|
{isSelected ? 'bg-bg-inset' : striped && i % 2 === 1 ? 'bg-bg-alt' : ''}
|
|
185
233
|
{hoverable ? 'transition-colors duration-150 hover:bg-bg-inset' : ''}"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
234
|
+
>
|
|
235
|
+
{#if selectable}
|
|
236
|
+
<td class={pad}>
|
|
237
|
+
<Checkbox
|
|
238
|
+
checked={isSelected}
|
|
239
|
+
{tone}
|
|
240
|
+
aria-label={t.current.selectRow}
|
|
241
|
+
onchange={() => toggleRow(row)}
|
|
242
|
+
/>
|
|
243
|
+
</td>
|
|
244
|
+
{/if}
|
|
245
|
+
{#each columns as column (column.key)}
|
|
246
|
+
<td
|
|
247
|
+
class="text-text-secondary {pad} {aligns[column.align ?? 'left']} {column.class ??
|
|
248
|
+
''}"
|
|
249
|
+
>
|
|
250
|
+
{#if cell}
|
|
251
|
+
{@render cell({ row, column, value: row[column.key] })}
|
|
252
|
+
{:else}
|
|
253
|
+
{row[column.key] ?? ''}
|
|
254
|
+
{/if}
|
|
255
|
+
</td>
|
|
256
|
+
{/each}
|
|
257
|
+
</tr>
|
|
258
|
+
{/each}
|
|
259
|
+
{/if}
|
|
260
|
+
</tbody>
|
|
261
|
+
</table>
|
|
262
|
+
</div>
|
|
263
|
+
{#if footer}
|
|
264
|
+
<div
|
|
265
|
+
class="flex flex-wrap items-center gap-3 border-t border-hairline px-3.5 py-2.5 font-sans text-sm text-text-secondary"
|
|
266
|
+
>
|
|
267
|
+
{@render footer()}
|
|
268
|
+
</div>
|
|
269
|
+
{/if}
|
|
214
270
|
</div>
|
|
@@ -29,6 +29,18 @@ declare function $$render<T extends Record<string, unknown>>(): {
|
|
|
29
29
|
compact?: boolean;
|
|
30
30
|
/** Header stays put while the body scrolls — pair with a `max-h-*` class. */
|
|
31
31
|
stickyHeader?: boolean;
|
|
32
|
+
/** Bar above the table — a title, a search field, a filter row. */
|
|
33
|
+
header?: Snippet;
|
|
34
|
+
/** Bar below the table — pagination, a total, a summary line. */
|
|
35
|
+
footer?: Snippet;
|
|
36
|
+
/**
|
|
37
|
+
* Shown in place of `header` while rows are ticked, after the count and the
|
|
38
|
+
* clear button. Passing it is what turns the bulk bar on.
|
|
39
|
+
*/
|
|
40
|
+
bulkActions?: Snippet<[{
|
|
41
|
+
selected: string[];
|
|
42
|
+
clear: () => void;
|
|
43
|
+
}]>;
|
|
32
44
|
/** Bindable. Set it to sort; leave `sortable` columns to drive it. */
|
|
33
45
|
sort?: TableSort | null;
|
|
34
46
|
/** Sort the rows here rather than refetching them server-side. Default true. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
4
|
import {
|
|
4
5
|
focusRing,
|
|
@@ -8,10 +9,13 @@
|
|
|
8
9
|
toneText,
|
|
9
10
|
type Tone
|
|
10
11
|
} from '../../core/tones.js';
|
|
12
|
+
import { iconMd } from './icon.js';
|
|
11
13
|
|
|
12
14
|
export interface TabItem {
|
|
13
15
|
value: string;
|
|
14
16
|
label: string;
|
|
17
|
+
/** Leading icon, 16px. */
|
|
18
|
+
icon?: Snippet;
|
|
15
19
|
disabled?: boolean;
|
|
16
20
|
/** Small count/label rendered after the tab title. */
|
|
17
21
|
badge?: string | number;
|
|
@@ -89,6 +93,7 @@
|
|
|
89
93
|
onclick={() => select(item)}
|
|
90
94
|
class={tabClass(value === item.value)}
|
|
91
95
|
>
|
|
96
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}
|
|
92
97
|
{item.label}
|
|
93
98
|
{#if item.badge !== undefined}
|
|
94
99
|
<span class="font-mono text-[11px] text-text-muted">{item.badge}</span>
|
|
@@ -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 TabItem {
|
|
4
5
|
value: string;
|
|
5
6
|
label: string;
|
|
7
|
+
/** Leading icon, 16px. */
|
|
8
|
+
icon?: Snippet;
|
|
6
9
|
disabled?: boolean;
|
|
7
10
|
/** Small count/label rendered after the tab title. */
|
|
8
11
|
badge?: string | number;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { portal } from '../../core/actions/dismissable.js';
|
|
5
5
|
import { toast } from '../../core/toast.svelte.js';
|
|
6
6
|
import { toneBorderSoft, toneSurface, toneText } from '../../core/tones.js';
|
|
7
|
+
import { iconMd } from './icon.js';
|
|
7
8
|
import Spinner from './Spinner.svelte';
|
|
8
9
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
9
10
|
|
|
@@ -57,6 +58,8 @@
|
|
|
57
58
|
<span class="mt-0.5 shrink-0 {toneText[item.tone]}">
|
|
58
59
|
{#if item.loading}
|
|
59
60
|
<Spinner size="sm" label="" />
|
|
61
|
+
{:else if item.icon}
|
|
62
|
+
<span class={iconMd}>{@render item.icon()}</span>
|
|
60
63
|
{:else if item.tone === 'success'}
|
|
61
64
|
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
62
65
|
<path d="m3 8.5 3.2 3.2L13 5" stroke="currentColor" stroke-width="1.6" />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The box a consumer-supplied icon renders into.
|
|
3
|
+
*
|
|
4
|
+
* Every icon in the library is a `Snippet` the app writes — the package ships
|
|
5
|
+
* no icon set and no runtime dependency — so the component has to size the
|
|
6
|
+
* glyph rather than trust it. The `*:` variant hits the snippet's own element,
|
|
7
|
+
* which is what keeps a bare `<svg viewBox="0 0 16 16">` from laying out at its
|
|
8
|
+
* intrinsic 300×150.
|
|
9
|
+
*
|
|
10
|
+
* Kept per style rather than in `core/`: an app only `@source`s the style
|
|
11
|
+
* folder it installed, so a class string living outside it would never be
|
|
12
|
+
* generated by Tailwind.
|
|
13
|
+
*/
|
|
14
|
+
/** 14px — menu rows, chips, anything sitting on one line of small text. */
|
|
15
|
+
export declare const iconSm = "grid size-3.5 shrink-0 place-items-center *:size-3.5";
|
|
16
|
+
/** 16px — the default: nav items, tabs, buttons, list rows. */
|
|
17
|
+
export declare const iconMd = "grid size-4 shrink-0 place-items-center *:size-4";
|
|
18
|
+
/** 20px — headers, stats, anything sitting beside a heading. */
|
|
19
|
+
export declare const iconLg = "grid size-5 shrink-0 place-items-center *:size-5";
|
|
20
|
+
/** 24px — dialog and empty-state marks. */
|
|
21
|
+
export declare const iconXl = "grid size-6 shrink-0 place-items-center *:size-6";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The box a consumer-supplied icon renders into.
|
|
3
|
+
*
|
|
4
|
+
* Every icon in the library is a `Snippet` the app writes — the package ships
|
|
5
|
+
* no icon set and no runtime dependency — so the component has to size the
|
|
6
|
+
* glyph rather than trust it. The `*:` variant hits the snippet's own element,
|
|
7
|
+
* which is what keeps a bare `<svg viewBox="0 0 16 16">` from laying out at its
|
|
8
|
+
* intrinsic 300×150.
|
|
9
|
+
*
|
|
10
|
+
* Kept per style rather than in `core/`: an app only `@source`s the style
|
|
11
|
+
* folder it installed, so a class string living outside it would never be
|
|
12
|
+
* generated by Tailwind.
|
|
13
|
+
*/
|
|
14
|
+
/** 14px — menu rows, chips, anything sitting on one line of small text. */
|
|
15
|
+
export const iconSm = 'grid size-3.5 shrink-0 place-items-center *:size-3.5';
|
|
16
|
+
/** 16px — the default: nav items, tabs, buttons, list rows. */
|
|
17
|
+
export const iconMd = 'grid size-4 shrink-0 place-items-center *:size-4';
|
|
18
|
+
/** 20px — headers, stats, anything sitting beside a heading. */
|
|
19
|
+
export const iconLg = 'grid size-5 shrink-0 place-items-center *:size-5';
|
|
20
|
+
/** 24px — dialog and empty-state marks. */
|
|
21
|
+
export const iconXl = 'grid size-6 shrink-0 place-items-center *:size-6';
|
|
@@ -22,8 +22,6 @@ export { default as Switch } from './Switch.svelte';
|
|
|
22
22
|
export type { SwitchSize } from './Switch.svelte';
|
|
23
23
|
export { default as Field } from './Field.svelte';
|
|
24
24
|
export { default as Label } from './Label.svelte';
|
|
25
|
-
export { default as PasswordInput } from './PasswordInput.svelte';
|
|
26
|
-
export { default as NumberInput } from './NumberInput.svelte';
|
|
27
25
|
export { default as Slider } from './Slider.svelte';
|
|
28
26
|
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
29
27
|
export type { RadioOption } from './RadioGroup.svelte';
|
|
@@ -21,8 +21,6 @@ export { default as Radio } from './Radio.svelte';
|
|
|
21
21
|
export { default as Switch } from './Switch.svelte';
|
|
22
22
|
export { default as Field } from './Field.svelte';
|
|
23
23
|
export { default as Label } from './Label.svelte';
|
|
24
|
-
export { default as PasswordInput } from './PasswordInput.svelte';
|
|
25
|
-
export { default as NumberInput } from './NumberInput.svelte';
|
|
26
24
|
export { default as Slider } from './Slider.svelte';
|
|
27
25
|
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
28
26
|
export { default as CheckboxGroup } from './CheckboxGroup.svelte';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { toneText, type Tone } from '../../core/tones.js';
|
|
5
|
+
import { iconMd } from './icon.js';
|
|
5
6
|
|
|
6
7
|
interface Props extends HTMLAttributes<HTMLDetailsElement> {
|
|
7
8
|
title: string;
|
|
@@ -9,6 +10,8 @@
|
|
|
9
10
|
tone?: Tone;
|
|
10
11
|
/** Short text on the right of the summary row. */
|
|
11
12
|
meta?: string;
|
|
13
|
+
/** Leading icon on the summary row. */
|
|
14
|
+
icon?: Snippet;
|
|
12
15
|
children: Snippet;
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -17,6 +20,7 @@
|
|
|
17
20
|
open = $bindable(false),
|
|
18
21
|
tone = 'brand',
|
|
19
22
|
meta,
|
|
23
|
+
icon,
|
|
20
24
|
class: className = '',
|
|
21
25
|
children,
|
|
22
26
|
...rest
|
|
@@ -27,6 +31,7 @@
|
|
|
27
31
|
<summary
|
|
28
32
|
class="flex cursor-pointer list-none items-center gap-4 px-4 py-3.5 font-sans text-sm font-medium text-text transition-colors duration-150 ease-brand-out select-none hover:bg-bg-alt focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-current [&::-webkit-details-marker]:hidden"
|
|
29
33
|
>
|
|
34
|
+
{#if icon}<span class={iconMd}>{@render icon()}</span>{/if}
|
|
30
35
|
<span class="flex-1">{title}</span>
|
|
31
36
|
{#if meta}<span class="text-[13px] font-normal text-text-muted">{meta}</span>{/if}
|
|
32
37
|
<svg
|
|
@@ -7,6 +7,8 @@ interface Props extends HTMLAttributes<HTMLDetailsElement> {
|
|
|
7
7
|
tone?: Tone;
|
|
8
8
|
/** Short text on the right of the summary row. */
|
|
9
9
|
meta?: string;
|
|
10
|
+
/** Leading icon on the summary row. */
|
|
11
|
+
icon?: Snippet;
|
|
10
12
|
children: Snippet;
|
|
11
13
|
}
|
|
12
14
|
declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
4
|
import { toneSoft, type Tone } from '../../core/tones.js';
|
|
4
5
|
|
|
@@ -13,6 +14,8 @@
|
|
|
13
14
|
tone?: Tone;
|
|
14
15
|
/** Rounded square instead of the default circle. */
|
|
15
16
|
squared?: boolean;
|
|
17
|
+
/** Drawn instead of the initials when there is no `src` — a user mark. */
|
|
18
|
+
fallback?: Snippet;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
let {
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
size = 'md',
|
|
23
26
|
tone = 'brand',
|
|
24
27
|
squared = false,
|
|
28
|
+
fallback,
|
|
25
29
|
class: className = '',
|
|
26
30
|
...rest
|
|
27
31
|
}: Props = $props();
|
|
@@ -34,6 +38,15 @@
|
|
|
34
38
|
xl: 'size-16 text-lg'
|
|
35
39
|
};
|
|
36
40
|
|
|
41
|
+
// The mark tracks the initials: about half the circle, whatever the size.
|
|
42
|
+
const glyphs: Record<AvatarSize, string> = {
|
|
43
|
+
xs: 'grid place-items-center *:size-3',
|
|
44
|
+
sm: 'grid place-items-center *:size-4',
|
|
45
|
+
md: 'grid place-items-center *:size-5',
|
|
46
|
+
lg: 'grid place-items-center *:size-6',
|
|
47
|
+
xl: 'grid place-items-center *:size-8'
|
|
48
|
+
};
|
|
49
|
+
|
|
37
50
|
const initials = $derived(
|
|
38
51
|
name
|
|
39
52
|
.trim()
|
|
@@ -54,6 +67,8 @@
|
|
|
54
67
|
>
|
|
55
68
|
{#if src}
|
|
56
69
|
<img {src} alt={alt ?? name} class="size-full object-cover" />
|
|
70
|
+
{:else if fallback}
|
|
71
|
+
<span class={glyphs[size]}>{@render fallback()}</span>
|
|
57
72
|
{:else}
|
|
58
73
|
{initials}
|
|
59
74
|
{/if}
|
|
@@ -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 AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
@@ -10,6 +11,8 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
|
10
11
|
tone?: Tone;
|
|
11
12
|
/** Rounded square instead of the default circle. */
|
|
12
13
|
squared?: boolean;
|
|
14
|
+
/** Drawn instead of the initials when there is no `src` — a user mark. */
|
|
15
|
+
fallback?: Snippet;
|
|
13
16
|
}
|
|
14
17
|
declare const Avatar: import("svelte").Component<Props, {}, "">;
|
|
15
18
|
type Avatar = ReturnType<typeof Avatar>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
toneText,
|
|
10
10
|
type Tone
|
|
11
11
|
} from '../../core/tones.js';
|
|
12
|
+
import { iconSm } from './icon.js';
|
|
12
13
|
|
|
13
14
|
export type BadgeVariant = 'soft' | 'solid' | 'outline';
|
|
14
15
|
export type BadgeSize = 'sm' | 'md';
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
size?: BadgeSize;
|
|
20
21
|
/** Small filled circle before the label. */
|
|
21
22
|
dot?: boolean;
|
|
23
|
+
/** Leading icon, 14px — sits where the dot would. */
|
|
24
|
+
icon?: Snippet;
|
|
22
25
|
children: Snippet;
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -27,6 +30,7 @@
|
|
|
27
30
|
variant = 'soft',
|
|
28
31
|
size = 'md',
|
|
29
32
|
dot = false,
|
|
33
|
+
icon,
|
|
30
34
|
class: className = '',
|
|
31
35
|
children,
|
|
32
36
|
...rest
|
|
@@ -54,5 +58,6 @@
|
|
|
54
58
|
class="size-1.5 shrink-0 rounded-full {variant === 'solid' ? 'bg-current' : toneFill[tone]}"
|
|
55
59
|
></span>
|
|
56
60
|
{/if}
|
|
61
|
+
{#if icon}<span class={iconSm}>{@render icon()}</span>{/if}
|
|
57
62
|
{@render children()}
|
|
58
63
|
</span>
|
|
@@ -9,6 +9,8 @@ interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
|
9
9
|
size?: BadgeSize;
|
|
10
10
|
/** Small filled circle before the label. */
|
|
11
11
|
dot?: boolean;
|
|
12
|
+
/** Leading icon, 14px — sits where the dot would. */
|
|
13
|
+
icon?: Snippet;
|
|
12
14
|
children: Snippet;
|
|
13
15
|
}
|
|
14
16
|
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
4
|
import { toneHoverText, type Tone } from '../../core/tones.js';
|
|
4
5
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
6
|
+
import { iconMd } from './icon.js';
|
|
5
7
|
|
|
6
8
|
export interface BreadcrumbItem {
|
|
7
9
|
label: string;
|
|
8
10
|
href?: string;
|
|
11
|
+
/** Leading icon — a home mark on the first crumb, say. */
|
|
12
|
+
icon?: Snippet;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
interface Props extends HTMLAttributes<HTMLElement> {
|
|
@@ -29,18 +33,18 @@
|
|
|
29
33
|
{#if item.href && i < items.length - 1}
|
|
30
34
|
<a
|
|
31
35
|
href={item.href}
|
|
32
|
-
class="rounded px-1 py-0.5 text-text-muted transition-colors duration-150 ease-brand-out hover:bg-bg-inset {toneHoverText[
|
|
36
|
+
class="inline-flex items-center gap-1.5 rounded px-1 py-0.5 text-text-muted transition-colors duration-150 ease-brand-out hover:bg-bg-inset {toneHoverText[
|
|
33
37
|
tone
|
|
34
38
|
]}"
|
|
35
39
|
>
|
|
36
|
-
{item.label}
|
|
40
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}{item.label}
|
|
37
41
|
</a>
|
|
38
42
|
{:else}
|
|
39
43
|
<span
|
|
40
|
-
class="px-1 py-0.5 font-medium text-text"
|
|
44
|
+
class="inline-flex items-center gap-1.5 px-1 py-0.5 font-medium text-text"
|
|
41
45
|
aria-current={i === items.length - 1 ? 'page' : undefined}
|
|
42
46
|
>
|
|
43
|
-
{item.label}
|
|
47
|
+
{#if item.icon}<span class={iconMd}>{@render item.icon()}</span>{/if}{item.label}
|
|
44
48
|
</span>
|
|
45
49
|
{/if}
|
|
46
50
|
</li>
|