@dorsk/tsumikit 0.2.7 → 0.2.9
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/components/atoms/Badge.svelte +38 -0
- package/dist/components/atoms/Badge.svelte.d.ts +2 -0
- package/dist/components/atoms/Button.svelte +54 -0
- package/dist/components/atoms/Button.svelte.d.ts +2 -0
- package/dist/components/atoms/Card.svelte +105 -1
- package/dist/components/atoms/Card.svelte.d.ts +7 -0
- package/dist/components/atoms/Progress.svelte +15 -2
- package/dist/components/atoms/Progress.svelte.d.ts +1 -0
- package/dist/components/atoms/Select.svelte +31 -5
- package/dist/components/atoms/Select.svelte.d.ts +4 -0
- package/dist/components/atoms/Text.svelte +16 -2
- package/dist/components/atoms/Text.svelte.d.ts +2 -1
- package/dist/components/layouts/AppShell.svelte +9 -5
- package/dist/components/molecules/IconButton.svelte +9 -1
- package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
as = 'span',
|
|
16
16
|
size = 'md',
|
|
17
17
|
mono = false,
|
|
18
|
+
uppercase = false,
|
|
19
|
+
active = false,
|
|
18
20
|
removable = false,
|
|
19
21
|
onremove,
|
|
20
22
|
class: klass = '',
|
|
@@ -25,6 +27,11 @@
|
|
|
25
27
|
as?: 'span' | 'button';
|
|
26
28
|
size?: 'sm' | 'md';
|
|
27
29
|
mono?: boolean;
|
|
30
|
+
// Uppercase, letter-spaced label — for status tags/eyebrows.
|
|
31
|
+
uppercase?: boolean;
|
|
32
|
+
// Interactive "on" state for a toggle/count badge (`as="button"`): fills the
|
|
33
|
+
// pill with its tone instead of just tinting the border.
|
|
34
|
+
active?: boolean;
|
|
28
35
|
removable?: boolean;
|
|
29
36
|
onremove?: (e: MouseEvent) => void;
|
|
30
37
|
class?: string;
|
|
@@ -42,6 +49,8 @@
|
|
|
42
49
|
class:badge-info={tone === 'info'}
|
|
43
50
|
class:badge-sm={size === 'sm'}
|
|
44
51
|
class:mono
|
|
52
|
+
class:uppercase
|
|
53
|
+
class:active
|
|
45
54
|
class:interactive={as === 'button'}
|
|
46
55
|
{...rest}
|
|
47
56
|
>
|
|
@@ -103,6 +112,30 @@
|
|
|
103
112
|
font-family: var(--font-mono);
|
|
104
113
|
font-weight: var(--fw-normal);
|
|
105
114
|
}
|
|
115
|
+
.uppercase {
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
letter-spacing: 0.04em;
|
|
118
|
+
font-weight: var(--fw-semibold);
|
|
119
|
+
}
|
|
120
|
+
/* Interactive "on" state: fill the pill with its tone. Falls back to the
|
|
121
|
+
neutral accent when no semantic tone is set. */
|
|
122
|
+
.active {
|
|
123
|
+
color: var(--text-on-accent);
|
|
124
|
+
background: var(--badge-fill, var(--accent));
|
|
125
|
+
border-color: var(--badge-fill, var(--accent));
|
|
126
|
+
}
|
|
127
|
+
.badge-ok.active {
|
|
128
|
+
--badge-fill: var(--ok);
|
|
129
|
+
}
|
|
130
|
+
.badge-warn.active {
|
|
131
|
+
--badge-fill: var(--warn);
|
|
132
|
+
}
|
|
133
|
+
.badge-danger.active {
|
|
134
|
+
--badge-fill: var(--danger);
|
|
135
|
+
}
|
|
136
|
+
.badge-info.active {
|
|
137
|
+
--badge-fill: var(--info);
|
|
138
|
+
}
|
|
106
139
|
.interactive {
|
|
107
140
|
cursor: pointer;
|
|
108
141
|
transition:
|
|
@@ -113,6 +146,11 @@
|
|
|
113
146
|
border-color: var(--border-strong);
|
|
114
147
|
color: var(--text);
|
|
115
148
|
}
|
|
149
|
+
.interactive.active:hover {
|
|
150
|
+
color: var(--text-on-accent);
|
|
151
|
+
border-color: var(--badge-fill, var(--accent));
|
|
152
|
+
filter: brightness(1.08);
|
|
153
|
+
}
|
|
116
154
|
.remove {
|
|
117
155
|
display: inline-flex;
|
|
118
156
|
align-items: center;
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
type ButtonProps = HTMLButtonAttributes & {
|
|
6
6
|
variant?: 'default' | 'primary' | 'ghost' | 'danger';
|
|
7
|
+
// Semantic state tint layered on top of the variant: tints text/border and
|
|
8
|
+
// adds a subtle fill on hover. For stateful controls — an "on"/active toggle
|
|
9
|
+
// (`accent`), or cost/severity states (`info`, `warn`).
|
|
10
|
+
tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
|
|
7
11
|
size?: 'sm' | 'md';
|
|
8
12
|
control?: boolean;
|
|
9
13
|
block?: boolean;
|
|
@@ -11,6 +15,9 @@
|
|
|
11
15
|
// borderless, compact variant (chip-remove ✕, inline edit ✎); pair with
|
|
12
16
|
// `hoverDanger` to tint it red on hover (delete affordances).
|
|
13
17
|
icon?: boolean;
|
|
18
|
+
// Larger 2.5rem outlined square icon-chip (header/toolbar action). Pairs
|
|
19
|
+
// with `tone` for tinted severity chips (back/archive/interrupt/more).
|
|
20
|
+
chip?: boolean;
|
|
14
21
|
iconInline?: boolean;
|
|
15
22
|
hoverDanger?: boolean;
|
|
16
23
|
// Async/busy state: shows a spinner, blocks clicks, sets aria-busy. Stays
|
|
@@ -22,10 +29,12 @@
|
|
|
22
29
|
|
|
23
30
|
let {
|
|
24
31
|
variant = 'default',
|
|
32
|
+
tone = 'none',
|
|
25
33
|
size = 'md',
|
|
26
34
|
control = false,
|
|
27
35
|
block = false,
|
|
28
36
|
icon = false,
|
|
37
|
+
chip = false,
|
|
29
38
|
iconInline = false,
|
|
30
39
|
hoverDanger = false,
|
|
31
40
|
loading = false,
|
|
@@ -53,6 +62,11 @@
|
|
|
53
62
|
class:btn-control={control}
|
|
54
63
|
class:btn-block={block}
|
|
55
64
|
class:btn-icon={icon}
|
|
65
|
+
class:btn-chip={chip}
|
|
66
|
+
class:btn-tone-accent={tone === 'accent'}
|
|
67
|
+
class:btn-tone-info={tone === 'info'}
|
|
68
|
+
class:btn-tone-warn={tone === 'warn'}
|
|
69
|
+
class:btn-tone-danger={tone === 'danger'}
|
|
56
70
|
class:btn-icon-inline={iconInline}
|
|
57
71
|
class:hover-danger={hoverDanger}
|
|
58
72
|
class:loading
|
|
@@ -129,6 +143,46 @@
|
|
|
129
143
|
width: 100%;
|
|
130
144
|
}
|
|
131
145
|
|
|
146
|
+
/* Semantic state tones — tint text + border, subtle fill on hover. Layered on
|
|
147
|
+
the neutral/ghost variant; each defines --btn-tone so one ruleset paints. */
|
|
148
|
+
.btn-tone-accent {
|
|
149
|
+
--btn-tone: var(--accent);
|
|
150
|
+
}
|
|
151
|
+
.btn-tone-info {
|
|
152
|
+
--btn-tone: var(--info);
|
|
153
|
+
}
|
|
154
|
+
.btn-tone-warn {
|
|
155
|
+
--btn-tone: var(--warn);
|
|
156
|
+
}
|
|
157
|
+
.btn-tone-danger {
|
|
158
|
+
--btn-tone: var(--danger);
|
|
159
|
+
}
|
|
160
|
+
.btn-tone-accent,
|
|
161
|
+
.btn-tone-info,
|
|
162
|
+
.btn-tone-warn,
|
|
163
|
+
.btn-tone-danger {
|
|
164
|
+
color: var(--btn-tone);
|
|
165
|
+
border-color: color-mix(in srgb, var(--btn-tone) 50%, var(--border));
|
|
166
|
+
}
|
|
167
|
+
.btn-tone-accent:hover:not(:disabled),
|
|
168
|
+
.btn-tone-info:hover:not(:disabled),
|
|
169
|
+
.btn-tone-warn:hover:not(:disabled),
|
|
170
|
+
.btn-tone-danger:hover:not(:disabled) {
|
|
171
|
+
background: color-mix(in srgb, var(--btn-tone) 14%, transparent);
|
|
172
|
+
border-color: var(--btn-tone);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Icon-chip: larger square outlined tap target for header/toolbar actions.
|
|
176
|
+
Combines with a tone for tinted severity chips. */
|
|
177
|
+
.btn-chip {
|
|
178
|
+
min-height: 2.5rem;
|
|
179
|
+
min-width: 2.5rem;
|
|
180
|
+
height: 2.5rem;
|
|
181
|
+
width: 2.5rem;
|
|
182
|
+
padding: 0;
|
|
183
|
+
border-radius: var(--r-md);
|
|
184
|
+
}
|
|
185
|
+
|
|
132
186
|
/* Uniform-height control: icon buttons, inputs and action buttons that share a
|
|
133
187
|
toolbar/composer row line up exactly. */
|
|
134
188
|
.btn-control {
|
|
@@ -2,10 +2,12 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
type ButtonProps = HTMLButtonAttributes & {
|
|
4
4
|
variant?: 'default' | 'primary' | 'ghost' | 'danger';
|
|
5
|
+
tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
|
|
5
6
|
size?: 'sm' | 'md';
|
|
6
7
|
control?: boolean;
|
|
7
8
|
block?: boolean;
|
|
8
9
|
icon?: boolean;
|
|
10
|
+
chip?: boolean;
|
|
9
11
|
iconInline?: boolean;
|
|
10
12
|
hoverDanger?: boolean;
|
|
11
13
|
loading?: boolean;
|
|
@@ -3,24 +3,64 @@
|
|
|
3
3
|
// background/border/radius/padding from theme tokens. `tap` adds the
|
|
4
4
|
// interactive hover/active affordance for tappable list items (e.g. session
|
|
5
5
|
// rows); `as` lets it be a button/anchor when the whole surface is clickable.
|
|
6
|
+
// `padding` dials the inner spacing (none/sm/md/lg) for denser cards.
|
|
7
|
+
//
|
|
8
|
+
// `stacked` fakes a pile of cards by drawing two layers peeking out below
|
|
9
|
+
// (and optionally to the right) via pseudo-elements. `stackTone` tints those
|
|
10
|
+
// back layers with a semantic hue (e.g. `info` for a blue stack); `neutral`
|
|
11
|
+
// keeps them on the plain border colour. `stackY`/`stackX` set the per-layer
|
|
12
|
+
// vertical / horizontal offset in px (vertical spacing stays even across the
|
|
13
|
+
// 3 borders); horizontal defaults to a tiny 2px peek.
|
|
6
14
|
import type { Snippet } from 'svelte';
|
|
7
15
|
|
|
16
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
17
|
+
|
|
8
18
|
let {
|
|
9
19
|
tap = false,
|
|
10
20
|
as = 'div',
|
|
21
|
+
padding = 'md',
|
|
22
|
+
stacked = false,
|
|
23
|
+
stackTone = 'neutral',
|
|
24
|
+
stackY = 8,
|
|
25
|
+
stackX = 2,
|
|
11
26
|
class: klass = '',
|
|
27
|
+
style = '',
|
|
12
28
|
children,
|
|
13
29
|
...rest
|
|
14
30
|
}: {
|
|
15
31
|
tap?: boolean;
|
|
16
32
|
as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
|
|
33
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
34
|
+
stacked?: boolean;
|
|
35
|
+
stackTone?: Tone;
|
|
36
|
+
stackY?: number;
|
|
37
|
+
stackX?: number;
|
|
17
38
|
class?: string;
|
|
39
|
+
style?: string;
|
|
18
40
|
children?: Snippet;
|
|
19
41
|
[key: string]: unknown;
|
|
20
42
|
} = $props();
|
|
43
|
+
|
|
44
|
+
let stackStyle = $derived(
|
|
45
|
+
stacked ? `--stack-y:${stackY}px;--stack-x:${stackX}px;` : ''
|
|
46
|
+
);
|
|
21
47
|
</script>
|
|
22
48
|
|
|
23
|
-
<svelte:element
|
|
49
|
+
<svelte:element
|
|
50
|
+
this={as}
|
|
51
|
+
class="card {klass}"
|
|
52
|
+
class:pad-none={padding === 'none'}
|
|
53
|
+
class:pad-sm={padding === 'sm'}
|
|
54
|
+
class:pad-lg={padding === 'lg'}
|
|
55
|
+
class:card-tap={tap}
|
|
56
|
+
class:card-stacked={stacked}
|
|
57
|
+
class:stack-ok={stacked && stackTone === 'ok'}
|
|
58
|
+
class:stack-warn={stacked && stackTone === 'warn'}
|
|
59
|
+
class:stack-danger={stacked && stackTone === 'danger'}
|
|
60
|
+
class:stack-info={stacked && stackTone === 'info'}
|
|
61
|
+
style={`${stackStyle}${style}`}
|
|
62
|
+
{...rest}
|
|
63
|
+
>
|
|
24
64
|
{@render children?.()}
|
|
25
65
|
</svelte:element>
|
|
26
66
|
|
|
@@ -31,6 +71,15 @@
|
|
|
31
71
|
border-radius: var(--r-lg);
|
|
32
72
|
padding: var(--sp-4);
|
|
33
73
|
}
|
|
74
|
+
.pad-none {
|
|
75
|
+
padding: 0;
|
|
76
|
+
}
|
|
77
|
+
.pad-sm {
|
|
78
|
+
padding: var(--sp-2);
|
|
79
|
+
}
|
|
80
|
+
.pad-lg {
|
|
81
|
+
padding: var(--sp-6);
|
|
82
|
+
}
|
|
34
83
|
.card-tap {
|
|
35
84
|
cursor: pointer;
|
|
36
85
|
transition:
|
|
@@ -43,4 +92,59 @@
|
|
|
43
92
|
.card-tap:hover {
|
|
44
93
|
border-color: var(--border-strong);
|
|
45
94
|
}
|
|
95
|
+
|
|
96
|
+
/* Stacked effect — two back layers peeking out bottom-right. The front
|
|
97
|
+
surface keeps its own background so the layers only show at the edges. */
|
|
98
|
+
.card-stacked {
|
|
99
|
+
position: relative;
|
|
100
|
+
/* Defaults; overridden inline by the stackY/stackX props. */
|
|
101
|
+
--stack-y: 8px;
|
|
102
|
+
--stack-x: 2px;
|
|
103
|
+
--stack-bg: var(--bg-elevated-2);
|
|
104
|
+
--stack-border: var(--border);
|
|
105
|
+
/* Reserve room for the two peeking layers so they aren't clipped. */
|
|
106
|
+
margin-right: calc(var(--stack-x) * 2);
|
|
107
|
+
margin-bottom: calc(var(--stack-y) * 2);
|
|
108
|
+
}
|
|
109
|
+
.card-stacked::before,
|
|
110
|
+
.card-stacked::after {
|
|
111
|
+
content: '';
|
|
112
|
+
position: absolute;
|
|
113
|
+
inset: 0;
|
|
114
|
+
border-radius: inherit;
|
|
115
|
+
/* Opaque fill so each layer fully hides the one behind it — only the
|
|
116
|
+
bottom-right peek (and its single border line) stays visible. */
|
|
117
|
+
background: var(--stack-bg);
|
|
118
|
+
border: 1px solid var(--stack-border);
|
|
119
|
+
}
|
|
120
|
+
/* Nearest back layer — sits just under the front surface. */
|
|
121
|
+
.card-stacked::before {
|
|
122
|
+
z-index: -1;
|
|
123
|
+
transform: translate(var(--stack-x), var(--stack-y));
|
|
124
|
+
}
|
|
125
|
+
/* Furthest back layer — behind the nearest one. */
|
|
126
|
+
.card-stacked::after {
|
|
127
|
+
z-index: -2;
|
|
128
|
+
transform: translate(
|
|
129
|
+
calc(var(--stack-x) * 2),
|
|
130
|
+
calc(var(--stack-y) * 2)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.stack-ok {
|
|
135
|
+
--stack-border: color-mix(in srgb, var(--ok) 45%, transparent);
|
|
136
|
+
--stack-bg: color-mix(in srgb, var(--ok) 12%, var(--bg-elevated));
|
|
137
|
+
}
|
|
138
|
+
.stack-warn {
|
|
139
|
+
--stack-border: color-mix(in srgb, var(--warn) 45%, transparent);
|
|
140
|
+
--stack-bg: color-mix(in srgb, var(--warn) 12%, var(--bg-elevated));
|
|
141
|
+
}
|
|
142
|
+
.stack-danger {
|
|
143
|
+
--stack-border: color-mix(in srgb, var(--danger) 45%, transparent);
|
|
144
|
+
--stack-bg: color-mix(in srgb, var(--danger) 12%, var(--bg-elevated));
|
|
145
|
+
}
|
|
146
|
+
.stack-info {
|
|
147
|
+
--stack-border: color-mix(in srgb, var(--info) 45%, transparent);
|
|
148
|
+
--stack-bg: color-mix(in srgb, var(--info) 12%, var(--bg-elevated));
|
|
149
|
+
}
|
|
46
150
|
</style>
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
|
|
2
3
|
type $$ComponentProps = {
|
|
3
4
|
tap?: boolean;
|
|
4
5
|
as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
|
|
6
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
7
|
+
stacked?: boolean;
|
|
8
|
+
stackTone?: Tone;
|
|
9
|
+
stackY?: number;
|
|
10
|
+
stackX?: number;
|
|
5
11
|
class?: string;
|
|
12
|
+
style?: string;
|
|
6
13
|
children?: Snippet;
|
|
7
14
|
[key: string]: unknown;
|
|
8
15
|
};
|
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
value,
|
|
7
7
|
max = 100,
|
|
8
8
|
label,
|
|
9
|
+
tone = 'accent',
|
|
9
10
|
class: klass = ''
|
|
10
11
|
}: {
|
|
11
12
|
value?: number;
|
|
12
13
|
max?: number;
|
|
13
14
|
label?: string;
|
|
15
|
+
// Fill colour. `accent` is the default brand fill; the semantic tones
|
|
16
|
+
// retint the bar for severity (e.g. usage meters going warm/hot).
|
|
17
|
+
tone?: 'accent' | 'success' | 'warn' | 'danger';
|
|
14
18
|
class?: string;
|
|
15
19
|
} = $props();
|
|
16
20
|
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
</script>
|
|
20
24
|
|
|
21
25
|
<div
|
|
22
|
-
class="progress {klass}"
|
|
26
|
+
class="progress tone-{tone} {klass}"
|
|
23
27
|
class:indeterminate
|
|
24
28
|
role="progressbar"
|
|
25
29
|
aria-label={label}
|
|
@@ -40,10 +44,19 @@
|
|
|
40
44
|
}
|
|
41
45
|
.bar {
|
|
42
46
|
height: 100%;
|
|
43
|
-
background: var(--accent);
|
|
47
|
+
background: var(--fill, var(--accent));
|
|
44
48
|
border-radius: inherit;
|
|
45
49
|
transition: width 0.2s var(--ease);
|
|
46
50
|
}
|
|
51
|
+
.tone-success {
|
|
52
|
+
--fill: var(--ok);
|
|
53
|
+
}
|
|
54
|
+
.tone-warn {
|
|
55
|
+
--fill: var(--warn);
|
|
56
|
+
}
|
|
57
|
+
.tone-danger {
|
|
58
|
+
--fill: var(--danger);
|
|
59
|
+
}
|
|
47
60
|
.progress.indeterminate .bar {
|
|
48
61
|
width: 40%;
|
|
49
62
|
animation: progress-slide 1.1s ease-in-out infinite;
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
variant?: 'default' | 'ghost';
|
|
20
20
|
/** Error state: danger border + aria-invalid. */
|
|
21
21
|
invalid?: boolean;
|
|
22
|
+
/** Compact inline form: smaller padding + font for dense toolbars/headers. */
|
|
23
|
+
compact?: boolean;
|
|
24
|
+
/** Draw the custom chevron (default). Set false for a bare inline select. */
|
|
25
|
+
chevron?: boolean;
|
|
22
26
|
class?: string;
|
|
23
27
|
value?: HTMLSelectAttributes['value'];
|
|
24
28
|
children?: Snippet;
|
|
@@ -27,6 +31,8 @@
|
|
|
27
31
|
let {
|
|
28
32
|
variant = 'default',
|
|
29
33
|
invalid = false,
|
|
34
|
+
compact = false,
|
|
35
|
+
chevron = true,
|
|
30
36
|
class: klass = '',
|
|
31
37
|
value = $bindable(),
|
|
32
38
|
children,
|
|
@@ -39,13 +45,21 @@
|
|
|
39
45
|
{@render children?.()}
|
|
40
46
|
</select>
|
|
41
47
|
{:else}
|
|
42
|
-
<div class="select-wrap {klass}">
|
|
43
|
-
<select
|
|
48
|
+
<div class="select-wrap {klass}" class:no-chevron={!chevron}>
|
|
49
|
+
<select
|
|
50
|
+
class="select"
|
|
51
|
+
class:compact
|
|
52
|
+
bind:value
|
|
53
|
+
{...rest}
|
|
54
|
+
aria-invalid={invalid || undefined}
|
|
55
|
+
>
|
|
44
56
|
{@render children?.()}
|
|
45
57
|
</select>
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
58
|
+
{#if chevron}
|
|
59
|
+
<span class="select-chevron" aria-hidden="true">
|
|
60
|
+
<Icon name="chevron-down" size={16} />
|
|
61
|
+
</span>
|
|
62
|
+
{/if}
|
|
49
63
|
</div>
|
|
50
64
|
{/if}
|
|
51
65
|
|
|
@@ -70,6 +84,18 @@
|
|
|
70
84
|
-webkit-appearance: none;
|
|
71
85
|
padding-right: calc(var(--sp-3) + 1.25rem);
|
|
72
86
|
}
|
|
87
|
+
/* No custom chevron: reclaim the reserved right padding. */
|
|
88
|
+
.select-wrap.no-chevron .select {
|
|
89
|
+
padding-right: var(--sp-3);
|
|
90
|
+
}
|
|
91
|
+
/* Compact inline form for dense headers/toolbars. */
|
|
92
|
+
.select.compact {
|
|
93
|
+
padding: var(--sp-1) var(--sp-2);
|
|
94
|
+
font-size: var(--fs-xs);
|
|
95
|
+
}
|
|
96
|
+
.select-wrap:not(.no-chevron) .select.compact {
|
|
97
|
+
padding-right: calc(var(--sp-2) + 1rem);
|
|
98
|
+
}
|
|
73
99
|
.select:focus {
|
|
74
100
|
outline: none;
|
|
75
101
|
border-color: var(--accent);
|
|
@@ -4,6 +4,10 @@ type Props = HTMLSelectAttributes & {
|
|
|
4
4
|
variant?: 'default' | 'ghost';
|
|
5
5
|
/** Error state: danger border + aria-invalid. */
|
|
6
6
|
invalid?: boolean;
|
|
7
|
+
/** Compact inline form: smaller padding + font for dense toolbars/headers. */
|
|
8
|
+
compact?: boolean;
|
|
9
|
+
/** Draw the custom chevron (default). Set false for a bare inline select. */
|
|
10
|
+
chevron?: boolean;
|
|
7
11
|
class?: string;
|
|
8
12
|
value?: HTMLSelectAttributes['value'];
|
|
9
13
|
children?: Snippet;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
tone = 'inherit',
|
|
15
15
|
weight,
|
|
16
16
|
size,
|
|
17
|
+
numeric = false,
|
|
17
18
|
truncate = false,
|
|
18
19
|
class: klass = '',
|
|
19
20
|
children,
|
|
@@ -23,9 +24,12 @@
|
|
|
23
24
|
// body: default reading text · label: form-label · caption: small meta ·
|
|
24
25
|
// code: monospace. Omit for inline glue that should inherit its parent.
|
|
25
26
|
variant?: 'body' | 'label' | 'caption' | 'code';
|
|
26
|
-
tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'danger' | 'accent';
|
|
27
|
+
tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'success' | 'warn' | 'danger' | 'accent';
|
|
27
28
|
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
|
28
29
|
size?: Size;
|
|
30
|
+
// Tabular figures: digits share a fixed advance width so counts/percentages
|
|
31
|
+
// don't jitter as they change. Use for counters, timers, metrics.
|
|
32
|
+
numeric?: boolean;
|
|
29
33
|
truncate?: boolean;
|
|
30
34
|
class?: string;
|
|
31
35
|
children?: Snippet;
|
|
@@ -37,7 +41,7 @@
|
|
|
37
41
|
this={as}
|
|
38
42
|
class="text {variant ? `v-${variant}` : ''} tone-{tone} {weight ? `fw-${weight}` : ''} {size
|
|
39
43
|
? `fs-${size}`
|
|
40
|
-
: ''} {truncate ? 'truncate' : ''} {klass}"
|
|
44
|
+
: ''} {numeric ? 'numeric' : ''} {truncate ? 'truncate' : ''} {klass}"
|
|
41
45
|
{...rest}
|
|
42
46
|
>
|
|
43
47
|
{@render children?.()}
|
|
@@ -77,6 +81,12 @@
|
|
|
77
81
|
.tone-faint {
|
|
78
82
|
color: var(--text-faint);
|
|
79
83
|
}
|
|
84
|
+
.tone-success {
|
|
85
|
+
color: var(--ok);
|
|
86
|
+
}
|
|
87
|
+
.tone-warn {
|
|
88
|
+
color: var(--warn);
|
|
89
|
+
}
|
|
80
90
|
.tone-danger {
|
|
81
91
|
color: var(--danger);
|
|
82
92
|
}
|
|
@@ -118,6 +128,10 @@
|
|
|
118
128
|
.fs-2xl {
|
|
119
129
|
font-size: var(--fs-2xl);
|
|
120
130
|
}
|
|
131
|
+
.numeric {
|
|
132
|
+
font-variant-numeric: tabular-nums;
|
|
133
|
+
font-feature-settings: 'tnum';
|
|
134
|
+
}
|
|
121
135
|
/* `text-overflow: ellipsis` is a no-op on an inline box, so a truncated bare
|
|
122
136
|
<Text> (default as="span") would overrun its container instead of clipping.
|
|
123
137
|
inline-block gives it a block formatting context so the ellipsis applies,
|
|
@@ -3,9 +3,10 @@ type Size = 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | '2xl';
|
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
as?: 'span' | 'p' | 'div' | 'label';
|
|
5
5
|
variant?: 'body' | 'label' | 'caption' | 'code';
|
|
6
|
-
tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'danger' | 'accent';
|
|
6
|
+
tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'success' | 'warn' | 'danger' | 'accent';
|
|
7
7
|
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
|
8
8
|
size?: Size;
|
|
9
|
+
numeric?: boolean;
|
|
9
10
|
truncate?: boolean;
|
|
10
11
|
class?: string;
|
|
11
12
|
children?: Snippet;
|
|
@@ -293,14 +293,18 @@
|
|
|
293
293
|
touch-action: none;
|
|
294
294
|
z-index: 1;
|
|
295
295
|
}
|
|
296
|
+
/* Persistent grip hint (mirrors the Modal): a small pill centered on the
|
|
297
|
+
handle, brightening to the accent on hover / while dragging. */
|
|
296
298
|
.shell-sidebar-resize::after {
|
|
297
299
|
content: '';
|
|
298
300
|
position: absolute;
|
|
299
|
-
top:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
width:
|
|
303
|
-
|
|
301
|
+
top: 50%;
|
|
302
|
+
right: 1px;
|
|
303
|
+
transform: translateY(-50%);
|
|
304
|
+
width: 3px;
|
|
305
|
+
height: 28px;
|
|
306
|
+
border-radius: 999px;
|
|
307
|
+
background: var(--border-strong);
|
|
304
308
|
transition: background 0.12s var(--ease);
|
|
305
309
|
}
|
|
306
310
|
.shell-sidebar-resize:hover::after,
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
children?: Snippet;
|
|
14
14
|
label: string;
|
|
15
15
|
variant?: 'default' | 'primary' | 'ghost' | 'danger';
|
|
16
|
+
// Semantic state tint (forwarded to Button). Pairs well with `chip`.
|
|
17
|
+
tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
|
|
18
|
+
// Larger 2.5rem outlined square icon-chip (header/toolbar actions).
|
|
19
|
+
chip?: boolean;
|
|
16
20
|
size?: number;
|
|
17
21
|
// Borderless, compact icon affordance (chip-remove ✕, inline edit ✎) —
|
|
18
22
|
// no square box; just a muted glyph that brightens on hover. Pair with
|
|
@@ -32,6 +36,8 @@
|
|
|
32
36
|
label,
|
|
33
37
|
title = label,
|
|
34
38
|
variant = 'ghost',
|
|
39
|
+
tone = 'none',
|
|
40
|
+
chip = false,
|
|
35
41
|
size = 18,
|
|
36
42
|
inline = false,
|
|
37
43
|
hoverDanger = false,
|
|
@@ -48,10 +54,12 @@
|
|
|
48
54
|
<Button
|
|
49
55
|
{...rest}
|
|
50
56
|
{variant}
|
|
57
|
+
{tone}
|
|
58
|
+
{chip}
|
|
51
59
|
{disabled}
|
|
52
60
|
{title}
|
|
53
61
|
{onclick}
|
|
54
|
-
icon={!inline}
|
|
62
|
+
icon={!inline && !chip}
|
|
55
63
|
iconInline={inline}
|
|
56
64
|
{hoverDanger}
|
|
57
65
|
aria-pressed={pressed}
|
|
@@ -9,6 +9,8 @@ type IconButtonProps = HTMLButtonAttributes & {
|
|
|
9
9
|
children?: Snippet;
|
|
10
10
|
label: string;
|
|
11
11
|
variant?: 'default' | 'primary' | 'ghost' | 'danger';
|
|
12
|
+
tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
|
|
13
|
+
chip?: boolean;
|
|
12
14
|
size?: number;
|
|
13
15
|
inline?: boolean;
|
|
14
16
|
hoverDanger?: boolean;
|
package/package.json
CHANGED