@dorsk/tsumikit 0.23.0 → 0.25.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 +174 -6
- package/dist/components/atoms/Button.svelte +1 -1
- package/dist/components/atoms/Card.svelte +65 -2
- package/dist/components/atoms/Card.svelte.d.ts +6 -0
- package/dist/components/atoms/Scrim.svelte +81 -0
- package/dist/components/atoms/Scrim.svelte.d.ts +14 -0
- package/dist/components/layouts/ResizablePanel.svelte +238 -120
- package/dist/components/layouts/ResizablePanel.svelte.d.ts +24 -8
- package/dist/components/layouts/resizable-panel-frame.d.ts +70 -0
- package/dist/components/layouts/resizable-panel-frame.js +161 -0
- package/dist/components/molecules/ConfirmModal.svelte +102 -0
- package/dist/components/molecules/ConfirmModal.svelte.d.ts +16 -0
- package/dist/components/molecules/KeyValue.svelte +117 -0
- package/dist/components/molecules/KeyValue.svelte.d.ts +20 -0
- package/dist/components/molecules/LoadMore.svelte +78 -0
- package/dist/components/molecules/LoadMore.svelte.d.ts +15 -0
- package/dist/components/molecules/Modal.svelte +66 -10
- package/dist/components/molecules/Modal.svelte.d.ts +12 -2
- package/dist/components/molecules/Pagination.svelte +209 -0
- package/dist/components/molecules/Pagination.svelte.d.ts +22 -0
- package/dist/components/molecules/SectionHeader.svelte +233 -0
- package/dist/components/molecules/SectionHeader.svelte.d.ts +29 -0
- package/dist/components/molecules/ThemePicker.svelte +11 -13
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/stores/theme.svelte.d.ts +21 -5
- package/dist/stores/theme.svelte.js +48 -22
- package/dist/styles/app.css +7 -297
- package/dist/styles/reset.css +97 -0
- package/dist/styles/syntax.css +94 -0
- package/dist/styles/themes.css +729 -0
- package/dist/styles/tokens.css +207 -0
- package/dist/styles/utilities.css +111 -0
- package/dist/styles/variables.css +5 -926
- package/package.json +7 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Page navigation in two flavours: page mode (`page` + `pageCount`) or
|
|
3
|
+
// offset mode (`offset` + `limit` + `total`, page/pageCount derived and
|
|
4
|
+
// `offset` written back). Numbered buttons collapse to prev / "3 / 12" /
|
|
5
|
+
// next when the container is narrower than 24rem.
|
|
6
|
+
import Text from '../atoms/Text.svelte';
|
|
7
|
+
import IconButton from './IconButton.svelte';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
page = $bindable(1),
|
|
11
|
+
pageCount,
|
|
12
|
+
offset = $bindable(),
|
|
13
|
+
limit,
|
|
14
|
+
total,
|
|
15
|
+
onchange,
|
|
16
|
+
siblings = 1,
|
|
17
|
+
showEdges = true,
|
|
18
|
+
showRange = false,
|
|
19
|
+
size = 'md',
|
|
20
|
+
label = 'Pagination',
|
|
21
|
+
class: klass = ''
|
|
22
|
+
}: {
|
|
23
|
+
/** 1-based current page. */
|
|
24
|
+
page?: number;
|
|
25
|
+
pageCount?: number;
|
|
26
|
+
/** Offset mode: 0-based index of the first item; needs `limit` + `total`. */
|
|
27
|
+
offset?: number;
|
|
28
|
+
limit?: number;
|
|
29
|
+
total?: number;
|
|
30
|
+
onchange?: (page: number) => void;
|
|
31
|
+
/** Numbered pages shown on each side of the current one. */
|
|
32
|
+
siblings?: number;
|
|
33
|
+
/** Always show the first and last page. */
|
|
34
|
+
showEdges?: boolean;
|
|
35
|
+
/** Show "1–20 / 412" (offset mode) or "3 / 12" next to the buttons. */
|
|
36
|
+
showRange?: boolean;
|
|
37
|
+
size?: 'sm' | 'md';
|
|
38
|
+
label?: string;
|
|
39
|
+
class?: string;
|
|
40
|
+
} = $props();
|
|
41
|
+
|
|
42
|
+
const offsetMode = $derived(offset !== undefined && limit !== undefined && total !== undefined);
|
|
43
|
+
const count = $derived(
|
|
44
|
+
Math.max(
|
|
45
|
+
1,
|
|
46
|
+
offsetMode ? Math.ceil((total as number) / Math.max(1, limit as number)) : (pageCount ?? 1)
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
const current = $derived(
|
|
50
|
+
Math.min(
|
|
51
|
+
count,
|
|
52
|
+
Math.max(
|
|
53
|
+
1,
|
|
54
|
+
offsetMode ? Math.floor((offset as number) / Math.max(1, limit as number)) + 1 : page
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const ELLIPSIS = -1;
|
|
60
|
+
const items = $derived.by((): number[] => {
|
|
61
|
+
const out: number[] = [];
|
|
62
|
+
const lo = Math.max(1, current - siblings);
|
|
63
|
+
const hi = Math.min(count, current + siblings);
|
|
64
|
+
if (showEdges && lo > 1) {
|
|
65
|
+
out.push(1);
|
|
66
|
+
if (lo > 2) out.push(ELLIPSIS);
|
|
67
|
+
}
|
|
68
|
+
for (let p = lo; p <= hi; p++) out.push(p);
|
|
69
|
+
if (showEdges && hi < count) {
|
|
70
|
+
if (hi < count - 1) out.push(ELLIPSIS);
|
|
71
|
+
out.push(count);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const rangeText = $derived.by(() => {
|
|
77
|
+
if (offsetMode) {
|
|
78
|
+
const t = total as number;
|
|
79
|
+
const from = t === 0 ? 0 : (current - 1) * (limit as number) + 1;
|
|
80
|
+
const to = Math.min(t, current * (limit as number));
|
|
81
|
+
return `${from}–${to} / ${t}`;
|
|
82
|
+
}
|
|
83
|
+
return `${current} / ${count}`;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function go(p: number) {
|
|
87
|
+
const next = Math.min(count, Math.max(1, p));
|
|
88
|
+
if (next === current) return;
|
|
89
|
+
page = next;
|
|
90
|
+
if (offsetMode) offset = (next - 1) * (limit as number);
|
|
91
|
+
onchange?.(next);
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<nav
|
|
96
|
+
data-tsu="Pagination"
|
|
97
|
+
class="pagination {klass}"
|
|
98
|
+
class:pagination-sm={size === 'sm'}
|
|
99
|
+
aria-label={label}
|
|
100
|
+
>
|
|
101
|
+
<IconButton
|
|
102
|
+
icon="chevron-left"
|
|
103
|
+
label="Previous page"
|
|
104
|
+
box={size === 'sm' ? 'sm' : 'md'}
|
|
105
|
+
disabled={current <= 1}
|
|
106
|
+
onclick={() => go(current - 1)}
|
|
107
|
+
/>
|
|
108
|
+
<ol class="pages">
|
|
109
|
+
{#each items as item, i (item === ELLIPSIS ? `e${i}` : item)}
|
|
110
|
+
<li>
|
|
111
|
+
{#if item === ELLIPSIS}
|
|
112
|
+
<span class="ellipsis" aria-hidden="true">…</span>
|
|
113
|
+
{:else}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
class="page"
|
|
117
|
+
class:on={item === current}
|
|
118
|
+
aria-current={item === current ? 'page' : undefined}
|
|
119
|
+
aria-label="Page {item}"
|
|
120
|
+
onclick={() => go(item)}
|
|
121
|
+
>
|
|
122
|
+
{item}
|
|
123
|
+
</button>
|
|
124
|
+
{/if}
|
|
125
|
+
</li>
|
|
126
|
+
{/each}
|
|
127
|
+
</ol>
|
|
128
|
+
<Text class="compact" size="sm" numeric tone="muted" aria-live="polite">{current} / {count}</Text>
|
|
129
|
+
<IconButton
|
|
130
|
+
icon="chevron-right"
|
|
131
|
+
label="Next page"
|
|
132
|
+
box={size === 'sm' ? 'sm' : 'md'}
|
|
133
|
+
disabled={current >= count}
|
|
134
|
+
onclick={() => go(current + 1)}
|
|
135
|
+
/>
|
|
136
|
+
{#if showRange}
|
|
137
|
+
<Text class="range" size="sm" numeric tone="muted">{rangeText}</Text>
|
|
138
|
+
{/if}
|
|
139
|
+
</nav>
|
|
140
|
+
|
|
141
|
+
<style>
|
|
142
|
+
.pagination {
|
|
143
|
+
container-type: inline-size;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: var(--sp-1);
|
|
147
|
+
flex-wrap: wrap;
|
|
148
|
+
}
|
|
149
|
+
.pages {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: var(--sp-1);
|
|
153
|
+
list-style: none;
|
|
154
|
+
margin: 0;
|
|
155
|
+
padding: 0;
|
|
156
|
+
}
|
|
157
|
+
.page {
|
|
158
|
+
min-width: var(--box-md);
|
|
159
|
+
height: var(--box-md);
|
|
160
|
+
padding: 0 var(--sp-2);
|
|
161
|
+
border: 1px solid transparent;
|
|
162
|
+
border-radius: 999px;
|
|
163
|
+
background: transparent;
|
|
164
|
+
color: var(--text-muted);
|
|
165
|
+
font-size: var(--fs-sm);
|
|
166
|
+
font-variant-numeric: tabular-nums;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
transition:
|
|
169
|
+
background 0.12s var(--ease),
|
|
170
|
+
color 0.12s var(--ease);
|
|
171
|
+
}
|
|
172
|
+
.page:hover {
|
|
173
|
+
background: var(--bg-elevated-2);
|
|
174
|
+
color: var(--text);
|
|
175
|
+
}
|
|
176
|
+
.page.on {
|
|
177
|
+
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
178
|
+
border-color: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
179
|
+
color: var(--accent);
|
|
180
|
+
font-weight: var(--fw-semibold);
|
|
181
|
+
}
|
|
182
|
+
.pagination-sm .page {
|
|
183
|
+
min-width: var(--box-sm);
|
|
184
|
+
height: var(--box-sm);
|
|
185
|
+
font-size: var(--fs-xs);
|
|
186
|
+
}
|
|
187
|
+
.ellipsis {
|
|
188
|
+
display: inline-flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
justify-content: center;
|
|
191
|
+
min-width: var(--box-md);
|
|
192
|
+
color: var(--text-faint);
|
|
193
|
+
}
|
|
194
|
+
.pagination :global(.compact) {
|
|
195
|
+
display: none;
|
|
196
|
+
padding: 0 var(--sp-2);
|
|
197
|
+
}
|
|
198
|
+
.pagination :global(.range) {
|
|
199
|
+
margin-left: var(--sp-2);
|
|
200
|
+
}
|
|
201
|
+
@container (max-width: 24rem) {
|
|
202
|
+
.pages {
|
|
203
|
+
display: none;
|
|
204
|
+
}
|
|
205
|
+
.pagination :global(.compact) {
|
|
206
|
+
display: inline;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** 1-based current page. */
|
|
3
|
+
page?: number;
|
|
4
|
+
pageCount?: number;
|
|
5
|
+
/** Offset mode: 0-based index of the first item; needs `limit` + `total`. */
|
|
6
|
+
offset?: number;
|
|
7
|
+
limit?: number;
|
|
8
|
+
total?: number;
|
|
9
|
+
onchange?: (page: number) => void;
|
|
10
|
+
/** Numbered pages shown on each side of the current one. */
|
|
11
|
+
siblings?: number;
|
|
12
|
+
/** Always show the first and last page. */
|
|
13
|
+
showEdges?: boolean;
|
|
14
|
+
/** Show "1–20 / 412" (offset mode) or "3 / 12" next to the buttons. */
|
|
15
|
+
showRange?: boolean;
|
|
16
|
+
size?: 'sm' | 'md';
|
|
17
|
+
label?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
};
|
|
20
|
+
declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "page" | "offset">;
|
|
21
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
22
|
+
export default Pagination;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Section / group header: heading + optional subtitle, icon, count, hue
|
|
3
|
+
// swatch and right-aligned actions on one wrapping row. `divider` underlines
|
|
4
|
+
// it, `sticky` pins it under the app header and publishes its height as
|
|
5
|
+
// `--section-header-h` on the parent so sibling sticky content can offset
|
|
6
|
+
// itself. `collapsible` turns the title into a disclosure button that
|
|
7
|
+
// toggles the `children` block rendered beneath.
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
import Heading from '../atoms/Heading.svelte';
|
|
10
|
+
import Icon, { type IconName } from '../atoms/Icon.svelte';
|
|
11
|
+
import Text from '../atoms/Text.svelte';
|
|
12
|
+
|
|
13
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
14
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
title,
|
|
18
|
+
label,
|
|
19
|
+
level = 2,
|
|
20
|
+
size,
|
|
21
|
+
subtitle,
|
|
22
|
+
icon,
|
|
23
|
+
count,
|
|
24
|
+
tone = 'neutral',
|
|
25
|
+
hue,
|
|
26
|
+
uppercase = false,
|
|
27
|
+
divider = false,
|
|
28
|
+
sticky = false,
|
|
29
|
+
collapsible = false,
|
|
30
|
+
open = $bindable(true),
|
|
31
|
+
class: klass = '',
|
|
32
|
+
actions,
|
|
33
|
+
children,
|
|
34
|
+
...rest
|
|
35
|
+
}: {
|
|
36
|
+
title?: string;
|
|
37
|
+
/** Alias for `title`. */
|
|
38
|
+
label?: string;
|
|
39
|
+
level?: 1 | 2 | 3 | 4;
|
|
40
|
+
size?: Size;
|
|
41
|
+
subtitle?: string;
|
|
42
|
+
icon?: IconName;
|
|
43
|
+
count?: number | string;
|
|
44
|
+
tone?: Tone;
|
|
45
|
+
/** Hue (0–360) rendered as a small swatch chip before the title. */
|
|
46
|
+
hue?: number;
|
|
47
|
+
uppercase?: boolean;
|
|
48
|
+
divider?: boolean;
|
|
49
|
+
sticky?: boolean;
|
|
50
|
+
collapsible?: boolean;
|
|
51
|
+
open?: boolean;
|
|
52
|
+
class?: string;
|
|
53
|
+
actions?: Snippet;
|
|
54
|
+
children?: Snippet;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
} = $props();
|
|
57
|
+
|
|
58
|
+
const text = $derived(title ?? label ?? '');
|
|
59
|
+
const id = $props.id();
|
|
60
|
+
const panelId = `${id}-panel`;
|
|
61
|
+
let el = $state<HTMLElement>();
|
|
62
|
+
let height = $state(0);
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
const parent = el?.parentElement;
|
|
66
|
+
if (!parent) return;
|
|
67
|
+
if (sticky) parent.style.setProperty('--section-header-h', `${height}px`);
|
|
68
|
+
else parent.style.removeProperty('--section-header-h');
|
|
69
|
+
return () => parent.style.removeProperty('--section-header-h');
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<div
|
|
74
|
+
bind:this={el}
|
|
75
|
+
bind:offsetHeight={height}
|
|
76
|
+
data-tsu="SectionHeader"
|
|
77
|
+
class="section-header sh-{tone} {klass}"
|
|
78
|
+
class:sh-divider={divider}
|
|
79
|
+
class:sh-sticky={sticky}
|
|
80
|
+
class:sh-uppercase={uppercase}
|
|
81
|
+
class:sh-open={open}
|
|
82
|
+
{...rest}
|
|
83
|
+
>
|
|
84
|
+
<div class="sh-row">
|
|
85
|
+
{#if collapsible}
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="sh-toggle"
|
|
89
|
+
aria-expanded={open}
|
|
90
|
+
aria-controls={panelId}
|
|
91
|
+
onclick={() => (open = !open)}
|
|
92
|
+
>
|
|
93
|
+
<Icon name="chevron-right" class="sh-chevron" />
|
|
94
|
+
{@render head()}
|
|
95
|
+
</button>
|
|
96
|
+
{:else}
|
|
97
|
+
{@render head()}
|
|
98
|
+
{/if}
|
|
99
|
+
{#if subtitle}
|
|
100
|
+
<Text variant="caption" class="sh-subtitle">{subtitle}</Text>
|
|
101
|
+
{/if}
|
|
102
|
+
{#if actions}
|
|
103
|
+
<div class="sh-actions">{@render actions()}</div>
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
106
|
+
{#if children && (!collapsible || open)}
|
|
107
|
+
<div class="sh-panel" id={panelId}>{@render children()}</div>
|
|
108
|
+
{/if}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{#snippet head()}
|
|
112
|
+
{#if hue !== undefined}
|
|
113
|
+
<span class="sh-swatch" style:--sh-hue={hue} aria-hidden="true"></span>
|
|
114
|
+
{/if}
|
|
115
|
+
{#if icon}
|
|
116
|
+
<span class="sh-icon" aria-hidden="true"><Icon name={icon} /></span>
|
|
117
|
+
{/if}
|
|
118
|
+
<Heading {level} {size} class="sh-title">{text}</Heading>
|
|
119
|
+
{#if count !== undefined}
|
|
120
|
+
<Text tone="faint" weight="normal" numeric class="sh-count">{count}</Text>
|
|
121
|
+
{/if}
|
|
122
|
+
{/snippet}
|
|
123
|
+
|
|
124
|
+
<style>
|
|
125
|
+
.section-header {
|
|
126
|
+
--sh-tone: var(--text);
|
|
127
|
+
}
|
|
128
|
+
.sh-ok {
|
|
129
|
+
--sh-tone: var(--ok);
|
|
130
|
+
}
|
|
131
|
+
.sh-warn {
|
|
132
|
+
--sh-tone: var(--warn);
|
|
133
|
+
}
|
|
134
|
+
.sh-danger {
|
|
135
|
+
--sh-tone: var(--danger);
|
|
136
|
+
}
|
|
137
|
+
.sh-info {
|
|
138
|
+
--sh-tone: var(--info);
|
|
139
|
+
}
|
|
140
|
+
.sh-row {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: var(--sp-3);
|
|
144
|
+
flex-wrap: wrap;
|
|
145
|
+
min-width: 0;
|
|
146
|
+
}
|
|
147
|
+
.sh-divider > .sh-row {
|
|
148
|
+
padding-bottom: var(--sp-2);
|
|
149
|
+
border-bottom: 1px solid var(--border);
|
|
150
|
+
}
|
|
151
|
+
.sh-sticky {
|
|
152
|
+
position: sticky;
|
|
153
|
+
top: var(--sticky-offset, var(--header-h, 0));
|
|
154
|
+
z-index: 1;
|
|
155
|
+
background: var(--section-header-bg, var(--bg));
|
|
156
|
+
}
|
|
157
|
+
.section-header :global(.sh-title) {
|
|
158
|
+
color: var(--sh-tone);
|
|
159
|
+
min-width: 0;
|
|
160
|
+
overflow-wrap: anywhere;
|
|
161
|
+
}
|
|
162
|
+
.sh-uppercase :global(.sh-title) {
|
|
163
|
+
text-transform: uppercase;
|
|
164
|
+
letter-spacing: 0.06em;
|
|
165
|
+
font-size: var(--fs-xs);
|
|
166
|
+
color: var(--text-muted);
|
|
167
|
+
}
|
|
168
|
+
.sh-ok.sh-uppercase :global(.sh-title),
|
|
169
|
+
.sh-warn.sh-uppercase :global(.sh-title),
|
|
170
|
+
.sh-danger.sh-uppercase :global(.sh-title),
|
|
171
|
+
.sh-info.sh-uppercase :global(.sh-title) {
|
|
172
|
+
color: var(--sh-tone);
|
|
173
|
+
}
|
|
174
|
+
.sh-icon {
|
|
175
|
+
display: inline-flex;
|
|
176
|
+
flex: none;
|
|
177
|
+
color: var(--sh-tone);
|
|
178
|
+
line-height: 1;
|
|
179
|
+
}
|
|
180
|
+
.sh-swatch {
|
|
181
|
+
flex: none;
|
|
182
|
+
width: 0.625rem;
|
|
183
|
+
height: 0.625rem;
|
|
184
|
+
border-radius: var(--r-sm);
|
|
185
|
+
background: hsl(var(--sh-hue) 70% 55%);
|
|
186
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 20%, transparent);
|
|
187
|
+
}
|
|
188
|
+
.section-header :global(.sh-subtitle) {
|
|
189
|
+
flex: 1 1 auto;
|
|
190
|
+
min-width: 0;
|
|
191
|
+
}
|
|
192
|
+
.sh-actions {
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-wrap: wrap;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: var(--sp-2);
|
|
197
|
+
margin-inline-start: auto;
|
|
198
|
+
}
|
|
199
|
+
.sh-toggle {
|
|
200
|
+
display: inline-flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: var(--sp-2);
|
|
203
|
+
min-width: 0;
|
|
204
|
+
padding: 0;
|
|
205
|
+
border: 0;
|
|
206
|
+
background: none;
|
|
207
|
+
font: inherit;
|
|
208
|
+
color: inherit;
|
|
209
|
+
text-align: start;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
border-radius: var(--r-sm);
|
|
212
|
+
}
|
|
213
|
+
.sh-toggle:focus-visible {
|
|
214
|
+
outline: 2px solid var(--accent);
|
|
215
|
+
outline-offset: 2px;
|
|
216
|
+
}
|
|
217
|
+
.section-header :global(.sh-chevron) {
|
|
218
|
+
flex: none;
|
|
219
|
+
color: var(--text-muted);
|
|
220
|
+
transition: transform 0.15s var(--ease);
|
|
221
|
+
}
|
|
222
|
+
.sh-open :global(.sh-chevron) {
|
|
223
|
+
transform: rotate(90deg);
|
|
224
|
+
}
|
|
225
|
+
.sh-panel {
|
|
226
|
+
margin-top: var(--sp-3);
|
|
227
|
+
}
|
|
228
|
+
@media (prefers-reduced-motion: reduce) {
|
|
229
|
+
.section-header :global(.sh-chevron) {
|
|
230
|
+
transition: none;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type IconName } from '../atoms/Icon.svelte';
|
|
3
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
4
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
5
|
+
type $$ComponentProps = {
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Alias for `title`. */
|
|
8
|
+
label?: string;
|
|
9
|
+
level?: 1 | 2 | 3 | 4;
|
|
10
|
+
size?: Size;
|
|
11
|
+
subtitle?: string;
|
|
12
|
+
icon?: IconName;
|
|
13
|
+
count?: number | string;
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
/** Hue (0–360) rendered as a small swatch chip before the title. */
|
|
16
|
+
hue?: number;
|
|
17
|
+
uppercase?: boolean;
|
|
18
|
+
divider?: boolean;
|
|
19
|
+
sticky?: boolean;
|
|
20
|
+
collapsible?: boolean;
|
|
21
|
+
open?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
actions?: Snippet;
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
declare const SectionHeader: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
28
|
+
type SectionHeader = ReturnType<typeof SectionHeader>;
|
|
29
|
+
export default SectionHeader;
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
// Theme switcher: a compact icon-button hosting a native <select> over the
|
|
3
|
-
// full theme registry, wired to the global
|
|
4
|
-
// gives correct mobile/keyboard behaviour and
|
|
5
|
-
// The button glyph reflects the active theme
|
|
3
|
+
// full theme registry (built-ins + theme.register()), wired to the global
|
|
4
|
+
// theme store. The native control gives correct mobile/keyboard behaviour and
|
|
5
|
+
// outside-click handling for free. The button glyph reflects the active theme.
|
|
6
6
|
import SelectButton from './SelectButton.svelte';
|
|
7
|
-
import {
|
|
7
|
+
import { type ThemeDef, theme } from '../../stores/theme.svelte';
|
|
8
8
|
|
|
9
9
|
let { class: klass = '' }: { class?: string } = $props();
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
// scannable. `<optgroup>` keeps native popup/keyboard behaviour for free.
|
|
13
|
-
const toOption = (t: (typeof THEMES)[number]) => ({
|
|
11
|
+
const toOption = (t: ThemeDef) => ({
|
|
14
12
|
value: t.id,
|
|
15
|
-
label: `${t.icon} ${t.label}`
|
|
13
|
+
label: `${t.icon ?? theme.fallbackIcon} ${t.label}`
|
|
16
14
|
});
|
|
17
|
-
const groups = [
|
|
18
|
-
{ label: '— light', options:
|
|
19
|
-
{ label: '— dark', options:
|
|
20
|
-
];
|
|
15
|
+
const groups = $derived([
|
|
16
|
+
{ label: '— light', options: theme.all.filter((t) => t.mode === 'light').map(toOption) },
|
|
17
|
+
{ label: '— dark', options: theme.all.filter((t) => t.mode === 'dark').map(toOption) }
|
|
18
|
+
]);
|
|
21
19
|
</script>
|
|
22
20
|
|
|
23
21
|
<SelectButton
|
|
@@ -28,5 +26,5 @@
|
|
|
28
26
|
title={`Theme: ${theme.label}`}
|
|
29
27
|
value={theme.current}
|
|
30
28
|
{groups}
|
|
31
|
-
onchange={(v) => theme.set(v
|
|
29
|
+
onchange={(v) => theme.set(v)}
|
|
32
30
|
/>
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { default as Icon } from './components/atoms/Icon.svelte';
|
|
|
11
11
|
export { default as Input } from './components/atoms/Input.svelte';
|
|
12
12
|
export { default as Link } from './components/atoms/Link.svelte';
|
|
13
13
|
export { default as Progress } from './components/atoms/Progress.svelte';
|
|
14
|
+
export { default as Scrim } from './components/atoms/Scrim.svelte';
|
|
14
15
|
export { default as SegmentedProgress, type ProgressSegment, } from './components/atoms/SegmentedProgress.svelte';
|
|
15
16
|
export { default as Select } from './components/atoms/Select.svelte';
|
|
16
17
|
export { default as Slider } from './components/atoms/Slider.svelte';
|
|
@@ -25,11 +26,13 @@ export { default as Container } from './components/layouts/Container.svelte';
|
|
|
25
26
|
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
26
27
|
export { default as NavSection } from './components/layouts/NavSection.svelte';
|
|
27
28
|
export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
|
|
29
|
+
export { type ResizeHandleParams, resizeHandle, } from './components/layouts/resizable-panel-frame.js';
|
|
28
30
|
export { default as Stack } from './components/layouts/Stack.svelte';
|
|
29
31
|
export { type AccordionItem, default as Accordion, } from './components/molecules/Accordion.svelte';
|
|
30
32
|
export { type BreadcrumbItem, default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
|
|
31
33
|
export { default as Callout } from './components/molecules/Callout.svelte';
|
|
32
34
|
export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
|
|
35
|
+
export { default as ConfirmModal } from './components/molecules/ConfirmModal.svelte';
|
|
33
36
|
export { default as CopyButton } from './components/molecules/CopyButton.svelte';
|
|
34
37
|
export { default as Dropzone } from './components/molecules/Dropzone.svelte';
|
|
35
38
|
export { default as EmptyState } from './components/molecules/EmptyState.svelte';
|
|
@@ -38,12 +41,16 @@ export { default as FileButton } from './components/molecules/FileButton.svelte'
|
|
|
38
41
|
export { default as FilterInput, type FilterInputContext, } from './components/molecules/FilterInput.svelte';
|
|
39
42
|
export { default as FontScalePicker } from './components/molecules/FontScalePicker.svelte';
|
|
40
43
|
export { default as IconButton } from './components/molecules/IconButton.svelte';
|
|
44
|
+
export { default as KeyValue, type KeyValueRow, type KeyValueTone, } from './components/molecules/KeyValue.svelte';
|
|
45
|
+
export { default as LoadMore } from './components/molecules/LoadMore.svelte';
|
|
41
46
|
export { default as Menu, type MenuItem } from './components/molecules/Menu.svelte';
|
|
42
47
|
export { default as Metric, default as StatTile, } from './components/molecules/Metric.svelte';
|
|
43
48
|
export { default as Modal } from './components/molecules/Modal.svelte';
|
|
44
49
|
export { default as OptionButton } from './components/molecules/OptionButton.svelte';
|
|
50
|
+
export { default as Pagination } from './components/molecules/Pagination.svelte';
|
|
45
51
|
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
46
52
|
export { default as RadioGroup, type RadioOption, } from './components/molecules/RadioGroup.svelte';
|
|
53
|
+
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
47
54
|
export { default as SegmentedControl, type SegmentOption, } from './components/molecules/SegmentedControl.svelte';
|
|
48
55
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
49
56
|
export { default as Tabs, type TabItem } from './components/molecules/Tabs.svelte';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { default as Icon } from './components/atoms/Icon.svelte';
|
|
|
16
16
|
export { default as Input } from './components/atoms/Input.svelte';
|
|
17
17
|
export { default as Link } from './components/atoms/Link.svelte';
|
|
18
18
|
export { default as Progress } from './components/atoms/Progress.svelte';
|
|
19
|
+
export { default as Scrim } from './components/atoms/Scrim.svelte';
|
|
19
20
|
export { default as SegmentedProgress, } from './components/atoms/SegmentedProgress.svelte';
|
|
20
21
|
export { default as Select } from './components/atoms/Select.svelte';
|
|
21
22
|
export { default as Slider } from './components/atoms/Slider.svelte';
|
|
@@ -32,11 +33,13 @@ export { default as Container } from './components/layouts/Container.svelte';
|
|
|
32
33
|
export { default as NavItem } from './components/layouts/NavItem.svelte';
|
|
33
34
|
export { default as NavSection } from './components/layouts/NavSection.svelte';
|
|
34
35
|
export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
|
|
36
|
+
export { resizeHandle, } from './components/layouts/resizable-panel-frame.js';
|
|
35
37
|
export { default as Stack } from './components/layouts/Stack.svelte';
|
|
36
38
|
export { default as Accordion, } from './components/molecules/Accordion.svelte';
|
|
37
39
|
export { default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
|
|
38
40
|
export { default as Callout } from './components/molecules/Callout.svelte';
|
|
39
41
|
export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
|
|
42
|
+
export { default as ConfirmModal } from './components/molecules/ConfirmModal.svelte';
|
|
40
43
|
export { default as CopyButton } from './components/molecules/CopyButton.svelte';
|
|
41
44
|
export { default as Dropzone } from './components/molecules/Dropzone.svelte';
|
|
42
45
|
export { default as EmptyState } from './components/molecules/EmptyState.svelte';
|
|
@@ -46,14 +49,18 @@ export { default as FileButton } from './components/molecules/FileButton.svelte'
|
|
|
46
49
|
export { default as FilterInput, } from './components/molecules/FilterInput.svelte';
|
|
47
50
|
export { default as FontScalePicker } from './components/molecules/FontScalePicker.svelte';
|
|
48
51
|
export { default as IconButton } from './components/molecules/IconButton.svelte';
|
|
52
|
+
export { default as KeyValue, } from './components/molecules/KeyValue.svelte';
|
|
53
|
+
export { default as LoadMore } from './components/molecules/LoadMore.svelte';
|
|
49
54
|
export { default as Menu } from './components/molecules/Menu.svelte';
|
|
50
55
|
export { default as Metric,
|
|
51
56
|
// StatTile is an alias for Metric — same component, dashboard-friendly name.
|
|
52
57
|
default as StatTile, } from './components/molecules/Metric.svelte';
|
|
53
58
|
export { default as Modal } from './components/molecules/Modal.svelte';
|
|
54
59
|
export { default as OptionButton } from './components/molecules/OptionButton.svelte';
|
|
60
|
+
export { default as Pagination } from './components/molecules/Pagination.svelte';
|
|
55
61
|
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
56
62
|
export { default as RadioGroup, } from './components/molecules/RadioGroup.svelte';
|
|
63
|
+
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
57
64
|
export { default as SegmentedControl, } from './components/molecules/SegmentedControl.svelte';
|
|
58
65
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
59
66
|
export { default as Tabs } from './components/molecules/Tabs.svelte';
|
|
@@ -145,17 +145,33 @@ export declare const THEMES: readonly [{
|
|
|
145
145
|
}];
|
|
146
146
|
export type Mode = (typeof THEMES)[number]['id'];
|
|
147
147
|
export type ThemeMode = (typeof THEMES)[number]['mode'];
|
|
148
|
-
type
|
|
148
|
+
export type ThemeId = Mode | (string & {});
|
|
149
|
+
export interface ThemeDef {
|
|
150
|
+
id: ThemeId;
|
|
151
|
+
label: string;
|
|
152
|
+
mode: ThemeMode;
|
|
153
|
+
icon?: string;
|
|
154
|
+
themeColor?: string;
|
|
155
|
+
}
|
|
149
156
|
declare class Theme {
|
|
150
|
-
current:
|
|
157
|
+
current: ThemeId;
|
|
158
|
+
readonly fallbackIcon = "\u25C8";
|
|
159
|
+
private registered;
|
|
160
|
+
private fallback;
|
|
161
|
+
private saved;
|
|
151
162
|
constructor();
|
|
163
|
+
get all(): readonly ThemeDef[];
|
|
164
|
+
has(id: string | null | undefined): id is ThemeId;
|
|
165
|
+
register(defs: ThemeDef | ThemeDef[]): void;
|
|
166
|
+
setDefault(id: ThemeId): void;
|
|
167
|
+
private resolve;
|
|
152
168
|
private apply;
|
|
153
|
-
get option():
|
|
169
|
+
get option(): ThemeDef;
|
|
154
170
|
get label(): string;
|
|
155
171
|
get icon(): string;
|
|
156
|
-
get next():
|
|
172
|
+
get next(): ThemeDef;
|
|
157
173
|
toggle(): void;
|
|
158
|
-
set(mode:
|
|
174
|
+
set(mode: ThemeId): void;
|
|
159
175
|
}
|
|
160
176
|
export declare const theme: Theme;
|
|
161
177
|
export {};
|