@dorsk/tsumikit 0.27.0 → 0.28.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.
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
// rows); `as` lets it be a button/anchor when the whole surface is clickable.
|
|
6
6
|
// `padding` dials the inner spacing (none/sm/md/lg) for denser cards.
|
|
7
7
|
// `tone` tints the surface itself (border + faint background wash) with a
|
|
8
|
-
// semantic hue for inline banners; `neutral` is the plain card
|
|
8
|
+
// semantic hue for inline banners; `neutral` is the plain card; `attention`
|
|
9
|
+
// adds a left bar for needs-input rows.
|
|
10
|
+
//
|
|
11
|
+
// `interactive` makes a non-button/anchor card keyboard-operable
|
|
12
|
+
// (role=button, tabindex, Enter/Space) and ignores clicks originating from
|
|
13
|
+
// nested interactive elements so inner buttons/links keep working.
|
|
9
14
|
//
|
|
10
15
|
// `stacked` fakes a pile of cards by drawing two layers peeking out below
|
|
11
16
|
// (and optionally to the right) via pseudo-elements. `stackTone` tints those
|
|
@@ -20,10 +25,11 @@
|
|
|
20
25
|
import type { Snippet } from 'svelte';
|
|
21
26
|
import SectionHeader from '../molecules/SectionHeader.svelte';
|
|
22
27
|
|
|
23
|
-
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
28
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info' | 'attention';
|
|
24
29
|
|
|
25
30
|
let {
|
|
26
31
|
tap = false,
|
|
32
|
+
interactive = false,
|
|
27
33
|
as = 'div',
|
|
28
34
|
padding = 'md',
|
|
29
35
|
surface = 'base',
|
|
@@ -35,6 +41,8 @@
|
|
|
35
41
|
title,
|
|
36
42
|
subtitle,
|
|
37
43
|
gap,
|
|
44
|
+
maxWidth,
|
|
45
|
+
onclick,
|
|
38
46
|
class: klass = '',
|
|
39
47
|
style = '',
|
|
40
48
|
header,
|
|
@@ -44,6 +52,7 @@
|
|
|
44
52
|
...rest
|
|
45
53
|
}: {
|
|
46
54
|
tap?: boolean;
|
|
55
|
+
interactive?: boolean;
|
|
47
56
|
as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
|
|
48
57
|
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
49
58
|
surface?: 'base' | 'raised' | 'sunken';
|
|
@@ -55,6 +64,8 @@
|
|
|
55
64
|
title?: string;
|
|
56
65
|
subtitle?: string;
|
|
57
66
|
gap?: string;
|
|
67
|
+
maxWidth?: string;
|
|
68
|
+
onclick?: (e: MouseEvent | KeyboardEvent) => void;
|
|
58
69
|
class?: string;
|
|
59
70
|
style?: string;
|
|
60
71
|
header?: Snippet;
|
|
@@ -68,6 +79,26 @@
|
|
|
68
79
|
stacked ? `--stack-y:${stackY}px;--stack-x:${stackX}px;` : ''
|
|
69
80
|
);
|
|
70
81
|
const framed = $derived(!!(header || footer || title));
|
|
82
|
+
const native = $derived(as === 'button' || as === 'a');
|
|
83
|
+
|
|
84
|
+
const NESTED = 'button,a,input,select,textarea,[role=button],[contenteditable],[contenteditable=true]';
|
|
85
|
+
function fromNested(e: Event): boolean {
|
|
86
|
+
const target = e.target as Element | null;
|
|
87
|
+
const hit = target?.closest?.(NESTED);
|
|
88
|
+
return !!hit && hit !== e.currentTarget;
|
|
89
|
+
}
|
|
90
|
+
function handleClick(e: MouseEvent) {
|
|
91
|
+
if (!onclick) return;
|
|
92
|
+
if (interactive && fromNested(e)) return;
|
|
93
|
+
onclick(e);
|
|
94
|
+
}
|
|
95
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
96
|
+
if (!interactive || native || !onclick) return;
|
|
97
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
98
|
+
if (fromNested(e)) return;
|
|
99
|
+
if (e.key === ' ') e.preventDefault();
|
|
100
|
+
onclick(e);
|
|
101
|
+
}
|
|
71
102
|
</script>
|
|
72
103
|
|
|
73
104
|
<svelte:element
|
|
@@ -79,11 +110,13 @@
|
|
|
79
110
|
class:pad-lg={padding === 'lg'}
|
|
80
111
|
class:surface-raised={surface === 'raised'}
|
|
81
112
|
class:surface-sunken={surface === 'sunken'}
|
|
82
|
-
class:card-tap={tap}
|
|
113
|
+
class:card-tap={tap || interactive}
|
|
114
|
+
class:card-max={maxWidth !== undefined}
|
|
83
115
|
class:card-ok={tone === 'ok'}
|
|
84
116
|
class:card-warn={tone === 'warn'}
|
|
85
117
|
class:card-danger={tone === 'danger'}
|
|
86
118
|
class:card-info={tone === 'info'}
|
|
119
|
+
class:card-attention={tone === 'attention'}
|
|
87
120
|
class:card-stacked={stacked}
|
|
88
121
|
class:stack-ok={stacked && stackTone === 'ok'}
|
|
89
122
|
class:stack-warn={stacked && stackTone === 'warn'}
|
|
@@ -92,7 +125,12 @@
|
|
|
92
125
|
class:card-framed={framed}
|
|
93
126
|
class:card-gap={!framed && gap !== undefined}
|
|
94
127
|
style:--card-gap={gap}
|
|
128
|
+
style:max-width={maxWidth}
|
|
95
129
|
style={`${stackStyle}${style}`}
|
|
130
|
+
role={interactive && !native ? 'button' : undefined}
|
|
131
|
+
tabindex={interactive && !native ? 0 : undefined}
|
|
132
|
+
onclick={handleClick}
|
|
133
|
+
onkeydown={handleKeydown}
|
|
96
134
|
{...rest}
|
|
97
135
|
>
|
|
98
136
|
{#if framed}
|
|
@@ -177,6 +215,9 @@
|
|
|
177
215
|
.card-tap:hover {
|
|
178
216
|
border-color: var(--border-strong);
|
|
179
217
|
}
|
|
218
|
+
.card-max {
|
|
219
|
+
width: 100%;
|
|
220
|
+
}
|
|
180
221
|
|
|
181
222
|
.card-ok {
|
|
182
223
|
--card-tone: var(--ok);
|
|
@@ -190,13 +231,21 @@
|
|
|
190
231
|
.card-info {
|
|
191
232
|
--card-tone: var(--info);
|
|
192
233
|
}
|
|
234
|
+
.card-attention {
|
|
235
|
+
--card-tone: var(--attention-bar);
|
|
236
|
+
}
|
|
193
237
|
.card-ok,
|
|
194
238
|
.card-warn,
|
|
195
239
|
.card-danger,
|
|
196
|
-
.card-info
|
|
240
|
+
.card-info,
|
|
241
|
+
.card-attention {
|
|
197
242
|
border-color: color-mix(in srgb, var(--card-tone) 55%, var(--border));
|
|
198
243
|
background: color-mix(in srgb, var(--card-tone) 8%, var(--bg-elevated));
|
|
199
244
|
}
|
|
245
|
+
.card-attention {
|
|
246
|
+
background: var(--attention-bg);
|
|
247
|
+
box-shadow: inset 3px 0 0 var(--attention-bar);
|
|
248
|
+
}
|
|
200
249
|
|
|
201
250
|
/* Stacked effect — two back layers peeking out bottom-right. The front
|
|
202
251
|
surface keeps its own background so the layers only show at the edges. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
2
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info' | 'attention';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
tap?: boolean;
|
|
5
|
+
interactive?: boolean;
|
|
5
6
|
as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
|
|
6
7
|
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
7
8
|
surface?: 'base' | 'raised' | 'sunken';
|
|
@@ -13,6 +14,8 @@ type $$ComponentProps = {
|
|
|
13
14
|
title?: string;
|
|
14
15
|
subtitle?: string;
|
|
15
16
|
gap?: string;
|
|
17
|
+
maxWidth?: string;
|
|
18
|
+
onclick?: (e: MouseEvent | KeyboardEvent) => void;
|
|
16
19
|
class?: string;
|
|
17
20
|
style?: string;
|
|
18
21
|
header?: Snippet;
|
package/package.json
CHANGED