@dorsk/tsumikit 0.2.8 → 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.
@@ -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;
@@ -5,6 +5,8 @@ type $$ComponentProps = {
5
5
  as?: 'span' | 'button';
6
6
  size?: 'sm' | 'md';
7
7
  mono?: boolean;
8
+ uppercase?: boolean;
9
+ active?: boolean;
8
10
  removable?: boolean;
9
11
  onremove?: (e: MouseEvent) => void;
10
12
  class?: string;
@@ -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;
@@ -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;
@@ -2,6 +2,7 @@ type $$ComponentProps = {
2
2
  value?: number;
3
3
  max?: number;
4
4
  label?: string;
5
+ tone?: 'accent' | 'success' | 'warn' | 'danger';
5
6
  class?: string;
6
7
  };
7
8
  declare const Progress: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -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 class="select" bind:value {...rest} aria-invalid={invalid || undefined}>
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
- <span class="select-chevron" aria-hidden="true">
47
- <Icon name="chevron-down" size={16} />
48
- </span>
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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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",