@dorsk/tsumikit 0.28.0 → 0.30.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.
@@ -3,17 +3,24 @@
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
 
5
5
  type ButtonProps = HTMLButtonAttributes & {
6
- variant?: 'default' | 'primary' | 'ghost' | 'danger';
6
+ // `link`: no box at all an inline text action coloured like a link
7
+ // (or like `tone` when set), underlined on hover/focus.
8
+ variant?: 'default' | 'primary' | 'ghost' | 'danger' | 'link';
7
9
  // Semantic state tint layered on top of the variant: tints text/border and
8
10
  // adds a subtle fill on hover. For stateful controls — an "on"/active toggle
9
11
  // (`accent`), positive actions (`success`), or cost/severity states.
10
12
  tone?: 'none' | 'accent' | 'success' | 'info' | 'warn' | 'danger';
11
13
  size?: 'sm' | 'md' | 'lg';
12
14
  control?: boolean;
15
+ // Fully rounded ends (`--r-pill`) on any size/variant.
16
+ pill?: boolean;
13
17
  block?: boolean;
14
18
  // Fill the available width of a flex/Cluster row (flex: 1). `block` stretches
15
19
  // to 100% (grid/stack); `grow` shares a flex row with siblings.
16
20
  grow?: boolean;
21
+ // `shrink={false}` pins the button to its intrinsic width in a flex row
22
+ // (flex: none, top-aligned) so a growing sibling can't squash it.
23
+ shrink?: boolean;
17
24
  // Render as a link (`<a href>`) while keeping button chrome — for navigations
18
25
  // and open-in-new-tab actions that shouldn't be a bare <a>. Setting `href`
19
26
  // implies `as="a"`.
@@ -56,8 +63,10 @@
56
63
  tone = 'none',
57
64
  size = 'md',
58
65
  control = false,
66
+ pill = false,
59
67
  block = false,
60
68
  grow = false,
69
+ shrink = true,
61
70
  as = 'button',
62
71
  href,
63
72
  icon = false,
@@ -113,6 +122,9 @@
113
122
  class:btn-primary={variant === 'primary'}
114
123
  class:btn-ghost={variant === 'ghost'}
115
124
  class:btn-danger={variant === 'danger' || (tone === 'danger' && variant === 'default')}
125
+ class:btn-link={variant === 'link'}
126
+ class:btn-pill={pill}
127
+ class:no-shrink={!shrink}
116
128
  class:btn-sm={size === 'sm'}
117
129
  class:btn-lg={size === 'lg'}
118
130
  class:btn-control={control}
@@ -354,6 +366,33 @@
354
366
  filter: brightness(1.08);
355
367
  }
356
368
 
369
+ .no-shrink {
370
+ flex: none;
371
+ align-self: flex-start;
372
+ }
373
+ .btn-pill {
374
+ border-radius: var(--r-pill);
375
+ }
376
+ /* Link variant: no box, inherits the surrounding text size. Placed after the
377
+ size rules so their min-height doesn't reapply; a tone wins over --link. */
378
+ .btn-link {
379
+ min-height: auto;
380
+ height: auto;
381
+ padding: 0 var(--sp-1);
382
+ border: none;
383
+ background: none;
384
+ box-shadow: none;
385
+ border-radius: 0;
386
+ font-size: inherit;
387
+ line-height: 1;
388
+ color: var(--btn-tone, var(--link));
389
+ }
390
+ .btn-link:hover:not(:disabled),
391
+ .btn-link:focus-visible {
392
+ background: none;
393
+ border: none;
394
+ text-decoration: underline;
395
+ }
357
396
  /* Icon-only buttons (IconButton). Square box = consistent --box-md tap target. */
358
397
  .btn-icon {
359
398
  min-height: var(--box-md);
@@ -1,12 +1,14 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  type ButtonProps = HTMLButtonAttributes & {
4
- variant?: 'default' | 'primary' | 'ghost' | 'danger';
4
+ variant?: 'default' | 'primary' | 'ghost' | 'danger' | 'link';
5
5
  tone?: 'none' | 'accent' | 'success' | 'info' | 'warn' | 'danger';
6
6
  size?: 'sm' | 'md' | 'lg';
7
7
  control?: boolean;
8
+ pill?: boolean;
8
9
  block?: boolean;
9
10
  grow?: boolean;
11
+ shrink?: boolean;
10
12
  as?: 'button' | 'a';
11
13
  href?: string;
12
14
  target?: string;
@@ -11,6 +11,15 @@
11
11
  level = 2,
12
12
  size,
13
13
  tone = 'default',
14
+ truncate = false,
15
+ italic = false,
16
+ nowrap = false,
17
+ wrap = 'normal',
18
+ uppercase = false,
19
+ leading,
20
+ measure,
21
+ grow = false,
22
+ scale = true,
14
23
  class: klass = '',
15
24
  children,
16
25
  ...rest
@@ -18,6 +27,17 @@
18
27
  level?: 1 | 2 | 3 | 4 | 5 | 6;
19
28
  size?: Size;
20
29
  tone?: 'default' | 'muted' | 'faint';
30
+ truncate?: boolean;
31
+ italic?: boolean;
32
+ nowrap?: boolean;
33
+ wrap?: 'normal' | 'anywhere' | 'balance';
34
+ uppercase?: boolean;
35
+ leading?: 'tight' | 'normal' | 'none';
36
+ // CSS max-width for the line measure, e.g. "40ch".
37
+ measure?: string;
38
+ grow?: boolean;
39
+ // false pins the size to its unscaled px value, ignoring --fs-scale.
40
+ scale?: boolean;
21
41
  class?: string;
22
42
  children?: Snippet;
23
43
  [key: string]: unknown;
@@ -26,9 +46,22 @@
26
46
  // Default display size per rank (h1 biggest). `size` overrides it.
27
47
  const DEFAULT_SIZE: Record<number, Size> = { 1: '2xl', 2: 'xl', 3: 'lg', 4: 'md', 5: 'sm', 6: 'xs' };
28
48
  const fs = $derived(size ?? DEFAULT_SIZE[level]);
49
+ const styleAttr = $derived(measure ? `max-width: ${measure}` : undefined);
29
50
  </script>
30
51
 
31
- <svelte:element this={`h${level}`} data-tsu="Heading" class="heading fs-{fs} tone-{tone} {klass}" {...rest}>
52
+ <svelte:element
53
+ this={`h${level}`}
54
+ data-tsu="Heading"
55
+ class="heading fs-{fs} tone-{tone} {leading ? `lh-${leading}` : ''} {wrap !== 'normal' ? `wrap-${wrap}` : ''} {klass}"
56
+ class:truncate
57
+ class:italic
58
+ class:nowrap
59
+ class:uppercase
60
+ class:grow
61
+ class:noscale={!scale}
62
+ style={styleAttr}
63
+ {...rest}
64
+ >
32
65
  {@render children?.()}
33
66
  </svelte:element>
34
67
 
@@ -63,4 +96,59 @@
63
96
  .fs-2xl {
64
97
  font-size: var(--fs-2xl);
65
98
  }
99
+ .noscale.fs-xs {
100
+ font-size: 12px;
101
+ }
102
+ .noscale.fs-sm {
103
+ font-size: 13px;
104
+ }
105
+ .noscale.fs-md {
106
+ font-size: 16px;
107
+ }
108
+ .noscale.fs-lg {
109
+ font-size: 18px;
110
+ }
111
+ .noscale.fs-xl {
112
+ font-size: 22px;
113
+ }
114
+ .noscale.fs-2xl {
115
+ font-size: 28px;
116
+ }
117
+ .truncate {
118
+ max-width: 100%;
119
+ overflow: hidden;
120
+ text-overflow: ellipsis;
121
+ white-space: nowrap;
122
+ min-width: 0;
123
+ }
124
+ .italic {
125
+ font-style: italic;
126
+ }
127
+ .nowrap {
128
+ white-space: nowrap;
129
+ }
130
+ .wrap-anywhere {
131
+ min-width: 0;
132
+ overflow-wrap: anywhere;
133
+ }
134
+ .wrap-balance {
135
+ text-wrap: balance;
136
+ }
137
+ .uppercase {
138
+ text-transform: uppercase;
139
+ letter-spacing: 0.04em;
140
+ }
141
+ .lh-tight {
142
+ line-height: var(--lh-tight);
143
+ }
144
+ .lh-normal {
145
+ line-height: var(--lh-normal);
146
+ }
147
+ .lh-none {
148
+ line-height: 1;
149
+ }
150
+ .grow {
151
+ flex: 1 1 0;
152
+ min-width: 0;
153
+ }
66
154
  </style>
@@ -4,6 +4,15 @@ type $$ComponentProps = {
4
4
  level?: 1 | 2 | 3 | 4 | 5 | 6;
5
5
  size?: Size;
6
6
  tone?: 'default' | 'muted' | 'faint';
7
+ truncate?: boolean;
8
+ italic?: boolean;
9
+ nowrap?: boolean;
10
+ wrap?: 'normal' | 'anywhere' | 'balance';
11
+ uppercase?: boolean;
12
+ leading?: 'tight' | 'normal' | 'none';
13
+ measure?: string;
14
+ grow?: boolean;
15
+ scale?: boolean;
7
16
  class?: string;
8
17
  children?: Snippet;
9
18
  [key: string]: unknown;
@@ -24,7 +24,8 @@
24
24
  label?: string;
25
25
  // Fill colour. `accent` is the default brand fill; the semantic tones
26
26
  // retint the bar for severity (e.g. usage meters going warm/hot).
27
- tone?: 'accent' | 'success' | 'warn' | 'danger';
27
+ // `ok` and `success` are aliases.
28
+ tone?: 'accent' | 'ok' | 'success' | 'warn' | 'danger';
28
29
  // Track height. `sm` is a thin ~5px track for inline rows.
29
30
  size?: 'sm' | 'md';
30
31
  // Accent→teal gradient fill (overrides the flat tone colour).
@@ -38,11 +39,12 @@
38
39
 
39
40
  const pct = $derived(value == null ? 0 : Math.max(0, Math.min(100, (value / max) * 100)));
40
41
  const indeterminate = $derived(indeterminateProp || value == null);
42
+ const toneClass = $derived(tone === 'ok' ? 'success' : tone);
41
43
  </script>
42
44
 
43
45
  <div
44
46
  data-tsu="Progress"
45
- class="progress tone-{tone} size-{size} {klass}"
47
+ class="progress tone-{toneClass} size-{size} {klass}"
46
48
  class:indeterminate
47
49
  class:gradient
48
50
  class:striped
@@ -2,7 +2,7 @@ type $$ComponentProps = {
2
2
  value?: number;
3
3
  max?: number;
4
4
  label?: string;
5
- tone?: 'accent' | 'success' | 'warn' | 'danger';
5
+ tone?: 'accent' | 'ok' | 'success' | 'warn' | 'danger';
6
6
  size?: 'sm' | 'md';
7
7
  gradient?: boolean;
8
8
  striped?: boolean;
@@ -6,6 +6,7 @@
6
6
  // inline glue text without changing rendering. All values come from tokens.
7
7
  import type { Snippet } from 'svelte';
8
8
 
9
+
9
10
  type Size = 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | '2xl';
10
11
 
11
12
  let {
@@ -16,33 +17,68 @@
16
17
  size,
17
18
  numeric = false,
18
19
  truncate = false,
20
+ italic = false,
21
+ nowrap = false,
22
+ wrap = 'normal',
23
+ uppercase = false,
24
+ leading,
25
+ measure,
26
+ grow = false,
27
+ scale = true,
28
+ block = false,
19
29
  class: klass = '',
20
30
  children,
21
31
  ...rest
22
32
  }: {
23
33
  as?: 'span' | 'p' | 'div' | 'label';
24
34
  // body: default reading text · label: form-label · caption: small meta ·
25
- // code: monospace. Omit for inline glue that should inherit its parent.
26
- variant?: 'body' | 'label' | 'caption' | 'code';
27
- tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'success' | 'warn' | 'danger' | 'accent';
35
+ // code: monospace · eyebrow: small uppercase muted kicker. Omit for inline
36
+ // glue that should inherit its parent.
37
+ variant?: 'body' | 'label' | 'caption' | 'code' | 'eyebrow';
38
+ // `ok` and `success` are aliases.
39
+ tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'ok' | 'success' | 'warn' | 'danger' | 'accent';
28
40
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
29
41
  size?: Size;
30
42
  // Tabular figures: digits share a fixed advance width so counts/percentages
31
43
  // don't jitter as they change. Use for counters, timers, metrics.
32
44
  numeric?: boolean;
33
45
  truncate?: boolean;
46
+ italic?: boolean;
47
+ nowrap?: boolean;
48
+ wrap?: 'normal' | 'anywhere' | 'balance';
49
+ uppercase?: boolean;
50
+ leading?: 'tight' | 'normal' | 'none';
51
+ // CSS max-width for the line measure, e.g. "60ch".
52
+ measure?: string;
53
+ grow?: boolean;
54
+ // false pins the size to its unscaled px value, ignoring --fs-scale.
55
+ scale?: boolean;
56
+ block?: boolean;
34
57
  class?: string;
35
58
  children?: Snippet;
36
59
  [key: string]: unknown;
37
60
  } = $props();
61
+
62
+ const toneClass = $derived(tone === 'ok' ? 'success' : tone);
63
+ const styleAttr = $derived(measure ? `max-width: ${measure}` : undefined);
38
64
  </script>
39
65
 
40
66
  <svelte:element
41
67
  this={as}
42
68
  data-tsu="Text"
43
- class="text {variant ? `v-${variant}` : ''} tone-{tone} {weight ? `fw-${weight}` : ''} {size
69
+ class="text {variant ? `v-${variant}` : ''} tone-{toneClass} {weight ? `fw-${weight}` : ''} {size
44
70
  ? `fs-${size}`
45
- : ''} {numeric ? 'numeric' : ''} {truncate ? 'truncate' : ''} {klass}"
71
+ : ''} {numeric ? 'numeric' : ''} {truncate ? 'truncate' : ''} {leading ? `lh-${leading}` : ''} {wrap !==
72
+ 'normal'
73
+ ? `wrap-${wrap}`
74
+ : ''} {klass}"
75
+ class:italic
76
+ class:nowrap
77
+ class:uppercase
78
+ class:grow
79
+ class:block
80
+ class:noscale={!scale}
81
+ style={styleAttr}
46
82
  {...rest}
47
83
  >
48
84
  {@render children?.()}
@@ -72,6 +108,13 @@
72
108
  font-family: var(--font-mono);
73
109
  font-size: 0.92em;
74
110
  }
111
+ .v-eyebrow {
112
+ font-size: var(--fs-xs);
113
+ color: var(--text-muted);
114
+ font-weight: var(--fw-medium);
115
+ text-transform: uppercase;
116
+ letter-spacing: 0.04em;
117
+ }
75
118
  /* Tone (colour) — overrides variant colour. */
76
119
  .tone-default {
77
120
  color: var(--text);
@@ -129,6 +172,60 @@
129
172
  .fs-2xl {
130
173
  font-size: var(--fs-2xl);
131
174
  }
175
+ .noscale.fs-xs {
176
+ font-size: 12px;
177
+ }
178
+ .noscale.fs-sm {
179
+ font-size: 13px;
180
+ }
181
+ .noscale.fs-base {
182
+ font-size: 15px;
183
+ }
184
+ .noscale.fs-md {
185
+ font-size: 16px;
186
+ }
187
+ .noscale.fs-lg {
188
+ font-size: 18px;
189
+ }
190
+ .noscale.fs-xl {
191
+ font-size: 22px;
192
+ }
193
+ .noscale.fs-2xl {
194
+ font-size: 28px;
195
+ }
196
+ .italic {
197
+ font-style: italic;
198
+ }
199
+ .nowrap {
200
+ white-space: nowrap;
201
+ }
202
+ .wrap-anywhere {
203
+ min-width: 0;
204
+ overflow-wrap: anywhere;
205
+ }
206
+ .wrap-balance {
207
+ text-wrap: balance;
208
+ }
209
+ .uppercase {
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.04em;
212
+ }
213
+ .lh-tight {
214
+ line-height: var(--lh-tight);
215
+ }
216
+ .lh-normal {
217
+ line-height: var(--lh-normal);
218
+ }
219
+ .lh-none {
220
+ line-height: 1;
221
+ }
222
+ .grow {
223
+ flex: 1 1 0;
224
+ min-width: 0;
225
+ }
226
+ .block {
227
+ display: block;
228
+ }
132
229
  .numeric {
133
230
  font-variant-numeric: tabular-nums;
134
231
  font-feature-settings: 'tnum';
@@ -2,12 +2,21 @@ import type { Snippet } from 'svelte';
2
2
  type Size = 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | '2xl';
3
3
  type $$ComponentProps = {
4
4
  as?: 'span' | 'p' | 'div' | 'label';
5
- variant?: 'body' | 'label' | 'caption' | 'code';
6
- tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'success' | 'warn' | 'danger' | 'accent';
5
+ variant?: 'body' | 'label' | 'caption' | 'code' | 'eyebrow';
6
+ tone?: 'inherit' | 'default' | 'muted' | 'faint' | 'ok' | 'success' | 'warn' | 'danger' | 'accent';
7
7
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
8
8
  size?: Size;
9
9
  numeric?: boolean;
10
10
  truncate?: boolean;
11
+ italic?: boolean;
12
+ nowrap?: boolean;
13
+ wrap?: 'normal' | 'anywhere' | 'balance';
14
+ uppercase?: boolean;
15
+ leading?: 'tight' | 'normal' | 'none';
16
+ measure?: string;
17
+ grow?: boolean;
18
+ scale?: boolean;
19
+ block?: boolean;
11
20
  class?: string;
12
21
  children?: Snippet;
13
22
  [key: string]: unknown;
@@ -16,6 +16,7 @@
16
16
  disabled = false,
17
17
  variant = 'default',
18
18
  size = 'md',
19
+ control = false,
19
20
  box,
20
21
  hitArea = 'auto',
21
22
  class: klass = ''
@@ -38,6 +39,9 @@
38
39
  /** Match the Button atom's sizes so a FileButton lines up with buttons in
39
40
  * the same row. */
40
41
  size?: 'sm' | 'md' | 'lg';
42
+ /** Uniform `--control-height`, like Button's `control`, so it lines up in a
43
+ * composer/toolbar row. */
44
+ control?: boolean;
41
45
  /** Shared square box scale (`--box-xs/sm/md/lg`); implies `iconOnly`. */
42
46
  box?: 'xs' | 'sm' | 'md' | 'lg';
43
47
  /** Icon-only buttons grow a 44px hit slab on coarse pointers; `compact` opts out. */
@@ -62,6 +66,7 @@
62
66
  class:ghost={variant === 'ghost'}
63
67
  class:sm={size === 'sm'}
64
68
  class:lg={size === 'lg'}
69
+ class:control
65
70
  class:icon-only={onlyIcon}
66
71
  class:box={box !== undefined}
67
72
  class:hit-compact={hitArea === 'compact'}
@@ -125,6 +130,11 @@
125
130
  padding: var(--sp-3) var(--sp-5);
126
131
  font-size: var(--fs-base);
127
132
  }
133
+ .file-btn.control {
134
+ height: var(--control-height);
135
+ min-height: var(--control-height);
136
+ padding: 0 var(--sp-3);
137
+ }
128
138
  /* icon-only: square it up and drop the label gap. */
129
139
  .file-btn.icon-only {
130
140
  gap: 0;
@@ -18,6 +18,9 @@ type $$ComponentProps = {
18
18
  /** Match the Button atom's sizes so a FileButton lines up with buttons in
19
19
  * the same row. */
20
20
  size?: 'sm' | 'md' | 'lg';
21
+ /** Uniform `--control-height`, like Button's `control`, so it lines up in a
22
+ * composer/toolbar row. */
23
+ control?: boolean;
21
24
  /** Shared square box scale (`--box-xs/sm/md/lg`); implies `iconOnly`. */
22
25
  box?: 'xs' | 'sm' | 'md' | 'lg';
23
26
  /** Icon-only buttons grow a 44px hit slab on coarse pointers; `compact` opts out. */
@@ -16,6 +16,11 @@
16
16
  children?: Snippet;
17
17
  label: string;
18
18
  variant?: 'default' | 'primary' | 'ghost' | 'danger';
19
+ // Fully rounded (`--r-pill`) box. Forwarded to Button.
20
+ pill?: boolean;
21
+ // `shrink={false}` pins the box to its intrinsic size in a flex row.
22
+ // Forwarded to Button.
23
+ shrink?: boolean;
19
24
  // Semantic state tint (forwarded to Button). Pairs well with `chip`.
20
25
  tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
21
26
  // Outlined `--box-lg` square icon-chip (header/toolbar actions).
@@ -55,6 +60,8 @@
55
60
  variant = 'ghost',
56
61
  tone = 'none',
57
62
  chip = false,
63
+ pill = false,
64
+ shrink = true,
58
65
  box,
59
66
  size = 18,
60
67
  glyphSize,
@@ -82,6 +89,8 @@
82
89
  {variant}
83
90
  {tone}
84
91
  {chip}
92
+ {pill}
93
+ {shrink}
85
94
  {box}
86
95
  {hitArea}
87
96
  {disabled}
@@ -12,6 +12,8 @@ type IconButtonProps = HTMLButtonAttributes & {
12
12
  children?: Snippet;
13
13
  label: string;
14
14
  variant?: 'default' | 'primary' | 'ghost' | 'danger';
15
+ pill?: boolean;
16
+ shrink?: boolean;
15
17
  tone?: 'none' | 'accent' | 'info' | 'warn' | 'danger';
16
18
  chip?: boolean;
17
19
  box?: 'xs' | 'sm' | 'md' | 'lg';
@@ -14,6 +14,9 @@
14
14
  pressed = false,
15
15
  pill = false,
16
16
  struck = false,
17
+ size = 'sm',
18
+ grow = false,
19
+ shrink = true,
17
20
  type = 'button',
18
21
  disabled = false,
19
22
  class: klass = '',
@@ -23,6 +26,11 @@
23
26
  pressed?: boolean;
24
27
  pill?: boolean;
25
28
  struck?: boolean;
29
+ size?: 'sm' | 'md';
30
+ // Share a flex row with siblings (flex: 1 1 0).
31
+ grow?: boolean;
32
+ // `shrink={false}` keeps the chip at its intrinsic width (flex: none).
33
+ shrink?: boolean;
26
34
  children?: Snippet;
27
35
  } = $props();
28
36
  </script>
@@ -35,6 +43,9 @@
35
43
  class="toggle {klass}"
36
44
  class:pill
37
45
  class:struck
46
+ class:md={size === 'md'}
47
+ class:grow
48
+ class:no-shrink={!shrink}
38
49
  class:on={pressed}
39
50
  aria-pressed={pressed}
40
51
  >
@@ -69,6 +80,17 @@
69
80
  .toggle.pill {
70
81
  border-radius: var(--r-pill);
71
82
  }
83
+ .toggle.md {
84
+ padding: 0.3rem var(--sp-2);
85
+ font-size: var(--fs-sm);
86
+ }
87
+ .toggle.grow {
88
+ flex: 1 1 0;
89
+ min-width: 0;
90
+ }
91
+ .toggle.no-shrink {
92
+ flex: none;
93
+ }
72
94
  .toggle:hover:not(:disabled) {
73
95
  border-color: var(--border-strong);
74
96
  }
@@ -4,6 +4,9 @@ type $$ComponentProps = HTMLButtonAttributes & {
4
4
  pressed?: boolean;
5
5
  pill?: boolean;
6
6
  struck?: boolean;
7
+ size?: 'sm' | 'md';
8
+ grow?: boolean;
9
+ shrink?: boolean;
7
10
  children?: Snippet;
8
11
  };
9
12
  declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "">;
package/dist/index.d.ts CHANGED
@@ -69,4 +69,5 @@ export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.sve
69
69
  export { type Mode, THEMES, theme } from './stores/theme.svelte';
70
70
  export { type Toast, type ToastAction, type ToastOptions, type ToastTone, type ToastToneInput, toasts, } from './stores/toast.svelte';
71
71
  export { formatTimestamp, localTimeZone, relativeTime, type TimeInput, type TimestampMode, } from './timestamp';
72
+ export { canonicalTone, type Tone } from './tone';
72
73
  export { type TruncateMode, type TruncateOptions, truncate } from './truncate';
package/dist/index.js CHANGED
@@ -82,4 +82,5 @@ export { fontScale, SCALE_LEVELS } from './stores/fontscale.svelte';
82
82
  export { THEMES, theme } from './stores/theme.svelte';
83
83
  export { toasts, } from './stores/toast.svelte';
84
84
  export { formatTimestamp, localTimeZone, relativeTime, } from './timestamp';
85
+ export { canonicalTone } from './tone';
85
86
  export { truncate } from './truncate';
package/dist/tone.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type Tone = 'neutral' | 'ok' | 'success' | 'warn' | 'danger' | 'info' | 'accent';
2
+ export declare function canonicalTone(t: string): string;
package/dist/tone.js ADDED
@@ -0,0 +1,3 @@
1
+ export function canonicalTone(t) {
2
+ return t === 'success' ? 'ok' : t;
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",