@dorsk/tsumikit 0.38.1 → 0.39.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/dist/autoresize.d.ts +2 -0
- package/dist/autoresize.js +15 -1
- package/dist/components/atoms/Artwork.svelte +19 -18
- package/dist/components/atoms/Artwork.svelte.d.ts +3 -2
- package/dist/components/atoms/Badge.svelte +60 -26
- package/dist/components/atoms/Badge.svelte.d.ts +14 -3
- package/dist/components/atoms/Button.svelte +30 -12
- package/dist/components/atoms/Button.svelte.d.ts +4 -2
- package/dist/components/atoms/Card.svelte +47 -30
- package/dist/components/atoms/Card.svelte.d.ts +12 -3
- package/dist/components/atoms/Checkbox.svelte +16 -2
- package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/atoms/Dot.svelte +17 -11
- package/dist/components/atoms/Dot.svelte.d.ts +5 -2
- package/dist/components/atoms/Gauge.svelte +19 -18
- package/dist/components/atoms/Gauge.svelte.d.ts +3 -2
- package/dist/components/atoms/Heading.svelte +20 -19
- package/dist/components/atoms/Heading.svelte.d.ts +3 -2
- package/dist/components/atoms/Icon.svelte +33 -13
- package/dist/components/atoms/Icon.svelte.d.ts +17 -2
- package/dist/components/atoms/Input.svelte +17 -3
- package/dist/components/atoms/Input.svelte.d.ts +2 -1
- package/dist/components/atoms/Kbd.svelte +25 -3
- package/dist/components/atoms/Kbd.svelte.d.ts +5 -1
- package/dist/components/atoms/Link.svelte +16 -12
- package/dist/components/atoms/Link.svelte.d.ts +6 -2
- package/dist/components/atoms/Progress.svelte +119 -4
- package/dist/components/atoms/Progress.svelte.d.ts +28 -2
- package/dist/components/atoms/Scrim.svelte +7 -2
- package/dist/components/atoms/Scrim.svelte.d.ts +2 -0
- package/dist/components/atoms/SegmentedProgress.svelte +14 -2
- package/dist/components/atoms/SegmentedProgress.svelte.d.ts +5 -2
- package/dist/components/atoms/Select.svelte +18 -6
- package/dist/components/atoms/Select.svelte.d.ts +3 -3
- package/dist/components/atoms/Skeleton.svelte +12 -11
- package/dist/components/atoms/Skeleton.svelte.d.ts +3 -2
- package/dist/components/atoms/Slider.svelte +45 -3
- package/dist/components/atoms/Slider.svelte.d.ts +7 -0
- package/dist/components/atoms/Spinner.svelte +2 -0
- package/dist/components/atoms/Spinner.svelte.d.ts +2 -0
- package/dist/components/atoms/Switch.svelte +70 -18
- package/dist/components/atoms/Switch.svelte.d.ts +5 -1
- package/dist/components/atoms/Text.svelte +39 -26
- package/dist/components/atoms/Text.svelte.d.ts +7 -3
- package/dist/components/atoms/Textarea.svelte +47 -10
- package/dist/components/atoms/Textarea.svelte.d.ts +10 -3
- package/dist/components/layouts/AppShell.svelte +7 -3
- package/dist/components/layouts/AppShell.svelte.d.ts +2 -0
- package/dist/components/layouts/AutoGrid.svelte +4 -0
- package/dist/components/layouts/AutoGrid.svelte.d.ts +2 -0
- package/dist/components/layouts/Cluster.svelte +62 -1
- package/dist/components/layouts/Cluster.svelte.d.ts +9 -0
- package/dist/components/layouts/NavItem.svelte +22 -1
- package/dist/components/layouts/NavItem.svelte.d.ts +5 -0
- package/dist/components/layouts/NavSection.svelte +4 -1
- package/dist/components/layouts/NavSection.svelte.d.ts +2 -0
- package/dist/components/layouts/ResizablePanel.svelte +14 -3
- package/dist/components/layouts/ResizablePanel.svelte.d.ts +5 -0
- package/dist/components/layouts/Stack.svelte +41 -0
- package/dist/components/layouts/Stack.svelte.d.ts +9 -0
- package/dist/components/layouts/Toolbar.svelte +122 -0
- package/dist/components/layouts/Toolbar.svelte.d.ts +22 -0
- package/dist/components/molecules/AttachmentList.svelte +59 -0
- package/dist/components/molecules/AttachmentList.svelte.d.ts +13 -0
- package/dist/components/molecules/Breadcrumb.svelte +6 -2
- package/dist/components/molecules/Breadcrumb.svelte.d.ts +2 -0
- package/dist/components/molecules/Callout.svelte +10 -4
- package/dist/components/molecules/Callout.svelte.d.ts +1 -1
- package/dist/components/molecules/CapBar.svelte +5 -1
- package/dist/components/molecules/ChatBubble.svelte +158 -0
- package/dist/components/molecules/ChatBubble.svelte.d.ts +26 -0
- package/dist/components/molecules/CodeBlock.svelte +36 -3
- package/dist/components/molecules/CodeBlock.svelte.d.ts +8 -0
- package/dist/components/molecules/Composer.svelte +195 -0
- package/dist/components/molecules/Composer.svelte.d.ts +27 -0
- package/dist/components/molecules/ConfirmModal.svelte +15 -5
- package/dist/components/molecules/ConfirmModal.svelte.d.ts +5 -1
- package/dist/components/molecules/Dropzone.svelte +9 -3
- package/dist/components/molecules/Dropzone.svelte.d.ts +2 -0
- package/dist/components/molecules/EmptyState.svelte +9 -5
- package/dist/components/molecules/EmptyState.svelte.d.ts +3 -1
- package/dist/components/molecules/Field.svelte +7 -1
- package/dist/components/molecules/Field.svelte.d.ts +2 -0
- package/dist/components/molecules/FileButton.svelte +2 -1
- package/dist/components/molecules/FileButton.svelte.d.ts +2 -1
- package/dist/components/molecules/FilterInput.svelte +13 -3
- package/dist/components/molecules/FilterInput.svelte.d.ts +4 -1
- package/dist/components/molecules/IconButton.svelte +32 -7
- package/dist/components/molecules/IconButton.svelte.d.ts +9 -1
- package/dist/components/molecules/KeyValue.svelte +4 -1
- package/dist/components/molecules/KeyValue.svelte.d.ts +2 -1
- package/dist/components/molecules/Menu.svelte +50 -5
- package/dist/components/molecules/Menu.svelte.d.ts +10 -1
- package/dist/components/molecules/Meter.svelte +91 -0
- package/dist/components/molecules/Meter.svelte.d.ts +29 -0
- package/dist/components/molecules/Metric.svelte +134 -8
- package/dist/components/molecules/Metric.svelte.d.ts +20 -2
- package/dist/components/molecules/Modal.svelte +26 -12
- package/dist/components/molecules/Modal.svelte.d.ts +7 -1
- package/dist/components/molecules/Pagination.svelte +25 -1
- package/dist/components/molecules/Pagination.svelte.d.ts +9 -2
- package/dist/components/molecules/Popover.svelte +14 -3
- package/dist/components/molecules/Popover.svelte.d.ts +5 -0
- package/dist/components/molecules/Prose.svelte +140 -0
- package/dist/components/molecules/Prose.svelte.d.ts +13 -0
- package/dist/components/molecules/RadioGroup.svelte +42 -2
- package/dist/components/molecules/RadioGroup.svelte.d.ts +4 -1
- package/dist/components/molecules/SectionHeader.svelte +5 -2
- package/dist/components/molecules/SectionHeader.svelte.d.ts +1 -1
- package/dist/components/molecules/SegmentedControl.svelte +36 -1
- package/dist/components/molecules/SegmentedControl.svelte.d.ts +11 -1
- package/dist/components/molecules/SelectButton.svelte +64 -4
- package/dist/components/molecules/SelectButton.svelte.d.ts +18 -1
- package/dist/components/molecules/Tabs.svelte +21 -3
- package/dist/components/molecules/Tabs.svelte.d.ts +6 -0
- package/dist/components/molecules/Timestamp.svelte +13 -6
- package/dist/components/molecules/Timestamp.svelte.d.ts +4 -0
- package/dist/components/molecules/Toaster.svelte +54 -38
- package/dist/components/molecules/Toaster.svelte.d.ts +5 -1
- package/dist/components/molecules/Toggle.svelte +2 -1
- package/dist/components/molecules/Toggle.svelte.d.ts +2 -1
- package/dist/components/molecules/Tooltip.svelte +32 -5
- package/dist/components/molecules/Tooltip.svelte.d.ts +9 -0
- package/dist/components/molecules/Truncate.svelte +156 -18
- package/dist/components/molecules/Truncate.svelte.d.ts +17 -1
- package/dist/components/molecules/WorkingDir.svelte +6 -0
- package/dist/components/molecules/WorkingDir.svelte.d.ts +2 -0
- package/dist/components/organisms/DataTable.svelte +38 -10
- package/dist/components/organisms/DataTable.svelte.d.ts +4 -0
- package/dist/components/organisms/FilterSearchBar.svelte +9 -2
- package/dist/components/organisms/FilterSearchBar.svelte.d.ts +4 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +8 -1
- package/dist/size.d.ts +2 -0
- package/dist/size.js +1 -0
- package/dist/styles/tokens.css +3 -0
- package/dist/timestamp.d.ts +2 -2
- package/dist/timestamp.js +4 -4
- package/dist/tone.d.ts +1 -1
- package/dist/tone.js +1 -1
- package/dist/truncate.d.ts +13 -1
- package/dist/truncate.js +39 -0
- package/package.json +5 -3
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { type Tone } from '../../tone';
|
|
1
2
|
import type { Snippet } from 'svelte';
|
|
2
3
|
import { type IconName } from '../atoms/Icon.svelte';
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
title?: string;
|
|
5
6
|
description?: string | Snippet;
|
|
6
7
|
icon?: IconName;
|
|
7
|
-
tone?:
|
|
8
|
+
tone?: Tone;
|
|
8
9
|
actionLabel?: string;
|
|
9
10
|
onAction?: () => void;
|
|
11
|
+
/** @deprecated use `size="compact"`; `size` wins when both are set. */
|
|
10
12
|
compact?: boolean;
|
|
11
13
|
size?: 'inline' | 'compact' | 'default';
|
|
12
14
|
loading?: boolean;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
layout = 'stack',
|
|
18
18
|
labelWidth,
|
|
19
19
|
grow = false,
|
|
20
|
+
shrink = true,
|
|
20
21
|
class: klass = '',
|
|
21
22
|
children
|
|
22
23
|
}: {
|
|
@@ -29,6 +30,8 @@
|
|
|
29
30
|
labelWidth?: string;
|
|
30
31
|
/** Fill the available width of a flex/Cluster row (flex: 1). */
|
|
31
32
|
grow?: boolean;
|
|
33
|
+
/** `false` pins the field (`flex: none`) in a row. */
|
|
34
|
+
shrink?: boolean;
|
|
32
35
|
class?: string;
|
|
33
36
|
children?: Snippet;
|
|
34
37
|
} = $props();
|
|
@@ -54,7 +57,7 @@
|
|
|
54
57
|
});
|
|
55
58
|
</script>
|
|
56
59
|
|
|
57
|
-
<div class="field {klass}" class:field-grow={grow} class:field-inline={layout === 'inline'} data-tsu="Field">
|
|
60
|
+
<div class="field {klass}" class:field-grow={grow} class:field-no-shrink={!shrink} class:field-inline={layout === 'inline'} data-tsu="Field">
|
|
58
61
|
{#if label}
|
|
59
62
|
<label class="label" for={forId} style:width={labelWidth}>{label}</label>
|
|
60
63
|
{/if}
|
|
@@ -75,6 +78,9 @@
|
|
|
75
78
|
flex: 1 1 0;
|
|
76
79
|
min-width: 0;
|
|
77
80
|
}
|
|
81
|
+
.field-no-shrink {
|
|
82
|
+
flex: none;
|
|
83
|
+
}
|
|
78
84
|
.field-inline {
|
|
79
85
|
flex-direction: row;
|
|
80
86
|
align-items: center;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { ControlSize } from '../../size';
|
|
2
3
|
// File-picker button. A real <input type="file"> visually hidden inside a
|
|
3
4
|
// <label> styled as a button — so it's keyboard-focusable and works with zero
|
|
4
5
|
// JS to open the dialog. Emits the chosen files via `onfiles`. Dependency-free.
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
variant?: 'default' | 'primary' | 'ghost';
|
|
39
40
|
/** Match the Button atom's sizes so a FileButton lines up with buttons in
|
|
40
41
|
* the same row. */
|
|
41
|
-
size?:
|
|
42
|
+
size?: ControlSize;
|
|
42
43
|
/** Uniform `--control-height`, like Button's `control`, so it lines up in a
|
|
43
44
|
* composer/toolbar row. */
|
|
44
45
|
control?: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ControlSize } from '../../size';
|
|
1
2
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
2
3
|
type $$ComponentProps = {
|
|
3
4
|
onfiles: (files: File[]) => void;
|
|
@@ -17,7 +18,7 @@ type $$ComponentProps = {
|
|
|
17
18
|
variant?: 'default' | 'primary' | 'ghost';
|
|
18
19
|
/** Match the Button atom's sizes so a FileButton lines up with buttons in
|
|
19
20
|
* the same row. */
|
|
20
|
-
size?:
|
|
21
|
+
size?: ControlSize;
|
|
21
22
|
/** Uniform `--control-height`, like Button's `control`, so it lines up in a
|
|
22
23
|
* composer/toolbar row. */
|
|
23
24
|
control?: boolean;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<script lang="ts">
|
|
26
|
+
import type { ControlSize } from '../../size';
|
|
26
27
|
// ───────────────────────────────────────────────────────────────────────
|
|
27
28
|
// FilterInput primitive — the headless base of the structured search bar.
|
|
28
29
|
//
|
|
@@ -64,7 +65,9 @@
|
|
|
64
65
|
onchange,
|
|
65
66
|
onsubmit,
|
|
66
67
|
inline,
|
|
67
|
-
below
|
|
68
|
+
below,
|
|
69
|
+
class: klass = '',
|
|
70
|
+
style: styleProp = '',
|
|
68
71
|
}: {
|
|
69
72
|
schema: Schema;
|
|
70
73
|
/** The raw textual query (two-way bindable). */
|
|
@@ -81,7 +84,7 @@
|
|
|
81
84
|
/** Leading icon; pass `null` to hide it. */
|
|
82
85
|
icon?: IconName | null;
|
|
83
86
|
/** `sm` renders a compact-toolbar bar (`--control-height-compact`). */
|
|
84
|
-
size?:
|
|
87
|
+
size?: ControlSize;
|
|
85
88
|
shape?: 'square' | 'pill';
|
|
86
89
|
/** Theme-aware surface shade of the bar (mirrors Card). */
|
|
87
90
|
surface?: 'base' | 'raised' | 'sunken';
|
|
@@ -110,6 +113,8 @@
|
|
|
110
113
|
* passes its chip row here; single-field hosts simply omit it.
|
|
111
114
|
*/
|
|
112
115
|
below?: Snippet<[FilterInputContext]>;
|
|
116
|
+
class?: string;
|
|
117
|
+
style?: string;
|
|
113
118
|
} = $props();
|
|
114
119
|
|
|
115
120
|
const field = getFieldContext();
|
|
@@ -306,7 +311,8 @@
|
|
|
306
311
|
</script>
|
|
307
312
|
|
|
308
313
|
<div
|
|
309
|
-
class="fi"
|
|
314
|
+
class="fi {klass}"
|
|
315
|
+
style={styleProp}
|
|
310
316
|
class:fi--sm={size === 'sm'}
|
|
311
317
|
class:fi--pill={shape === 'pill'}
|
|
312
318
|
class:surface-raised={surface === 'raised'}
|
|
@@ -436,6 +442,10 @@
|
|
|
436
442
|
border-color: var(--accent);
|
|
437
443
|
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
438
444
|
}
|
|
445
|
+
.fi__bar:has(:focus-visible) {
|
|
446
|
+
outline: var(--focus-ring);
|
|
447
|
+
outline-offset: var(--focus-ring-offset);
|
|
448
|
+
}
|
|
439
449
|
.fi__icon {
|
|
440
450
|
display: flex;
|
|
441
451
|
color: var(--text-faint);
|
|
@@ -18,6 +18,7 @@ export interface FilterInputContext {
|
|
|
18
18
|
/** Focus the underlying input. */
|
|
19
19
|
focus: () => void;
|
|
20
20
|
}
|
|
21
|
+
import type { ControlSize } from '../../size';
|
|
21
22
|
import type { Snippet } from 'svelte';
|
|
22
23
|
import { type IconName } from '../atoms/Icon.svelte';
|
|
23
24
|
import { type Schema } from '../../query/schema';
|
|
@@ -37,7 +38,7 @@ type $$ComponentProps = {
|
|
|
37
38
|
/** Leading icon; pass `null` to hide it. */
|
|
38
39
|
icon?: IconName | null;
|
|
39
40
|
/** `sm` renders a compact-toolbar bar (`--control-height-compact`). */
|
|
40
|
-
size?:
|
|
41
|
+
size?: ControlSize;
|
|
41
42
|
shape?: 'square' | 'pill';
|
|
42
43
|
/** Theme-aware surface shade of the bar (mirrors Card). */
|
|
43
44
|
surface?: 'base' | 'raised' | 'sunken';
|
|
@@ -66,6 +67,8 @@ type $$ComponentProps = {
|
|
|
66
67
|
* passes its chip row here; single-field hosts simply omit it.
|
|
67
68
|
*/
|
|
68
69
|
below?: Snippet<[FilterInputContext]>;
|
|
70
|
+
class?: string;
|
|
71
|
+
style?: string;
|
|
69
72
|
};
|
|
70
73
|
declare const FilterInput: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
71
74
|
type FilterInput = ReturnType<typeof FilterInput>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Tone } from '../../tone';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
5
|
import Button from '../atoms/Button.svelte';
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
// Forwarded to Button.
|
|
23
24
|
shrink?: boolean;
|
|
24
25
|
// Semantic state tint (forwarded to Button). Pairs well with `chip`.
|
|
25
|
-
tone?:
|
|
26
|
+
tone?: Tone | 'none';
|
|
26
27
|
// Outlined `--box-lg` square icon-chip (header/toolbar actions).
|
|
27
28
|
chip?: boolean;
|
|
28
29
|
// Shared square box scale (`--box-xs/sm/md/lg`); default `md` is the classic
|
|
@@ -48,6 +49,13 @@
|
|
|
48
49
|
// open-on-GitHub action. Forwarded to Button; `href` implies `as="a"`.
|
|
49
50
|
as?: 'button' | 'a';
|
|
50
51
|
href?: string;
|
|
52
|
+
/** Render `label` as visible text beside the glyph. `row` is a full-width,
|
|
53
|
+
* left-aligned menu-row form for overflow menus. */
|
|
54
|
+
showLabel?: boolean | 'row';
|
|
55
|
+
/** Rotate the glyph (refresh in flight). */
|
|
56
|
+
spin?: boolean;
|
|
57
|
+
/** Forwarded to Button: spinner, blocks clicks, aria-busy. */
|
|
58
|
+
loading?: boolean;
|
|
51
59
|
class?: string;
|
|
52
60
|
};
|
|
53
61
|
|
|
@@ -69,6 +77,9 @@
|
|
|
69
77
|
inline = false,
|
|
70
78
|
hoverDanger = false,
|
|
71
79
|
pressed,
|
|
80
|
+
showLabel = false,
|
|
81
|
+
spin = false,
|
|
82
|
+
loading = false,
|
|
72
83
|
disabled = false,
|
|
73
84
|
onclick,
|
|
74
85
|
class: klass = '',
|
|
@@ -94,36 +105,50 @@
|
|
|
94
105
|
{box}
|
|
95
106
|
{hitArea}
|
|
96
107
|
{disabled}
|
|
108
|
+
{loading}
|
|
97
109
|
{title}
|
|
98
110
|
{onclick}
|
|
99
|
-
icon={!inline && !chip}
|
|
111
|
+
icon={!inline && !chip && !showLabel}
|
|
100
112
|
iconInline={inline}
|
|
113
|
+
block={showLabel === 'row'}
|
|
101
114
|
{hoverDanger}
|
|
102
115
|
aria-pressed={pressed}
|
|
103
|
-
class={klass}
|
|
116
|
+
class="{showLabel ? 'ib-labelled' : ''} {showLabel === 'row' ? 'ib-row' : ''} {klass}"
|
|
104
117
|
aria-label={label}
|
|
105
118
|
>
|
|
106
119
|
{#if children}
|
|
107
120
|
{#if glyphCss}
|
|
108
|
-
<span class="glyph" style="font-size: {glyphCss}"><Icon>{@render children()}</Icon></span>
|
|
121
|
+
<span class="glyph" style="font-size: {glyphCss}"><Icon {spin}>{@render children()}</Icon></span>
|
|
109
122
|
{:else}
|
|
110
|
-
<Icon {size}>{@render children()}</Icon>
|
|
123
|
+
<Icon {size} {spin}>{@render children()}</Icon>
|
|
111
124
|
{/if}
|
|
112
125
|
{:else if emoji}
|
|
113
126
|
<span class="emoji" style="font-size: {emojiCss}" aria-hidden="true">{emoji}</span>
|
|
114
127
|
{:else if icon}
|
|
115
128
|
{#if glyphCss}
|
|
116
|
-
<span class="glyph" style="font-size: {glyphCss}"><Icon name={icon} /></span>
|
|
129
|
+
<span class="glyph" style="font-size: {glyphCss}"><Icon name={icon} {spin} /></span>
|
|
117
130
|
{:else}
|
|
118
|
-
<Icon name={icon} {size} />
|
|
131
|
+
<Icon name={icon} {size} {spin} />
|
|
119
132
|
{/if}
|
|
120
133
|
{/if}
|
|
134
|
+
{#if showLabel}<span class="ib-label">{label}</span>{/if}
|
|
121
135
|
</Button>
|
|
122
136
|
|
|
123
137
|
<style>
|
|
124
138
|
/* Off-registry glyph (emoji) rendered as text rather than an SVG. Sized off the
|
|
125
139
|
`size` prop (×1.35, since an emoji reads small next to an SVG glyph of the
|
|
126
140
|
same px) unless `glyphSize` is exact; centered so it shares the tap target. */
|
|
141
|
+
.ib-label {
|
|
142
|
+
white-space: nowrap;
|
|
143
|
+
}
|
|
144
|
+
:global(.btn.ib-labelled) {
|
|
145
|
+
gap: var(--sp-2);
|
|
146
|
+
}
|
|
147
|
+
:global(.btn.ib-row) {
|
|
148
|
+
justify-content: flex-start;
|
|
149
|
+
width: 100%;
|
|
150
|
+
text-align: left;
|
|
151
|
+
}
|
|
127
152
|
.emoji,
|
|
128
153
|
.glyph {
|
|
129
154
|
display: inline-flex;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Tone } from '../../tone';
|
|
1
2
|
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
4
|
import type { IconName } from '../atoms/Icon.svelte';
|
|
@@ -14,7 +15,7 @@ type IconButtonProps = HTMLButtonAttributes & {
|
|
|
14
15
|
variant?: 'default' | 'primary' | 'ghost' | 'danger';
|
|
15
16
|
pill?: boolean;
|
|
16
17
|
shrink?: boolean;
|
|
17
|
-
tone?:
|
|
18
|
+
tone?: Tone | 'none';
|
|
18
19
|
chip?: boolean;
|
|
19
20
|
box?: 'xs' | 'sm' | 'md' | 'lg';
|
|
20
21
|
size?: number;
|
|
@@ -25,6 +26,13 @@ type IconButtonProps = HTMLButtonAttributes & {
|
|
|
25
26
|
pressed?: boolean;
|
|
26
27
|
as?: 'button' | 'a';
|
|
27
28
|
href?: string;
|
|
29
|
+
/** Render `label` as visible text beside the glyph. `row` is a full-width,
|
|
30
|
+
* left-aligned menu-row form for overflow menus. */
|
|
31
|
+
showLabel?: boolean | 'row';
|
|
32
|
+
/** Rotate the glyph (refresh in flight). */
|
|
33
|
+
spin?: boolean;
|
|
34
|
+
/** Forwarded to Button: spinner, blocks clicks, aria-busy. */
|
|
35
|
+
loading?: boolean;
|
|
28
36
|
class?: string;
|
|
29
37
|
};
|
|
30
38
|
declare const IconButton: import("svelte").Component<IconButtonProps, {}, "">;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type { Tone } from '../../tone';
|
|
5
|
+
export type KeyValueTone = Tone;
|
|
5
6
|
|
|
6
7
|
export interface KeyValueRow {
|
|
7
8
|
label: string;
|
|
@@ -23,6 +24,8 @@
|
|
|
23
24
|
warn: 'warn',
|
|
24
25
|
danger: 'danger',
|
|
25
26
|
info: 'accent',
|
|
27
|
+
success: 'success',
|
|
28
|
+
accent: 'accent',
|
|
26
29
|
} as const;
|
|
27
30
|
|
|
28
31
|
let {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
|
|
2
|
+
import type { Tone } from '../../tone';
|
|
3
|
+
export type KeyValueTone = Tone;
|
|
3
4
|
export interface KeyValueRow {
|
|
4
5
|
label: string;
|
|
5
6
|
value: string | number | Snippet;
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
/** Free-form trailing pill ("admin", "pro", "beta"…), rendered as a Badge after the label. */
|
|
9
9
|
tag?: string;
|
|
10
10
|
tagTone?: import('svelte').ComponentProps<typeof import('../atoms/Badge.svelte').default>['tone'];
|
|
11
|
+
/** Two-state item: rendered as `menuitemcheckbox` with a check glyph when on. */
|
|
12
|
+
pressed?: boolean;
|
|
13
|
+
/** Custom row content (a rename Input, a slider…) replacing icon/label/tag. */
|
|
14
|
+
content?: import('svelte').Snippet<[MenuItem]>;
|
|
11
15
|
}
|
|
12
16
|
</script>
|
|
13
17
|
|
|
@@ -59,7 +63,11 @@
|
|
|
59
63
|
disabled,
|
|
60
64
|
gap,
|
|
61
65
|
onopen,
|
|
62
|
-
onclose
|
|
66
|
+
onclose,
|
|
67
|
+
open = $bindable(false),
|
|
68
|
+
class: klass = '',
|
|
69
|
+
style: styleProp = '',
|
|
70
|
+
panelClass = '',
|
|
63
71
|
}: {
|
|
64
72
|
label: string;
|
|
65
73
|
items: MenuItem[];
|
|
@@ -67,13 +75,28 @@
|
|
|
67
75
|
/** Custom trailing content for items that carry a `tag`. */
|
|
68
76
|
tag?: Snippet<[MenuItem]>;
|
|
69
77
|
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
78
|
+
/** Bindable open state; set it to open/close programmatically. */
|
|
79
|
+
open?: boolean;
|
|
80
|
+
class?: string;
|
|
81
|
+
style?: string;
|
|
82
|
+
panelClass?: string;
|
|
70
83
|
} & TriggerChrome = $props();
|
|
71
84
|
|
|
72
85
|
let listEl = $state<HTMLDivElement | null>(null);
|
|
73
86
|
|
|
87
|
+
$effect(() => {
|
|
88
|
+
const pop = listEl?.closest<HTMLElement>('[popover]');
|
|
89
|
+
if (!pop) return;
|
|
90
|
+
const isOpen = pop.matches(':popover-open');
|
|
91
|
+
try {
|
|
92
|
+
if (open && !isOpen) pop.showPopover();
|
|
93
|
+
else if (!open && isOpen) pop.hidePopover();
|
|
94
|
+
} catch {}
|
|
95
|
+
});
|
|
96
|
+
|
|
74
97
|
function buttons(): HTMLButtonElement[] {
|
|
75
98
|
return listEl
|
|
76
|
-
? Array.from(listEl.querySelectorAll<HTMLButtonElement>('[role="menuitem"]:not(:disabled)'))
|
|
99
|
+
? Array.from(listEl.querySelectorAll<HTMLButtonElement>('[role="menuitem"]:not(:disabled), [role="menuitemcheckbox"]:not(:disabled)'))
|
|
77
100
|
: [];
|
|
78
101
|
}
|
|
79
102
|
function focusAt(i: number) {
|
|
@@ -116,14 +139,21 @@
|
|
|
116
139
|
{control}
|
|
117
140
|
{block}
|
|
118
141
|
{triggerClass}
|
|
142
|
+
class={klass}
|
|
143
|
+
style={styleProp}
|
|
144
|
+
{panelClass}
|
|
119
145
|
{bare}
|
|
120
146
|
{hitArea}
|
|
121
147
|
{disabled}
|
|
122
148
|
{gap}
|
|
123
149
|
role="menu"
|
|
124
150
|
haspopup="menu"
|
|
125
|
-
{
|
|
151
|
+
onclose={() => {
|
|
152
|
+
open = false;
|
|
153
|
+
onclose?.();
|
|
154
|
+
}}
|
|
126
155
|
onopen={() => {
|
|
156
|
+
open = true;
|
|
127
157
|
queueMicrotask(() => focusAt(0));
|
|
128
158
|
onopen?.();
|
|
129
159
|
}}
|
|
@@ -132,14 +162,19 @@
|
|
|
132
162
|
{#each items as item (item.label)}
|
|
133
163
|
<button
|
|
134
164
|
type="button"
|
|
135
|
-
role=
|
|
165
|
+
role={item.pressed === undefined ? 'menuitem' : 'menuitemcheckbox'}
|
|
166
|
+
aria-checked={item.pressed === undefined ? undefined : item.pressed}
|
|
136
167
|
class="menu-item"
|
|
137
168
|
class:danger={item.danger}
|
|
169
|
+
class:on={item.pressed}
|
|
138
170
|
disabled={item.disabled}
|
|
139
171
|
tabindex="-1"
|
|
140
172
|
onclick={() => select(item)}
|
|
141
173
|
>
|
|
142
|
-
{#if item.
|
|
174
|
+
{#if item.content}
|
|
175
|
+
{@render item.content(item)}
|
|
176
|
+
{:else}
|
|
177
|
+
{#if item.pressed !== undefined}<span class="menu-check"><Icon name="check" /></span>{:else if item.icon}<Icon name={item.icon} />{/if}
|
|
143
178
|
<span>{item.label}</span>
|
|
144
179
|
{#if item.tag !== undefined}
|
|
145
180
|
{#if tag}
|
|
@@ -150,6 +185,7 @@
|
|
|
150
185
|
</span>
|
|
151
186
|
{/if}
|
|
152
187
|
{/if}
|
|
188
|
+
{/if}
|
|
153
189
|
</button>
|
|
154
190
|
{/each}
|
|
155
191
|
</div>
|
|
@@ -183,6 +219,15 @@
|
|
|
183
219
|
.menu-item.danger {
|
|
184
220
|
color: var(--danger);
|
|
185
221
|
}
|
|
222
|
+
.menu-check {
|
|
223
|
+
display: inline-flex;
|
|
224
|
+
width: 1em;
|
|
225
|
+
visibility: hidden;
|
|
226
|
+
}
|
|
227
|
+
.menu-item.on .menu-check {
|
|
228
|
+
visibility: visible;
|
|
229
|
+
color: var(--accent);
|
|
230
|
+
}
|
|
186
231
|
.menu-tag {
|
|
187
232
|
display: inline-flex;
|
|
188
233
|
flex: none;
|
|
@@ -7,6 +7,10 @@ export interface MenuItem {
|
|
|
7
7
|
/** Free-form trailing pill ("admin", "pro", "beta"…), rendered as a Badge after the label. */
|
|
8
8
|
tag?: string;
|
|
9
9
|
tagTone?: import('svelte').ComponentProps<typeof import('../atoms/Badge.svelte').default>['tone'];
|
|
10
|
+
/** Two-state item: rendered as `menuitemcheckbox` with a check glyph when on. */
|
|
11
|
+
pressed?: boolean;
|
|
12
|
+
/** Custom row content (a rename Input, a slider…) replacing icon/label/tag. */
|
|
13
|
+
content?: import('svelte').Snippet<[MenuItem]>;
|
|
10
14
|
}
|
|
11
15
|
import type { ComponentProps, Snippet } from 'svelte';
|
|
12
16
|
import Popover from './Popover.svelte';
|
|
@@ -19,7 +23,12 @@ type $$ComponentProps = {
|
|
|
19
23
|
/** Custom trailing content for items that carry a `tag`. */
|
|
20
24
|
tag?: Snippet<[MenuItem]>;
|
|
21
25
|
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
26
|
+
/** Bindable open state; set it to open/close programmatically. */
|
|
27
|
+
open?: boolean;
|
|
28
|
+
class?: string;
|
|
29
|
+
style?: string;
|
|
30
|
+
panelClass?: string;
|
|
22
31
|
} & TriggerChrome;
|
|
23
|
-
declare const Menu: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
32
|
+
declare const Menu: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
24
33
|
type Menu = ReturnType<typeof Menu>;
|
|
25
34
|
export default Menu;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Labelled usage meter: label row, Progress track (markers / thresholds
|
|
3
|
+
// forwarded), trailing value and an optional caption line (reset time…).
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
import Progress from '../atoms/Progress.svelte';
|
|
6
|
+
import type { ControlSize } from '../../size';
|
|
7
|
+
import type { Tone } from '../../tone';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
label,
|
|
11
|
+
value,
|
|
12
|
+
max = 100,
|
|
13
|
+
format = (v: number, m: number) => `${Math.round((v / m) * 100)}%`,
|
|
14
|
+
caption,
|
|
15
|
+
markers,
|
|
16
|
+
toneAt,
|
|
17
|
+
tone,
|
|
18
|
+
size = 'md',
|
|
19
|
+
actions,
|
|
20
|
+
class: klass = '',
|
|
21
|
+
style: styleProp = ''
|
|
22
|
+
}: {
|
|
23
|
+
label: string;
|
|
24
|
+
value: number;
|
|
25
|
+
max?: number;
|
|
26
|
+
/** Trailing readout; defaults to a percentage. */
|
|
27
|
+
format?: (value: number, max: number) => string;
|
|
28
|
+
caption?: string | Snippet;
|
|
29
|
+
markers?: { at: number; label?: string; tone?: Tone }[];
|
|
30
|
+
toneAt?: { warn?: number; danger?: number };
|
|
31
|
+
tone?: Tone;
|
|
32
|
+
size?: ControlSize;
|
|
33
|
+
/** Inline controls after the readout (edit cap, bypass…). */
|
|
34
|
+
actions?: Snippet;
|
|
35
|
+
class?: string;
|
|
36
|
+
style?: string;
|
|
37
|
+
} = $props();
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<div data-tsu="Meter" class="meter size-{size} {klass}" style={styleProp}>
|
|
41
|
+
<div class="head">
|
|
42
|
+
<span class="label">{label}</span>
|
|
43
|
+
<span class="readout">{format(value, max)}</span>
|
|
44
|
+
{#if actions}<span class="actions">{@render actions()}</span>{/if}
|
|
45
|
+
</div>
|
|
46
|
+
<Progress {value} {max} label={label} {markers} {toneAt} tone={tone ?? 'accent'} size={size === 'lg' ? 'md' : size} />
|
|
47
|
+
{#if caption}
|
|
48
|
+
<div class="caption">{#if typeof caption === 'string'}{caption}{:else}{@render caption()}{/if}</div>
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.meter {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: var(--sp-1);
|
|
57
|
+
min-width: 0;
|
|
58
|
+
font-size: var(--fs-sm);
|
|
59
|
+
}
|
|
60
|
+
.size-sm {
|
|
61
|
+
font-size: var(--fs-xs);
|
|
62
|
+
}
|
|
63
|
+
.head {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: baseline;
|
|
66
|
+
gap: var(--sp-2);
|
|
67
|
+
}
|
|
68
|
+
.label {
|
|
69
|
+
flex: 1 1 auto;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
text-overflow: ellipsis;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
color: var(--text-muted);
|
|
75
|
+
}
|
|
76
|
+
.readout {
|
|
77
|
+
font-variant-numeric: tabular-nums;
|
|
78
|
+
font-family: var(--font-mono);
|
|
79
|
+
color: var(--text);
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
}
|
|
82
|
+
.actions {
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--sp-1);
|
|
86
|
+
}
|
|
87
|
+
.caption {
|
|
88
|
+
font-size: var(--fs-xs);
|
|
89
|
+
color: var(--text-faint);
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ControlSize } from '../../size';
|
|
3
|
+
import type { Tone } from '../../tone';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
label: string;
|
|
6
|
+
value: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
/** Trailing readout; defaults to a percentage. */
|
|
9
|
+
format?: (value: number, max: number) => string;
|
|
10
|
+
caption?: string | Snippet;
|
|
11
|
+
markers?: {
|
|
12
|
+
at: number;
|
|
13
|
+
label?: string;
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
}[];
|
|
16
|
+
toneAt?: {
|
|
17
|
+
warn?: number;
|
|
18
|
+
danger?: number;
|
|
19
|
+
};
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
size?: ControlSize;
|
|
22
|
+
/** Inline controls after the readout (edit cap, bypass…). */
|
|
23
|
+
actions?: Snippet;
|
|
24
|
+
class?: string;
|
|
25
|
+
style?: string;
|
|
26
|
+
};
|
|
27
|
+
declare const Meter: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
28
|
+
type Meter = ReturnType<typeof Meter>;
|
|
29
|
+
export default Meter;
|