@dorsk/tsumikit 0.21.0 → 0.22.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 +16 -1
- package/dist/components/layouts/AppShell.svelte +47 -5
- package/dist/components/layouts/AppShell.svelte.d.ts +8 -0
- package/dist/components/layouts/Container.svelte +54 -13
- package/dist/components/layouts/Container.svelte.d.ts +11 -3
- package/dist/styles/app.css +1 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,11 +83,26 @@ Dropzone, CodeBlock, Callout, EmptyState, Toaster, ThemePicker, FontScalePicker.
|
|
|
83
83
|
**Organisms:** DataTable (generic `<T>`, typed columns + cell snippets).
|
|
84
84
|
|
|
85
85
|
**Layouts:** AppShell (responsive header/sidebar/main/footer — persistent
|
|
86
|
-
sidebar on desktop, overlay drawer on mobile, optionally resizable
|
|
86
|
+
sidebar on desktop, overlay drawer on mobile, optionally resizable;
|
|
87
|
+
`layout="sidebar-full"` runs the sidebar the full height with the header over
|
|
88
|
+
the content column only, `stickySidebar` pins it to the viewport, and
|
|
89
|
+
`sidebarPadding="none" | "sm" | "md"` sets the aside gutter — the header and its
|
|
90
|
+
children are `min-width: 0` so a wide title/actions row can't widen the grid on
|
|
91
|
+
mobile), NavItem
|
|
87
92
|
(collapses to an icon rail when the sidebar is narrow), Container, Stack
|
|
88
93
|
(vertical), Cluster (wrapping row), AutoGrid (intrinsically responsive columns —
|
|
89
94
|
no media/container query needed).
|
|
90
95
|
|
|
96
|
+
### Container
|
|
97
|
+
|
|
98
|
+
`size` caps the column (`--content-max` by default; `size="none"` fills the parent),
|
|
99
|
+
`gutter` overrides the `--sp-4` inline padding (safe-area insets still win),
|
|
100
|
+
`align="start"` drops the centering. `fullWidth` is a *viewport* breakout for
|
|
101
|
+
edge-to-edge sections — inside AppShell's main column use `size="none"` instead;
|
|
102
|
+
`inset="<left> <right>"` keeps a `fullWidth` container clear of docked panels.
|
|
103
|
+
Children can bleed to the column edge with
|
|
104
|
+
`margin-inline: calc(-1 * var(--container-gutter))`.
|
|
105
|
+
|
|
91
106
|
### Stacked distribution + legend
|
|
92
107
|
|
|
93
108
|
`SegmentedProgress mode="stacked"` turns the bar into one shared track whose slice
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
resizableSidebar = false,
|
|
25
25
|
minSidebar = 64,
|
|
26
26
|
maxSidebar = 360,
|
|
27
|
-
sidebarWidthKey
|
|
27
|
+
sidebarWidthKey,
|
|
28
|
+
layout = 'header-top',
|
|
29
|
+
stickySidebar = false,
|
|
30
|
+
sidebarPadding = 'md'
|
|
28
31
|
}: {
|
|
29
32
|
header?: Snippet;
|
|
30
33
|
sidebar?: Snippet;
|
|
@@ -41,6 +44,14 @@
|
|
|
41
44
|
maxSidebar?: number;
|
|
42
45
|
/** localStorage key to persist the resized width. */
|
|
43
46
|
sidebarWidthKey?: string;
|
|
47
|
+
/** `'sidebar-full'`: the sidebar spans the whole height on desktop/tablet
|
|
48
|
+
* and the header only covers the content column (brand sits top-left). */
|
|
49
|
+
layout?: 'header-top' | 'sidebar-full';
|
|
50
|
+
/** Pin the desktop sidebar to the viewport (100dvh, own scroll) so its
|
|
51
|
+
* footer stays visible on long pages. */
|
|
52
|
+
stickySidebar?: boolean;
|
|
53
|
+
/** Inner padding of the sidebar; `'none'` when the nav owns its gutters. */
|
|
54
|
+
sidebarPadding?: 'none' | 'sm' | 'md';
|
|
44
55
|
} = $props();
|
|
45
56
|
|
|
46
57
|
let open = $state(false);
|
|
@@ -115,7 +126,14 @@
|
|
|
115
126
|
|
|
116
127
|
<svelte:window onkeydown={(e) => e.key === 'Escape' && (open = false)} />
|
|
117
128
|
|
|
118
|
-
<div
|
|
129
|
+
<div
|
|
130
|
+
class="shell"
|
|
131
|
+
class:dragging
|
|
132
|
+
class:sidebar-full={layout === 'sidebar-full'}
|
|
133
|
+
class:sticky-sidebar={stickySidebar}
|
|
134
|
+
style="--shell-sidebar-w: {widthCss}"
|
|
135
|
+
data-tsu="AppShell"
|
|
136
|
+
>
|
|
119
137
|
<header class="shell-header">
|
|
120
138
|
{#if sidebar}
|
|
121
139
|
<!-- Wrapper owned here so the responsive hide is a scoped rule on our own
|
|
@@ -143,6 +161,7 @@
|
|
|
143
161
|
<aside
|
|
144
162
|
class="shell-sidebar"
|
|
145
163
|
class:open
|
|
164
|
+
data-padding={sidebarPadding}
|
|
146
165
|
aria-label={navLabel}
|
|
147
166
|
inert={isMobile && !open ? true : undefined}
|
|
148
167
|
>
|
|
@@ -190,6 +209,7 @@
|
|
|
190
209
|
display: flex;
|
|
191
210
|
align-items: center;
|
|
192
211
|
gap: var(--sp-3);
|
|
212
|
+
min-width: 0;
|
|
193
213
|
height: var(--header-h);
|
|
194
214
|
padding-inline: max(var(--sp-4), var(--safe-left)) max(var(--sp-4), var(--safe-right));
|
|
195
215
|
padding-top: var(--safe-top);
|
|
@@ -197,6 +217,9 @@
|
|
|
197
217
|
backdrop-filter: blur(8px);
|
|
198
218
|
border-bottom: 1px solid var(--border);
|
|
199
219
|
}
|
|
220
|
+
.shell-header > :global(*) {
|
|
221
|
+
min-width: 0;
|
|
222
|
+
}
|
|
200
223
|
.shell-menu-btn {
|
|
201
224
|
display: inline-flex;
|
|
202
225
|
align-items: center;
|
|
@@ -229,15 +252,22 @@
|
|
|
229
252
|
-webkit-overflow-scrolling: touch;
|
|
230
253
|
background: var(--bg-elevated);
|
|
231
254
|
border-right: 1px solid var(--border);
|
|
232
|
-
|
|
233
|
-
padding
|
|
234
|
-
padding-
|
|
255
|
+
--shell-sidebar-pad: var(--sp-3);
|
|
256
|
+
padding: var(--shell-sidebar-pad);
|
|
257
|
+
padding-top: max(var(--shell-sidebar-pad), var(--safe-top));
|
|
258
|
+
padding-bottom: max(var(--shell-sidebar-pad), var(--safe-bottom));
|
|
235
259
|
/* A query container named `sidebar` so nav items collapse to an icon rail
|
|
236
260
|
based on the sidebar's own width (see NavItem). */
|
|
237
261
|
container: sidebar / inline-size;
|
|
238
262
|
transform: translateX(-100%);
|
|
239
263
|
transition: transform 0.2s var(--ease);
|
|
240
264
|
}
|
|
265
|
+
.shell-sidebar[data-padding='sm'] {
|
|
266
|
+
--shell-sidebar-pad: var(--sp-2);
|
|
267
|
+
}
|
|
268
|
+
.shell-sidebar[data-padding='none'] {
|
|
269
|
+
--shell-sidebar-pad: 0px;
|
|
270
|
+
}
|
|
241
271
|
.shell-sidebar.open {
|
|
242
272
|
transform: translateX(0);
|
|
243
273
|
box-shadow: var(--shadow-lg);
|
|
@@ -273,6 +303,12 @@
|
|
|
273
303
|
'sidebar main'
|
|
274
304
|
'footer footer';
|
|
275
305
|
}
|
|
306
|
+
.shell.sidebar-full {
|
|
307
|
+
grid-template-areas:
|
|
308
|
+
'sidebar header'
|
|
309
|
+
'sidebar main'
|
|
310
|
+
'sidebar footer';
|
|
311
|
+
}
|
|
276
312
|
.shell-sidebar {
|
|
277
313
|
position: relative; /* anchor the absolute resize handle */
|
|
278
314
|
grid-area: sidebar;
|
|
@@ -281,6 +317,12 @@
|
|
|
281
317
|
box-shadow: none;
|
|
282
318
|
border-right: 1px solid var(--border);
|
|
283
319
|
}
|
|
320
|
+
.shell.sticky-sidebar .shell-sidebar {
|
|
321
|
+
position: sticky;
|
|
322
|
+
top: 0;
|
|
323
|
+
height: 100dvh;
|
|
324
|
+
align-self: start;
|
|
325
|
+
}
|
|
284
326
|
.shell-scrim,
|
|
285
327
|
.shell-menu-btn {
|
|
286
328
|
display: none !important;
|
|
@@ -15,6 +15,14 @@ type $$ComponentProps = {
|
|
|
15
15
|
maxSidebar?: number;
|
|
16
16
|
/** localStorage key to persist the resized width. */
|
|
17
17
|
sidebarWidthKey?: string;
|
|
18
|
+
/** `'sidebar-full'`: the sidebar spans the whole height on desktop/tablet
|
|
19
|
+
* and the header only covers the content column (brand sits top-left). */
|
|
20
|
+
layout?: 'header-top' | 'sidebar-full';
|
|
21
|
+
/** Pin the desktop sidebar to the viewport (100dvh, own scroll) so its
|
|
22
|
+
* footer stays visible on long pages. */
|
|
23
|
+
stickySidebar?: boolean;
|
|
24
|
+
/** Inner padding of the sidebar; `'none'` when the nav owns its gutters. */
|
|
25
|
+
sidebarPadding?: 'none' | 'sm' | 'md';
|
|
18
26
|
};
|
|
19
27
|
declare const AppShell: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
20
28
|
type AppShell = ReturnType<typeof AppShell>;
|
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// (the `margin-inline: calc(50% - 50vw)` trick), for edge-to-edge sections.
|
|
7
|
-
// Polymorphic via `as` so it can be a <main>, <section>, etc.
|
|
2
|
+
// Max-width content column with token gutters that respect safe-area insets.
|
|
3
|
+
// Self-contained: does not depend on the global `.container` utility. Exposes
|
|
4
|
+
// `--container-gutter` so children can bleed to the column edge with
|
|
5
|
+
// `margin-inline: calc(-1 * var(--container-gutter))`.
|
|
8
6
|
import type { Snippet } from 'svelte';
|
|
9
7
|
|
|
10
8
|
let {
|
|
11
9
|
as = 'div',
|
|
12
10
|
size,
|
|
11
|
+
gutter,
|
|
12
|
+
align = 'center',
|
|
13
13
|
pad = false,
|
|
14
14
|
fullWidth = false,
|
|
15
|
+
inset,
|
|
15
16
|
class: klass = '',
|
|
16
17
|
children,
|
|
17
18
|
...rest
|
|
18
19
|
}: {
|
|
19
20
|
as?: 'div' | 'main' | 'section' | 'article';
|
|
20
|
-
/** Max width (any CSS length). Defaults to --content-max. Ignored when `fullWidth`. */
|
|
21
|
-
size?: string;
|
|
21
|
+
/** Max width (any CSS length), or `'none'` to fill the parent. Defaults to --content-max. Ignored when `fullWidth`. */
|
|
22
|
+
size?: string | 'none';
|
|
23
|
+
/** Inline gutter (any CSS length). Defaults to --sp-4; safe-area insets still win when larger. */
|
|
24
|
+
gutter?: string;
|
|
25
|
+
/** `'center'` (margin-inline auto) or `'start'` (flush with the parent's start edge). */
|
|
26
|
+
align?: 'center' | 'start';
|
|
22
27
|
pad?: boolean;
|
|
23
|
-
/** Break out to the full viewport width, ignoring `size`/--content-max
|
|
28
|
+
/** Break out to the full *viewport* width, ignoring `size`/--content-max and any
|
|
29
|
+
* centered ancestor. Inside a sidebar layout prefer `size="none"` (fills the column). */
|
|
24
30
|
fullWidth?: boolean;
|
|
31
|
+
/** Space reserved at the viewport edges when `fullWidth` (`'left right'` or one value
|
|
32
|
+
* for both), e.g. `"var(--dock-left-w) var(--dock-right-w)"` for docked panels. */
|
|
33
|
+
inset?: string;
|
|
25
34
|
class?: string;
|
|
26
35
|
children?: Snippet;
|
|
27
36
|
[key: string]: unknown;
|
|
28
37
|
} = $props();
|
|
38
|
+
|
|
39
|
+
const style = $derived.by(() => {
|
|
40
|
+
const vars: string[] = [];
|
|
41
|
+
if (!fullWidth && size && size !== 'none') vars.push(`--ct-max: ${size}`);
|
|
42
|
+
if (gutter) vars.push(`--container-gutter: ${gutter}`);
|
|
43
|
+
if (fullWidth && inset) {
|
|
44
|
+
const [left, right = left] = inset.trim().split(/\s+/);
|
|
45
|
+
vars.push(`--ct-inset-l: ${left}`, `--ct-inset-r: ${right}`);
|
|
46
|
+
}
|
|
47
|
+
return vars.length ? vars.join('; ') : undefined;
|
|
48
|
+
});
|
|
29
49
|
</script>
|
|
30
50
|
|
|
31
51
|
<svelte:element
|
|
@@ -33,25 +53,46 @@
|
|
|
33
53
|
data-tsu="Container"
|
|
34
54
|
class="container ct {klass}"
|
|
35
55
|
class:pad
|
|
56
|
+
class:none={size === 'none'}
|
|
57
|
+
class:start={align === 'start'}
|
|
36
58
|
class:full={fullWidth}
|
|
37
|
-
style
|
|
59
|
+
{style}
|
|
38
60
|
{...rest}
|
|
39
61
|
>
|
|
40
62
|
{@render children?.()}
|
|
41
63
|
</svelte:element>
|
|
42
64
|
|
|
43
65
|
<style>
|
|
66
|
+
.ct {
|
|
67
|
+
--container-gutter: var(--sp-4);
|
|
68
|
+
width: 100%;
|
|
69
|
+
max-width: var(--ct-max, var(--content-max));
|
|
70
|
+
margin-inline: auto;
|
|
71
|
+
padding-inline: max(var(--container-gutter), var(--safe-left))
|
|
72
|
+
max(var(--container-gutter), var(--safe-right));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.ct.none {
|
|
76
|
+
max-width: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ct.start {
|
|
80
|
+
margin-inline: 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
.ct.pad {
|
|
45
84
|
padding-top: var(--sp-6);
|
|
46
85
|
padding-bottom: var(--sp-12);
|
|
47
86
|
}
|
|
48
87
|
|
|
49
|
-
/* Break out of any centered ancestor to span the
|
|
88
|
+
/* Break out of any centered ancestor to span the viewport (minus `inset`).
|
|
50
89
|
`margin-inline: calc(50% - 50vw)` pulls each edge out to the viewport,
|
|
51
90
|
keeping the element in normal flow (no transform/overflow side-effects). */
|
|
52
91
|
.ct.full {
|
|
92
|
+
--ct-inset-l: 0px;
|
|
93
|
+
--ct-inset-r: 0px;
|
|
53
94
|
max-width: none;
|
|
54
|
-
width: 100vw;
|
|
55
|
-
margin-inline: calc(50% - 50vw);
|
|
95
|
+
width: calc(100vw - var(--ct-inset-l) - var(--ct-inset-r));
|
|
96
|
+
margin-inline: calc(50% - 50vw + var(--ct-inset-l)) calc(50% - 50vw + var(--ct-inset-r));
|
|
56
97
|
}
|
|
57
98
|
</style>
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
as?: 'div' | 'main' | 'section' | 'article';
|
|
4
|
-
/** Max width (any CSS length). Defaults to --content-max. Ignored when `fullWidth`. */
|
|
5
|
-
size?: string;
|
|
4
|
+
/** Max width (any CSS length), or `'none'` to fill the parent. Defaults to --content-max. Ignored when `fullWidth`. */
|
|
5
|
+
size?: string | 'none';
|
|
6
|
+
/** Inline gutter (any CSS length). Defaults to --sp-4; safe-area insets still win when larger. */
|
|
7
|
+
gutter?: string;
|
|
8
|
+
/** `'center'` (margin-inline auto) or `'start'` (flush with the parent's start edge). */
|
|
9
|
+
align?: 'center' | 'start';
|
|
6
10
|
pad?: boolean;
|
|
7
|
-
/** Break out to the full viewport width, ignoring `size`/--content-max
|
|
11
|
+
/** Break out to the full *viewport* width, ignoring `size`/--content-max and any
|
|
12
|
+
* centered ancestor. Inside a sidebar layout prefer `size="none"` (fills the column). */
|
|
8
13
|
fullWidth?: boolean;
|
|
14
|
+
/** Space reserved at the viewport edges when `fullWidth` (`'left right'` or one value
|
|
15
|
+
* for both), e.g. `"var(--dock-left-w) var(--dock-right-w)"` for docked panels. */
|
|
16
|
+
inset?: string;
|
|
9
17
|
class?: string;
|
|
10
18
|
children?: Snippet;
|
|
11
19
|
[key: string]: unknown;
|
package/dist/styles/app.css
CHANGED
|
@@ -106,9 +106,7 @@ pre {
|
|
|
106
106
|
width: 100%;
|
|
107
107
|
max-width: var(--content-max);
|
|
108
108
|
margin-inline: auto;
|
|
109
|
-
padding-inline: var(--sp-4);
|
|
110
|
-
padding-left: max(var(--sp-4), var(--safe-left));
|
|
111
|
-
padding-right: max(var(--sp-4), var(--safe-right));
|
|
109
|
+
padding-inline: max(var(--sp-4), var(--safe-left)) max(var(--sp-4), var(--safe-right));
|
|
112
110
|
}
|
|
113
111
|
.stack {
|
|
114
112
|
display: flex;
|
package/package.json
CHANGED