@dorsk/tsumikit 0.74.0 → 0.75.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 +38 -1
- package/dist/autoresize.d.ts +2 -0
- package/dist/autoresize.js +8 -1
- package/dist/components/atoms/Artwork.svelte +12 -5
- package/dist/components/atoms/Avatar.svelte +1 -1
- package/dist/components/atoms/Badge.svelte +59 -23
- package/dist/components/atoms/Button.svelte +130 -96
- package/dist/components/atoms/Button.svelte.d.ts +2 -0
- package/dist/components/atoms/Card.svelte +10 -4
- package/dist/components/atoms/Checkbox.svelte +36 -9
- package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/atoms/Gauge.svelte +20 -14
- package/dist/components/atoms/Heading.svelte +1 -1
- package/dist/components/atoms/Icon.svelte +1 -0
- package/dist/components/atoms/Input.svelte +67 -13
- package/dist/components/atoms/Kbd.svelte +3 -3
- package/dist/components/atoms/Link.svelte +7 -2
- package/dist/components/atoms/Progress.svelte +4 -2
- package/dist/components/atoms/Scrim.svelte +3 -8
- package/dist/components/atoms/SegmentedProgress.svelte +5 -4
- package/dist/components/atoms/Select.svelte +58 -27
- package/dist/components/atoms/Slider.svelte +79 -22
- package/dist/components/atoms/Slider.svelte.d.ts +2 -0
- package/dist/components/atoms/Swatch.svelte +19 -8
- package/dist/components/atoms/Switch.svelte +34 -21
- package/dist/components/atoms/Switch.svelte.d.ts +2 -0
- package/dist/components/atoms/Text.svelte +1 -1
- package/dist/components/atoms/Textarea.svelte +34 -12
- package/dist/components/atoms/TimeDisplay.svelte +4 -2
- package/dist/components/layouts/AppShell.svelte +26 -15
- package/dist/components/layouts/MasterDetail.svelte +23 -21
- package/dist/components/layouts/NavBar.svelte +11 -8
- package/dist/components/layouts/NavItem.svelte +15 -7
- package/dist/components/layouts/NavSection.svelte +1 -1
- package/dist/components/layouts/ResizablePanel.svelte +37 -16
- package/dist/components/layouts/TabBar.svelte +24 -14
- package/dist/components/layouts/TabBar.svelte.d.ts +2 -0
- package/dist/components/molecules/Accordion.svelte +4 -0
- package/dist/components/molecules/Accordion.svelte.d.ts +2 -0
- package/dist/components/molecules/AttachmentList.svelte +14 -11
- package/dist/components/molecules/Breadcrumb.svelte +13 -9
- package/dist/components/molecules/Callout.svelte +3 -3
- package/dist/components/molecules/CapBar.svelte +23 -22
- package/dist/components/molecules/Carousel.svelte +27 -17
- package/dist/components/molecules/ChatBubble.svelte +2 -2
- package/dist/components/molecules/CodeBlock.svelte +2 -2
- package/dist/components/molecules/Combobox.svelte +30 -1
- package/dist/components/molecules/Combobox.svelte.d.ts +4 -0
- package/dist/components/molecules/Composer.svelte +2 -2
- package/dist/components/molecules/ConfirmModal.svelte +4 -0
- package/dist/components/molecules/ConfirmModal.svelte.d.ts +2 -0
- package/dist/components/molecules/CopyButton.svelte +46 -2
- package/dist/components/molecules/CopyButton.svelte.d.ts +2 -0
- package/dist/components/molecules/Disclosure.svelte +23 -12
- package/dist/components/molecules/Disclosure.svelte.d.ts +2 -0
- package/dist/components/molecules/Drawer.svelte +43 -23
- package/dist/components/molecules/Drawer.svelte.d.ts +2 -0
- package/dist/components/molecules/Dropzone.svelte +24 -8
- package/dist/components/molecules/EmojiPicker.svelte +27 -8
- package/dist/components/molecules/EmojiPicker.svelte.d.ts +2 -0
- package/dist/components/molecules/EmptyState.svelte +5 -5
- package/dist/components/molecules/Fieldset.svelte +8 -13
- package/dist/components/molecules/FileButton.svelte +57 -31
- package/dist/components/molecules/FileButton.svelte.d.ts +2 -0
- package/dist/components/molecules/FilterInput.svelte +51 -11
- package/dist/components/molecules/FontScalePicker.svelte +20 -9
- package/dist/components/molecules/GitRef.svelte +9 -7
- package/dist/components/molecules/GlyphStack.svelte +38 -14
- package/dist/components/molecules/IconButton.svelte +6 -3
- package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
- package/dist/components/molecules/InputGroup.svelte +13 -12
- package/dist/components/molecules/LoadMore.svelte +6 -1
- package/dist/components/molecules/LoadMore.svelte.d.ts +2 -0
- package/dist/components/molecules/Menu.svelte +33 -7
- package/dist/components/molecules/Menu.svelte.d.ts +2 -0
- package/dist/components/molecules/Metric.svelte +11 -9
- package/dist/components/molecules/Modal.svelte +76 -26
- package/dist/components/molecules/Modal.svelte.d.ts +2 -0
- package/dist/components/molecules/OptionButton.svelte +28 -10
- package/dist/components/molecules/OptionButton.svelte.d.ts +2 -0
- package/dist/components/molecules/Pagination.svelte +26 -9
- package/dist/components/molecules/Pagination.svelte.d.ts +2 -0
- package/dist/components/molecules/Popover.svelte +107 -51
- package/dist/components/molecules/Popover.svelte.d.ts +2 -0
- package/dist/components/molecules/Prose.svelte +1 -1
- package/dist/components/molecules/PullToRefresh.svelte +14 -10
- package/dist/components/molecules/PullToRefresh.svelte.d.ts +2 -0
- package/dist/components/molecules/RadialMenu.svelte +52 -19
- package/dist/components/molecules/RadialMenu.svelte.d.ts +2 -0
- package/dist/components/molecules/RadioGroup.svelte +38 -23
- package/dist/components/molecules/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/molecules/SectionHeader.svelte +13 -8
- package/dist/components/molecules/SeekSlider.svelte +38 -27
- package/dist/components/molecules/SeekSlider.svelte.d.ts +2 -0
- package/dist/components/molecules/SegmentedControl.svelte +139 -86
- package/dist/components/molecules/SegmentedControl.svelte.d.ts +2 -0
- package/dist/components/molecules/SelectButton.svelte +21 -6
- package/dist/components/molecules/SelectButton.svelte.d.ts +2 -0
- package/dist/components/molecules/SortableList.svelte +10 -11
- package/dist/components/molecules/SoundSettings.svelte +161 -0
- package/dist/components/molecules/SoundSettings.svelte.d.ts +17 -0
- package/dist/components/molecules/SplitButton.svelte +30 -3
- package/dist/components/molecules/SplitButton.svelte.d.ts +2 -0
- package/dist/components/molecules/SwipeRow.svelte +18 -15
- package/dist/components/molecules/SwipeRow.svelte.d.ts +2 -0
- package/dist/components/molecules/Tabs.svelte +92 -10
- package/dist/components/molecules/Tabs.svelte.d.ts +2 -0
- package/dist/components/molecules/ThemePicker.svelte +25 -10
- package/dist/components/molecules/Timestamp.svelte +12 -7
- package/dist/components/molecules/Toaster.svelte +65 -14
- package/dist/components/molecules/Toaster.svelte.d.ts +2 -0
- package/dist/components/molecules/Toggle.svelte +36 -13
- package/dist/components/molecules/Toggle.svelte.d.ts +2 -0
- package/dist/components/molecules/Tooltip.svelte +41 -10
- package/dist/components/molecules/Tooltip.svelte.d.ts +1 -1
- package/dist/components/molecules/VolumeSlider.svelte +34 -14
- package/dist/components/molecules/VolumeSlider.svelte.d.ts +2 -0
- package/dist/components/molecules/WorkingDir.svelte +1 -0
- package/dist/components/organisms/ConversationFeed.svelte +4 -0
- package/dist/components/organisms/DataTable.svelte +37 -14
- package/dist/floating.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sound-synth.js +17 -12
- package/dist/stores/toast.svelte.d.ts +9 -0
- package/dist/stores/toast.svelte.js +45 -9
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
retryLabel = 'Retry',
|
|
16
16
|
doneLabel = 'No more items',
|
|
17
17
|
pill = false,
|
|
18
|
+
sound = true,
|
|
18
19
|
class: klass = '',
|
|
19
20
|
...rest
|
|
20
21
|
}: {
|
|
@@ -26,6 +27,8 @@
|
|
|
26
27
|
retryLabel?: string;
|
|
27
28
|
doneLabel?: string;
|
|
28
29
|
pill?: boolean;
|
|
30
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
31
|
+
sound?: boolean;
|
|
29
32
|
class?: string;
|
|
30
33
|
[key: string]: unknown;
|
|
31
34
|
} = $props();
|
|
@@ -44,12 +47,13 @@
|
|
|
44
47
|
<Text variant="caption" class="load-more-done">{doneLabel}</Text>
|
|
45
48
|
{:else if state === 'error'}
|
|
46
49
|
<Text tone="danger" size="sm" class="load-more-error">{errorLabel}</Text>
|
|
47
|
-
<Button size="sm" onclick={() => onload?.()}>{retryLabel}</Button>
|
|
50
|
+
<Button size="sm" {sound} onclick={() => onload?.()}>{retryLabel}</Button>
|
|
48
51
|
{:else}
|
|
49
52
|
<Button
|
|
50
53
|
size="sm"
|
|
51
54
|
variant={pill ? 'default' : 'ghost'}
|
|
52
55
|
loading={state === 'loading'}
|
|
56
|
+
{sound}
|
|
53
57
|
class={pill ? 'load-more-chip' : ''}
|
|
54
58
|
onclick={() => onload?.()}
|
|
55
59
|
>
|
|
@@ -67,6 +71,7 @@
|
|
|
67
71
|
gap: var(--sp-2);
|
|
68
72
|
padding: var(--sp-2);
|
|
69
73
|
min-height: var(--sp-8);
|
|
74
|
+
flex-shrink: 0;
|
|
70
75
|
}
|
|
71
76
|
.load-more-pill {
|
|
72
77
|
padding: var(--sp-1);
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
import Icon from '../atoms/Icon.svelte';
|
|
52
52
|
import Badge from '../atoms/Badge.svelte';
|
|
53
53
|
import { menuItemCloses, menuItemGlyphs } from './menu-item';
|
|
54
|
+
import { playSound } from '../../sound';
|
|
54
55
|
|
|
55
56
|
type PopoverProps = ComponentProps<typeof Popover>;
|
|
56
57
|
type TriggerChrome = Pick<
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
style: styleProp = '',
|
|
104
105
|
panelClass = '',
|
|
105
106
|
panelStyle = '',
|
|
107
|
+
sound = true,
|
|
106
108
|
...rest
|
|
107
109
|
}: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
|
|
108
110
|
|
|
@@ -123,9 +125,13 @@
|
|
|
123
125
|
* size or cap it (`min-width`, `max-height`, `padding`). */
|
|
124
126
|
panelClass?: string;
|
|
125
127
|
panelStyle?: string;
|
|
128
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
129
|
+
sound?: boolean;
|
|
126
130
|
} & TriggerChrome;
|
|
127
131
|
|
|
128
132
|
let listEl = $state<HTMLDivElement | null>(null);
|
|
133
|
+
// The activated row keeps its accent flash while the menu runs its exit.
|
|
134
|
+
let flashed = $state<string | null>(null);
|
|
129
135
|
|
|
130
136
|
const ROW_TARGET = ':is(select, input, textarea, button):not(:disabled), a[href], [tabindex]:not([tabindex="-1"])';
|
|
131
137
|
|
|
@@ -162,9 +168,13 @@
|
|
|
162
168
|
focusAt(b.length - 1);
|
|
163
169
|
}
|
|
164
170
|
}
|
|
165
|
-
function select(item: MenuItem, close: () => void) {
|
|
171
|
+
function select(item: MenuItem, key: string, close: () => void) {
|
|
166
172
|
if (item.disabled) return;
|
|
167
|
-
|
|
173
|
+
playSound('click', sound);
|
|
174
|
+
if (menuItemCloses(item, closeOnSelect)) {
|
|
175
|
+
flashed = key;
|
|
176
|
+
close();
|
|
177
|
+
}
|
|
168
178
|
item.onselect?.();
|
|
169
179
|
}
|
|
170
180
|
</script>
|
|
@@ -193,11 +203,13 @@
|
|
|
193
203
|
{gap}
|
|
194
204
|
{count}
|
|
195
205
|
{countMax}
|
|
206
|
+
{sound}
|
|
196
207
|
role="menu"
|
|
197
208
|
haspopup="menu"
|
|
198
209
|
bind:open
|
|
199
210
|
{onclose}
|
|
200
211
|
onopen={() => {
|
|
212
|
+
flashed = null;
|
|
201
213
|
queueMicrotask(() => focusAt(0));
|
|
202
214
|
onopen?.();
|
|
203
215
|
}}
|
|
@@ -205,6 +217,7 @@
|
|
|
205
217
|
{#snippet children({ close }: { close: () => void })}
|
|
206
218
|
<div bind:this={listEl} role="none" class="menu" data-tsu="Menu" tabindex="-1" {onkeydown}>
|
|
207
219
|
{#each items as item, i (item.id ?? `${i}:${item.label}`)}
|
|
220
|
+
{@const key = item.id ?? `${i}:${item.label}`}
|
|
208
221
|
{#if item.control}
|
|
209
222
|
<div {...item.attrs as HTMLAttributes<HTMLDivElement>} role="none" class="menu-item menu-row" class:danger={item.danger} inert={item.disabled}>
|
|
210
223
|
{#if item.icon}<Icon name={item.icon} />{/if}
|
|
@@ -219,9 +232,10 @@
|
|
|
219
232
|
class="menu-item"
|
|
220
233
|
class:danger={item.danger}
|
|
221
234
|
class:on={item.pressed}
|
|
235
|
+
data-flash={flashed === key || undefined}
|
|
222
236
|
disabled={item.disabled}
|
|
223
237
|
tabindex="-1"
|
|
224
|
-
onclick={() => select(item, close)}
|
|
238
|
+
onclick={() => select(item, key, close)}
|
|
225
239
|
>
|
|
226
240
|
{#if item.content}
|
|
227
241
|
{@render item.content(item)}
|
|
@@ -251,7 +265,7 @@
|
|
|
251
265
|
.menu {
|
|
252
266
|
display: flex;
|
|
253
267
|
flex-direction: column;
|
|
254
|
-
gap:
|
|
268
|
+
gap: var(--sp-px);
|
|
255
269
|
}
|
|
256
270
|
.menu-item {
|
|
257
271
|
display: flex;
|
|
@@ -266,15 +280,27 @@
|
|
|
266
280
|
border-radius: var(--r-sm);
|
|
267
281
|
font-size: var(--fs-sm);
|
|
268
282
|
white-space: nowrap;
|
|
283
|
+
transition: background-color var(--dur-fast) var(--ease);
|
|
269
284
|
}
|
|
270
285
|
.menu-row {
|
|
271
286
|
position: relative;
|
|
272
287
|
}
|
|
273
|
-
.
|
|
288
|
+
/* Highlight lands instantly and fades out: rows change at pointer speed. */
|
|
274
289
|
.menu-item:focus-visible,
|
|
275
|
-
.menu-row:focus-within
|
|
290
|
+
.menu-row:focus-within,
|
|
291
|
+
.menu-item[data-flash] {
|
|
276
292
|
background: var(--bg-elevated-2);
|
|
277
293
|
outline: none;
|
|
294
|
+
transition-duration: var(--dur-instant);
|
|
295
|
+
}
|
|
296
|
+
.menu-item[data-flash] {
|
|
297
|
+
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
|
298
|
+
}
|
|
299
|
+
@media (hover: hover) and (pointer: fine) {
|
|
300
|
+
.menu-item:hover:not(:disabled) {
|
|
301
|
+
background: var(--bg-elevated-2);
|
|
302
|
+
transition-duration: var(--dur-instant);
|
|
303
|
+
}
|
|
278
304
|
}
|
|
279
305
|
.menu-item.danger {
|
|
280
306
|
color: var(--danger);
|
|
@@ -305,7 +331,7 @@
|
|
|
305
331
|
}
|
|
306
332
|
.menu-item:disabled,
|
|
307
333
|
.menu-row[inert] {
|
|
308
|
-
opacity:
|
|
334
|
+
opacity: var(--disabled-opacity);
|
|
309
335
|
cursor: not-allowed;
|
|
310
336
|
}
|
|
311
337
|
</style>
|
|
@@ -58,6 +58,8 @@ type Own = {
|
|
|
58
58
|
* size or cap it (`min-width`, `max-height`, `padding`). */
|
|
59
59
|
panelClass?: string;
|
|
60
60
|
panelStyle?: string;
|
|
61
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
62
|
+
sound?: boolean;
|
|
61
63
|
} & TriggerChrome;
|
|
62
64
|
type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
|
|
63
65
|
declare const Menu: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
letter-spacing: 0.06em;
|
|
189
189
|
color: var(--text-muted);
|
|
190
190
|
font-family: var(--font-mono);
|
|
191
|
-
line-height:
|
|
191
|
+
line-height: var(--lh-snug);
|
|
192
192
|
}
|
|
193
193
|
.metric-chip {
|
|
194
194
|
display: inline-flex;
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
width: 1.75rem;
|
|
199
199
|
height: 1.75rem;
|
|
200
200
|
border-radius: var(--r-md);
|
|
201
|
-
font-size:
|
|
201
|
+
font-size: var(--fs-md);
|
|
202
202
|
color: var(--metric-tone);
|
|
203
203
|
background: color-mix(in srgb, var(--metric-tone) 14%, transparent);
|
|
204
204
|
border: 1px solid color-mix(in srgb, var(--metric-tone) 30%, transparent);
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
align-items: baseline;
|
|
209
209
|
gap: var(--sp-1);
|
|
210
210
|
font-family: var(--font-mono);
|
|
211
|
-
line-height:
|
|
211
|
+
line-height: var(--lh-none);
|
|
212
212
|
}
|
|
213
213
|
.metric-num {
|
|
214
214
|
min-width: 0;
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
.metric-sub {
|
|
230
230
|
font-size: var(--fs-xs);
|
|
231
231
|
color: var(--text-faint);
|
|
232
|
-
line-height:
|
|
232
|
+
line-height: var(--lh-snug);
|
|
233
233
|
}
|
|
234
234
|
:global(.metric-sm) .metric-num {
|
|
235
235
|
font-size: var(--fs-lg);
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
:global(.metric-sm) .metric-chip {
|
|
238
238
|
width: 1.5rem;
|
|
239
239
|
height: 1.5rem;
|
|
240
|
-
font-size:
|
|
240
|
+
font-size: var(--fs-sm);
|
|
241
241
|
}
|
|
242
242
|
.metric-inline {
|
|
243
243
|
flex-direction: row;
|
|
@@ -260,12 +260,14 @@
|
|
|
260
260
|
cursor: pointer;
|
|
261
261
|
border-radius: var(--r-sm);
|
|
262
262
|
}
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
@media (hover: hover) and (pointer: fine) {
|
|
264
|
+
.metric-inline.metric-tap:hover .metric-label {
|
|
265
|
+
color: var(--text);
|
|
266
|
+
}
|
|
265
267
|
}
|
|
266
268
|
.metric-inline.metric-tap:focus-visible {
|
|
267
|
-
outline:
|
|
268
|
-
outline-offset:
|
|
269
|
+
outline: var(--focus-ring);
|
|
270
|
+
outline-offset: var(--focus-ring-offset);
|
|
269
271
|
}
|
|
270
272
|
.metric-segments {
|
|
271
273
|
display: flex;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { Snippet } from 'svelte';
|
|
11
11
|
import { browser } from '../../env';
|
|
12
12
|
import { readStorage, writeStorage } from '../../stores/storage';
|
|
13
|
+
import { playSound } from '../../sound';
|
|
13
14
|
import Icon, { type IconName } from '../atoms/Icon.svelte';
|
|
14
15
|
import Spinner from '../atoms/Spinner.svelte';
|
|
15
16
|
import IconButton from './IconButton.svelte';
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
style: styleProp = '',
|
|
38
39
|
bodyClass = '',
|
|
39
40
|
maxHeight,
|
|
41
|
+
sound = true,
|
|
40
42
|
}: {
|
|
41
43
|
title: string;
|
|
42
44
|
/** Controlled visibility. When provided the `<dialog>` stays mounted and
|
|
@@ -67,6 +69,8 @@
|
|
|
67
69
|
bodyClass?: string;
|
|
68
70
|
/** Cap the sheet height; the body scrolls inside it. */
|
|
69
71
|
maxHeight?: string;
|
|
72
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
73
|
+
sound?: boolean;
|
|
70
74
|
} = $props();
|
|
71
75
|
const t = $derived(canonicalTone(tone));
|
|
72
76
|
|
|
@@ -87,19 +91,26 @@
|
|
|
87
91
|
|
|
88
92
|
// Open as a modal (top layer + trap + inert background + focus mgmt). The
|
|
89
93
|
// native element restores focus to the trigger automatically on close.
|
|
94
|
+
// Mounting (or mounting with `open` already true) is a sync, not a
|
|
95
|
+
// transition, so it stays silent.
|
|
96
|
+
let synced = false;
|
|
90
97
|
$effect(() => {
|
|
91
98
|
if (!dialogEl) return;
|
|
92
99
|
if (!controlled) {
|
|
93
100
|
dialogEl.showModal();
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
|
-
if (open && !dialogEl.open)
|
|
97
|
-
|
|
103
|
+
if (open && !dialogEl.open) {
|
|
104
|
+
dialogEl.showModal();
|
|
105
|
+
if (synced) playSound('open', sound);
|
|
106
|
+
} else if (!open && dialogEl.open) dialogEl.close();
|
|
107
|
+
synced = true;
|
|
98
108
|
});
|
|
99
109
|
|
|
100
110
|
function requestClose() {
|
|
101
111
|
if (busy) return;
|
|
102
112
|
if (controlled) open = false;
|
|
113
|
+
else playSound('close', sound);
|
|
103
114
|
onclose?.();
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -174,6 +185,7 @@
|
|
|
174
185
|
}}
|
|
175
186
|
onclose={() => {
|
|
176
187
|
if (controlled) open = false;
|
|
188
|
+
playSound('close', sound);
|
|
177
189
|
}}
|
|
178
190
|
onpointerdown={onDialogPointerDown}
|
|
179
191
|
onclick={onDialogClick}
|
|
@@ -189,7 +201,7 @@
|
|
|
189
201
|
>
|
|
190
202
|
<div class="sheet-head">
|
|
191
203
|
{#if t !== 'neutral'}
|
|
192
|
-
<span class="sheet-tone-icon"><Icon name={TONE_ICON[t]}
|
|
204
|
+
<span class="sheet-tone-icon"><Icon name={TONE_ICON[t]} /></span>
|
|
193
205
|
{/if}
|
|
194
206
|
<span id={titleId} class="sheet-title truncate">{title}</span>
|
|
195
207
|
{#if busy}<Spinner label="Working" />{/if}
|
|
@@ -234,17 +246,28 @@
|
|
|
234
246
|
display: flex;
|
|
235
247
|
align-items: flex-end;
|
|
236
248
|
justify-content: center;
|
|
249
|
+
/* Exits run natively on close(): the box leaves the top layer once the sheet is gone. */
|
|
250
|
+
transition:
|
|
251
|
+
display var(--dur-exit-slow) allow-discrete,
|
|
252
|
+
overlay var(--dur-exit-slow) allow-discrete;
|
|
237
253
|
}
|
|
238
254
|
.modal:not([open]) {
|
|
239
255
|
display: none;
|
|
240
256
|
}
|
|
241
257
|
.modal::backdrop {
|
|
242
258
|
background: rgba(0, 0, 0, 0.55);
|
|
243
|
-
|
|
244
|
-
|
|
259
|
+
opacity: 0;
|
|
260
|
+
transition:
|
|
261
|
+
opacity var(--dur-exit) var(--ease-out),
|
|
262
|
+
display var(--dur-exit) allow-discrete,
|
|
263
|
+
overlay var(--dur-exit) allow-discrete;
|
|
264
|
+
}
|
|
265
|
+
.modal[open]::backdrop {
|
|
266
|
+
opacity: 1;
|
|
267
|
+
transition-duration: var(--dur-base);
|
|
245
268
|
}
|
|
246
|
-
@
|
|
247
|
-
|
|
269
|
+
@starting-style {
|
|
270
|
+
.modal[open]::backdrop {
|
|
248
271
|
opacity: 0;
|
|
249
272
|
}
|
|
250
273
|
}
|
|
@@ -267,8 +290,24 @@
|
|
|
267
290
|
border: 1px solid var(--border);
|
|
268
291
|
border-radius: var(--r-lg) var(--r-lg) 0 0;
|
|
269
292
|
box-shadow: var(--shadow-lg);
|
|
270
|
-
animation: sheet-up 0.18s var(--ease);
|
|
271
293
|
padding-bottom: var(--safe-bottom);
|
|
294
|
+
/* Bottom sheet: slides up on enter, down on exit. */
|
|
295
|
+
opacity: 0;
|
|
296
|
+
translate: 0 100%;
|
|
297
|
+
transition:
|
|
298
|
+
opacity var(--dur-exit-slow) var(--ease-out),
|
|
299
|
+
translate var(--dur-exit-slow) var(--ease-drawer);
|
|
300
|
+
}
|
|
301
|
+
.modal[open] .sheet {
|
|
302
|
+
opacity: 1;
|
|
303
|
+
translate: 0;
|
|
304
|
+
transition-duration: var(--dur-sheet);
|
|
305
|
+
}
|
|
306
|
+
@starting-style {
|
|
307
|
+
.modal[open] .sheet {
|
|
308
|
+
opacity: 0;
|
|
309
|
+
translate: 0 100%;
|
|
310
|
+
}
|
|
272
311
|
}
|
|
273
312
|
@media (min-width: 640px) {
|
|
274
313
|
.sheet {
|
|
@@ -276,6 +315,22 @@
|
|
|
276
315
|
padding-bottom: 0;
|
|
277
316
|
width: var(--sheet-w, var(--sw));
|
|
278
317
|
max-width: min(var(--sheet-w, var(--sw)), calc(100vw - 2rem));
|
|
318
|
+
/* Centred dialog: scales from the middle instead of sliding. */
|
|
319
|
+
translate: 0;
|
|
320
|
+
scale: var(--enter-scale);
|
|
321
|
+
transition:
|
|
322
|
+
opacity var(--dur-exit) var(--ease-out),
|
|
323
|
+
scale var(--dur-exit) var(--ease-out);
|
|
324
|
+
}
|
|
325
|
+
.modal[open] .sheet {
|
|
326
|
+
scale: 1;
|
|
327
|
+
transition-duration: var(--dur-slow);
|
|
328
|
+
}
|
|
329
|
+
@starting-style {
|
|
330
|
+
.modal[open] .sheet {
|
|
331
|
+
translate: 0;
|
|
332
|
+
scale: var(--enter-scale);
|
|
333
|
+
}
|
|
279
334
|
}
|
|
280
335
|
.sheet-sm {
|
|
281
336
|
--sw: 24rem;
|
|
@@ -287,18 +342,6 @@
|
|
|
287
342
|
--sw: 72rem;
|
|
288
343
|
}
|
|
289
344
|
}
|
|
290
|
-
@keyframes sheet-up {
|
|
291
|
-
from {
|
|
292
|
-
transform: translateY(8%);
|
|
293
|
-
opacity: 0.4;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
@media (prefers-reduced-motion: reduce) {
|
|
297
|
-
.modal::backdrop,
|
|
298
|
-
.sheet {
|
|
299
|
-
animation: none;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
345
|
.modal.resizing .sheet {
|
|
303
346
|
user-select: none;
|
|
304
347
|
/* Make per-frame width changes cheap to paint: hint the animated property,
|
|
@@ -322,6 +365,7 @@
|
|
|
322
365
|
.sheet-tone-icon {
|
|
323
366
|
display: inline-flex;
|
|
324
367
|
flex: none;
|
|
368
|
+
font-size: var(--icon-lg);
|
|
325
369
|
color: var(--modal-tone);
|
|
326
370
|
}
|
|
327
371
|
.sheet-title {
|
|
@@ -372,9 +416,9 @@
|
|
|
372
416
|
bottom: 0;
|
|
373
417
|
right: 6px;
|
|
374
418
|
width: 2px;
|
|
375
|
-
border-radius:
|
|
419
|
+
border-radius: var(--r-pill);
|
|
376
420
|
background: transparent;
|
|
377
|
-
transition: background
|
|
421
|
+
transition: background-color var(--dur-fast) var(--ease);
|
|
378
422
|
}
|
|
379
423
|
.sheet-resize::after {
|
|
380
424
|
content: '';
|
|
@@ -384,17 +428,23 @@
|
|
|
384
428
|
transform: translateY(-50%);
|
|
385
429
|
width: 4px;
|
|
386
430
|
height: 44px;
|
|
387
|
-
border-radius:
|
|
431
|
+
border-radius: var(--r-pill);
|
|
388
432
|
background: var(--border-strong);
|
|
389
|
-
transition: background
|
|
433
|
+
transition: background-color var(--dur-fast) var(--ease);
|
|
390
434
|
}
|
|
391
|
-
.sheet-resize:hover::before,
|
|
392
435
|
.modal.resizing .sheet-resize::before {
|
|
393
436
|
background: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
394
437
|
}
|
|
395
|
-
.sheet-resize:hover::after,
|
|
396
438
|
.modal.resizing .sheet-resize::after {
|
|
397
439
|
background: var(--accent);
|
|
398
440
|
}
|
|
399
441
|
}
|
|
442
|
+
@media (min-width: 640px) and (hover: hover) and (pointer: fine) {
|
|
443
|
+
.sheet-resize:hover::before {
|
|
444
|
+
background: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
445
|
+
}
|
|
446
|
+
.sheet-resize:hover::after {
|
|
447
|
+
background: var(--accent);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
400
450
|
</style>
|
|
@@ -30,6 +30,8 @@ type $$ComponentProps = {
|
|
|
30
30
|
bodyClass?: string;
|
|
31
31
|
/** Cap the sheet height; the body scrolls inside it. */
|
|
32
32
|
maxHeight?: string;
|
|
33
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
34
|
+
sound?: boolean;
|
|
33
35
|
};
|
|
34
36
|
declare const Modal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
35
37
|
type Modal = ReturnType<typeof Modal>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// own <button> + scoped styles rather than specializing the Button atom.
|
|
10
10
|
import type { Snippet } from 'svelte';
|
|
11
11
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
12
|
+
import { playSound } from '../../sound';
|
|
12
13
|
|
|
13
14
|
let {
|
|
14
15
|
selected = false,
|
|
@@ -22,6 +23,8 @@
|
|
|
22
23
|
onfocuschange,
|
|
23
24
|
onfocus,
|
|
24
25
|
onpointerenter,
|
|
26
|
+
onclick,
|
|
27
|
+
sound = true,
|
|
25
28
|
'aria-describedby': describedBy,
|
|
26
29
|
class: klass = '',
|
|
27
30
|
children,
|
|
@@ -41,6 +44,8 @@
|
|
|
41
44
|
align?: 'center' | 'start';
|
|
42
45
|
/** Stretch to fill the container width (full-width list/menu row). */
|
|
43
46
|
block?: boolean;
|
|
47
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
48
|
+
sound?: boolean;
|
|
44
49
|
children?: Snippet;
|
|
45
50
|
} = $props();
|
|
46
51
|
|
|
@@ -68,6 +73,11 @@
|
|
|
68
73
|
onpointerenter?.(e);
|
|
69
74
|
activate();
|
|
70
75
|
}}
|
|
76
|
+
onclick={(e) => {
|
|
77
|
+
if (disabled) return;
|
|
78
|
+
playSound('click', sound);
|
|
79
|
+
onclick?.(e);
|
|
80
|
+
}}
|
|
71
81
|
class="opt {klass}"
|
|
72
82
|
class:row
|
|
73
83
|
class:align-start={align === 'start'}
|
|
@@ -83,7 +93,7 @@
|
|
|
83
93
|
.opt {
|
|
84
94
|
display: flex;
|
|
85
95
|
flex-direction: column;
|
|
86
|
-
gap:
|
|
96
|
+
gap: var(--sp-0-5);
|
|
87
97
|
padding: var(--sp-2);
|
|
88
98
|
background: var(--bg);
|
|
89
99
|
border: 1px solid var(--border-strong);
|
|
@@ -92,17 +102,20 @@
|
|
|
92
102
|
text-align: left;
|
|
93
103
|
white-space: normal;
|
|
94
104
|
user-select: none;
|
|
105
|
+
--opt-scale-dur: var(--dur-fast);
|
|
95
106
|
transition:
|
|
96
|
-
background
|
|
97
|
-
border-color
|
|
98
|
-
color
|
|
107
|
+
background var(--dur-fast) ease,
|
|
108
|
+
border-color var(--dur-fast) ease,
|
|
109
|
+
color var(--dur-fast) ease,
|
|
110
|
+
scale var(--opt-scale-dur) var(--ease-out);
|
|
99
111
|
}
|
|
100
112
|
.opt:disabled {
|
|
101
|
-
opacity:
|
|
113
|
+
opacity: var(--disabled-opacity);
|
|
102
114
|
cursor: not-allowed;
|
|
103
115
|
}
|
|
104
|
-
.opt:
|
|
105
|
-
|
|
116
|
+
.opt:active:not(:disabled) {
|
|
117
|
+
--opt-scale-dur: var(--dur-press);
|
|
118
|
+
scale: var(--press-scale);
|
|
106
119
|
}
|
|
107
120
|
.opt.row {
|
|
108
121
|
flex-direction: row;
|
|
@@ -140,8 +153,13 @@
|
|
|
140
153
|
background: color-mix(in srgb, var(--oc) 14%, var(--bg));
|
|
141
154
|
color: var(--oc);
|
|
142
155
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
@media (hover: hover) and (pointer: fine) {
|
|
157
|
+
.opt:hover:not(:disabled):not(.selected) {
|
|
158
|
+
border-color: var(--border-strong);
|
|
159
|
+
}
|
|
160
|
+
.opt.selected:hover:not(:disabled) {
|
|
161
|
+
border-color: var(--oc);
|
|
162
|
+
background: color-mix(in srgb, var(--oc) 20%, var(--bg));
|
|
163
|
+
}
|
|
146
164
|
}
|
|
147
165
|
</style>
|
|
@@ -15,6 +15,8 @@ type $$ComponentProps = Omit<HTMLButtonAttributes, 'value'> & {
|
|
|
15
15
|
align?: 'center' | 'start';
|
|
16
16
|
/** Stretch to fill the container width (full-width list/menu row). */
|
|
17
17
|
block?: boolean;
|
|
18
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
19
|
+
sound?: boolean;
|
|
18
20
|
children?: Snippet;
|
|
19
21
|
};
|
|
20
22
|
declare const OptionButton: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// `offset` written back). Numbered buttons collapse to prev / "3 / 12" /
|
|
6
6
|
// next when the container is narrower than 24rem.
|
|
7
7
|
import Text from '../atoms/Text.svelte';
|
|
8
|
+
import { playSound } from '../../sound';
|
|
8
9
|
import IconButton from './IconButton.svelte';
|
|
9
10
|
|
|
10
11
|
let {
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
shrink = true,
|
|
23
24
|
block = false,
|
|
24
25
|
label = 'Pagination',
|
|
26
|
+
sound = true,
|
|
25
27
|
class: klass = ''
|
|
26
28
|
}: {
|
|
27
29
|
/** 1-based current page. */
|
|
@@ -46,6 +48,8 @@
|
|
|
46
48
|
/** Full-width block. */
|
|
47
49
|
block?: boolean;
|
|
48
50
|
label?: string;
|
|
51
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
52
|
+
sound?: boolean;
|
|
49
53
|
class?: string;
|
|
50
54
|
} = $props();
|
|
51
55
|
|
|
@@ -96,6 +100,7 @@
|
|
|
96
100
|
function go(p: number) {
|
|
97
101
|
const next = Math.min(count, Math.max(1, p));
|
|
98
102
|
if (next === current) return;
|
|
103
|
+
playSound('click', sound);
|
|
99
104
|
page = next;
|
|
100
105
|
if (offsetMode) offset = (next - 1) * (limit as number);
|
|
101
106
|
onchange?.(next);
|
|
@@ -116,6 +121,7 @@
|
|
|
116
121
|
label="Previous page"
|
|
117
122
|
box={size === 'sm' ? 'sm' : 'md'}
|
|
118
123
|
disabled={current <= 1}
|
|
124
|
+
sound={false}
|
|
119
125
|
onclick={() => go(current - 1)}
|
|
120
126
|
/>
|
|
121
127
|
<ol class="pages">
|
|
@@ -144,6 +150,7 @@
|
|
|
144
150
|
label="Next page"
|
|
145
151
|
box={size === 'sm' ? 'sm' : 'md'}
|
|
146
152
|
disabled={current >= count}
|
|
153
|
+
sound={false}
|
|
147
154
|
onclick={() => go(current + 1)}
|
|
148
155
|
/>
|
|
149
156
|
{#if showRange}
|
|
@@ -181,21 +188,30 @@
|
|
|
181
188
|
.page {
|
|
182
189
|
min-width: var(--box-md);
|
|
183
190
|
height: var(--box-md);
|
|
184
|
-
padding: 0 var(--
|
|
191
|
+
padding: 0 var(--control-px-md);
|
|
185
192
|
border: 1px solid transparent;
|
|
186
|
-
border-radius:
|
|
193
|
+
border-radius: var(--r-md);
|
|
187
194
|
background: transparent;
|
|
188
195
|
color: var(--text-muted);
|
|
189
|
-
font-size: var(--fs-
|
|
196
|
+
font-size: var(--control-fs-md);
|
|
190
197
|
font-variant-numeric: tabular-nums;
|
|
191
198
|
cursor: pointer;
|
|
199
|
+
--page-scale-dur: var(--dur-fast);
|
|
192
200
|
transition:
|
|
193
|
-
background
|
|
194
|
-
color
|
|
201
|
+
background var(--dur-fast) ease,
|
|
202
|
+
border-color var(--dur-fast) ease,
|
|
203
|
+
color var(--dur-fast) ease,
|
|
204
|
+
scale var(--page-scale-dur) var(--ease-out);
|
|
205
|
+
}
|
|
206
|
+
.page:active {
|
|
207
|
+
--page-scale-dur: var(--dur-press);
|
|
208
|
+
scale: var(--press-scale);
|
|
195
209
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
@media (hover: hover) and (pointer: fine) {
|
|
211
|
+
.page:hover {
|
|
212
|
+
background: var(--bg-elevated-2);
|
|
213
|
+
color: var(--text);
|
|
214
|
+
}
|
|
199
215
|
}
|
|
200
216
|
.page.on {
|
|
201
217
|
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
@@ -206,7 +222,8 @@
|
|
|
206
222
|
.pagination-sm .page {
|
|
207
223
|
min-width: var(--box-sm);
|
|
208
224
|
height: var(--box-sm);
|
|
209
|
-
|
|
225
|
+
padding: 0 var(--control-px-sm);
|
|
226
|
+
font-size: var(--control-fs-sm);
|
|
210
227
|
}
|
|
211
228
|
.ellipsis {
|
|
212
229
|
display: inline-flex;
|
|
@@ -22,6 +22,8 @@ type $$ComponentProps = {
|
|
|
22
22
|
/** Full-width block. */
|
|
23
23
|
block?: boolean;
|
|
24
24
|
label?: string;
|
|
25
|
+
/** Play UI sounds when enabled globally; false silences this instance. */
|
|
26
|
+
sound?: boolean;
|
|
25
27
|
class?: string;
|
|
26
28
|
};
|
|
27
29
|
declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "offset" | "page">;
|