@dorsk/tsumikit 0.38.1 → 0.39.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.
Files changed (143) hide show
  1. package/dist/autoresize.d.ts +2 -0
  2. package/dist/autoresize.js +15 -1
  3. package/dist/components/atoms/Artwork.svelte +19 -18
  4. package/dist/components/atoms/Artwork.svelte.d.ts +3 -2
  5. package/dist/components/atoms/Badge.svelte +60 -26
  6. package/dist/components/atoms/Badge.svelte.d.ts +14 -3
  7. package/dist/components/atoms/Button.svelte +30 -12
  8. package/dist/components/atoms/Button.svelte.d.ts +4 -2
  9. package/dist/components/atoms/Card.svelte +47 -30
  10. package/dist/components/atoms/Card.svelte.d.ts +12 -3
  11. package/dist/components/atoms/Checkbox.svelte +16 -2
  12. package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
  13. package/dist/components/atoms/Dot.svelte +17 -11
  14. package/dist/components/atoms/Dot.svelte.d.ts +5 -2
  15. package/dist/components/atoms/Gauge.svelte +19 -18
  16. package/dist/components/atoms/Gauge.svelte.d.ts +3 -2
  17. package/dist/components/atoms/Heading.svelte +20 -19
  18. package/dist/components/atoms/Heading.svelte.d.ts +3 -2
  19. package/dist/components/atoms/Icon.svelte +33 -13
  20. package/dist/components/atoms/Icon.svelte.d.ts +17 -2
  21. package/dist/components/atoms/Input.svelte +17 -3
  22. package/dist/components/atoms/Input.svelte.d.ts +2 -1
  23. package/dist/components/atoms/Kbd.svelte +25 -3
  24. package/dist/components/atoms/Kbd.svelte.d.ts +5 -1
  25. package/dist/components/atoms/Link.svelte +16 -12
  26. package/dist/components/atoms/Link.svelte.d.ts +6 -2
  27. package/dist/components/atoms/Progress.svelte +119 -4
  28. package/dist/components/atoms/Progress.svelte.d.ts +28 -2
  29. package/dist/components/atoms/Scrim.svelte +7 -2
  30. package/dist/components/atoms/Scrim.svelte.d.ts +2 -0
  31. package/dist/components/atoms/SegmentedProgress.svelte +14 -2
  32. package/dist/components/atoms/SegmentedProgress.svelte.d.ts +5 -2
  33. package/dist/components/atoms/Select.svelte +18 -6
  34. package/dist/components/atoms/Select.svelte.d.ts +3 -3
  35. package/dist/components/atoms/Skeleton.svelte +12 -11
  36. package/dist/components/atoms/Skeleton.svelte.d.ts +3 -2
  37. package/dist/components/atoms/Slider.svelte +45 -3
  38. package/dist/components/atoms/Slider.svelte.d.ts +7 -0
  39. package/dist/components/atoms/Spinner.svelte +2 -0
  40. package/dist/components/atoms/Spinner.svelte.d.ts +2 -0
  41. package/dist/components/atoms/Switch.svelte +70 -18
  42. package/dist/components/atoms/Switch.svelte.d.ts +5 -1
  43. package/dist/components/atoms/Text.svelte +39 -26
  44. package/dist/components/atoms/Text.svelte.d.ts +7 -3
  45. package/dist/components/atoms/Textarea.svelte +47 -10
  46. package/dist/components/atoms/Textarea.svelte.d.ts +10 -3
  47. package/dist/components/layouts/AppShell.svelte +7 -3
  48. package/dist/components/layouts/AppShell.svelte.d.ts +2 -0
  49. package/dist/components/layouts/AutoGrid.svelte +4 -0
  50. package/dist/components/layouts/AutoGrid.svelte.d.ts +2 -0
  51. package/dist/components/layouts/Cluster.svelte +62 -1
  52. package/dist/components/layouts/Cluster.svelte.d.ts +9 -0
  53. package/dist/components/layouts/NavItem.svelte +22 -1
  54. package/dist/components/layouts/NavItem.svelte.d.ts +5 -0
  55. package/dist/components/layouts/NavSection.svelte +4 -1
  56. package/dist/components/layouts/NavSection.svelte.d.ts +2 -0
  57. package/dist/components/layouts/ResizablePanel.svelte +14 -3
  58. package/dist/components/layouts/ResizablePanel.svelte.d.ts +5 -0
  59. package/dist/components/layouts/Stack.svelte +41 -0
  60. package/dist/components/layouts/Stack.svelte.d.ts +9 -0
  61. package/dist/components/layouts/Toolbar.svelte +122 -0
  62. package/dist/components/layouts/Toolbar.svelte.d.ts +22 -0
  63. package/dist/components/molecules/AttachmentList.svelte +59 -0
  64. package/dist/components/molecules/AttachmentList.svelte.d.ts +13 -0
  65. package/dist/components/molecules/Breadcrumb.svelte +6 -2
  66. package/dist/components/molecules/Breadcrumb.svelte.d.ts +2 -0
  67. package/dist/components/molecules/Callout.svelte +10 -4
  68. package/dist/components/molecules/Callout.svelte.d.ts +1 -1
  69. package/dist/components/molecules/CapBar.svelte +5 -1
  70. package/dist/components/molecules/ChatBubble.svelte +158 -0
  71. package/dist/components/molecules/ChatBubble.svelte.d.ts +26 -0
  72. package/dist/components/molecules/CodeBlock.svelte +36 -3
  73. package/dist/components/molecules/CodeBlock.svelte.d.ts +8 -0
  74. package/dist/components/molecules/Composer.svelte +195 -0
  75. package/dist/components/molecules/Composer.svelte.d.ts +27 -0
  76. package/dist/components/molecules/ConfirmModal.svelte +15 -5
  77. package/dist/components/molecules/ConfirmModal.svelte.d.ts +5 -1
  78. package/dist/components/molecules/Dropzone.svelte +9 -3
  79. package/dist/components/molecules/Dropzone.svelte.d.ts +2 -0
  80. package/dist/components/molecules/EmptyState.svelte +9 -5
  81. package/dist/components/molecules/EmptyState.svelte.d.ts +3 -1
  82. package/dist/components/molecules/Field.svelte +7 -1
  83. package/dist/components/molecules/Field.svelte.d.ts +2 -0
  84. package/dist/components/molecules/FileButton.svelte +2 -1
  85. package/dist/components/molecules/FileButton.svelte.d.ts +2 -1
  86. package/dist/components/molecules/FilterInput.svelte +13 -3
  87. package/dist/components/molecules/FilterInput.svelte.d.ts +4 -1
  88. package/dist/components/molecules/IconButton.svelte +32 -7
  89. package/dist/components/molecules/IconButton.svelte.d.ts +9 -1
  90. package/dist/components/molecules/KeyValue.svelte +4 -1
  91. package/dist/components/molecules/KeyValue.svelte.d.ts +2 -1
  92. package/dist/components/molecules/Menu.svelte +50 -5
  93. package/dist/components/molecules/Menu.svelte.d.ts +10 -1
  94. package/dist/components/molecules/Meter.svelte +91 -0
  95. package/dist/components/molecules/Meter.svelte.d.ts +29 -0
  96. package/dist/components/molecules/Metric.svelte +134 -8
  97. package/dist/components/molecules/Metric.svelte.d.ts +20 -2
  98. package/dist/components/molecules/Modal.svelte +26 -12
  99. package/dist/components/molecules/Modal.svelte.d.ts +7 -1
  100. package/dist/components/molecules/Pagination.svelte +25 -1
  101. package/dist/components/molecules/Pagination.svelte.d.ts +9 -2
  102. package/dist/components/molecules/Popover.svelte +14 -3
  103. package/dist/components/molecules/Popover.svelte.d.ts +5 -0
  104. package/dist/components/molecules/Prose.svelte +140 -0
  105. package/dist/components/molecules/Prose.svelte.d.ts +13 -0
  106. package/dist/components/molecules/RadioGroup.svelte +42 -2
  107. package/dist/components/molecules/RadioGroup.svelte.d.ts +4 -1
  108. package/dist/components/molecules/SectionHeader.svelte +5 -2
  109. package/dist/components/molecules/SectionHeader.svelte.d.ts +1 -1
  110. package/dist/components/molecules/SegmentedControl.svelte +36 -1
  111. package/dist/components/molecules/SegmentedControl.svelte.d.ts +11 -1
  112. package/dist/components/molecules/SelectButton.svelte +64 -4
  113. package/dist/components/molecules/SelectButton.svelte.d.ts +18 -1
  114. package/dist/components/molecules/Tabs.svelte +21 -3
  115. package/dist/components/molecules/Tabs.svelte.d.ts +6 -0
  116. package/dist/components/molecules/Timestamp.svelte +13 -6
  117. package/dist/components/molecules/Timestamp.svelte.d.ts +4 -0
  118. package/dist/components/molecules/Toaster.svelte +54 -38
  119. package/dist/components/molecules/Toaster.svelte.d.ts +5 -1
  120. package/dist/components/molecules/Toggle.svelte +2 -1
  121. package/dist/components/molecules/Toggle.svelte.d.ts +2 -1
  122. package/dist/components/molecules/Tooltip.svelte +32 -5
  123. package/dist/components/molecules/Tooltip.svelte.d.ts +9 -0
  124. package/dist/components/molecules/Truncate.svelte +156 -18
  125. package/dist/components/molecules/Truncate.svelte.d.ts +17 -1
  126. package/dist/components/molecules/WorkingDir.svelte +6 -0
  127. package/dist/components/molecules/WorkingDir.svelte.d.ts +2 -0
  128. package/dist/components/organisms/DataTable.svelte +38 -10
  129. package/dist/components/organisms/DataTable.svelte.d.ts +4 -0
  130. package/dist/components/organisms/FilterSearchBar.svelte +9 -2
  131. package/dist/components/organisms/FilterSearchBar.svelte.d.ts +4 -1
  132. package/dist/index.d.ts +10 -2
  133. package/dist/index.js +8 -1
  134. package/dist/size.d.ts +2 -0
  135. package/dist/size.js +1 -0
  136. package/dist/styles/tokens.css +3 -0
  137. package/dist/timestamp.d.ts +2 -2
  138. package/dist/timestamp.js +4 -4
  139. package/dist/tone.d.ts +1 -1
  140. package/dist/tone.js +1 -1
  141. package/dist/truncate.d.ts +13 -1
  142. package/dist/truncate.js +39 -0
  143. package/package.json +5 -3
@@ -5,6 +5,8 @@
5
5
  *
6
6
  * Usage: <textarea use:autoresize={value} ...></textarea>
7
7
  */
8
+ /** Height `rows` lines would occupy: line boxes plus vertical padding and borders. */
9
+ export declare function rowsFloor(node: HTMLTextAreaElement): number;
8
10
  export declare function autoresize(node: HTMLTextAreaElement, _value?: string): {
9
11
  update(): void;
10
12
  destroy(): void;
@@ -5,12 +5,26 @@
5
5
  *
6
6
  * Usage: <textarea use:autoresize={value} ...></textarea>
7
7
  */
8
+ /** Height `rows` lines would occupy: line boxes plus vertical padding and borders. */
9
+ export function rowsFloor(node) {
10
+ const rows = node.rows || 0;
11
+ if (rows <= 1)
12
+ return 0;
13
+ const cs = getComputedStyle(node);
14
+ const fontSize = parseFloat(cs.fontSize) || 16;
15
+ const lh = parseFloat(cs.lineHeight) || fontSize * 1.2;
16
+ const box = (parseFloat(cs.paddingTop) || 0) +
17
+ (parseFloat(cs.paddingBottom) || 0) +
18
+ (parseFloat(cs.borderTopWidth) || 0) +
19
+ (parseFloat(cs.borderBottomWidth) || 0);
20
+ return rows * lh + box;
21
+ }
8
22
  export function autoresize(node, _value) {
9
23
  const resize = () => {
10
24
  // A manual drag handle may set `min-height` as a user-chosen floor; grow
11
25
  // with content but never collapse below it. Content always wins the lower
12
26
  // bound, so dragging shorter than the text can't shrink past it.
13
- const floor = parseFloat(node.style.minHeight) || 0;
27
+ const floor = Math.max(parseFloat(node.style.minHeight) || 0, rowsFloor(node));
14
28
  node.style.height = 'auto';
15
29
  node.style.height = `${Math.max(node.scrollHeight, floor)}px`;
16
30
  };
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
2
3
  // Cover / thumbnail tile: a lazy <img> that swaps to a seeded gradient with
3
4
  // initials (or an icon) when there is no source or it fails to load. Width
4
5
  // comes from `size`, height from `aspect`; `status` overlays a Dot/Badge in
@@ -8,22 +9,7 @@
8
9
  import Icon from './Icon.svelte';
9
10
  import type { IconName } from './Icon.svelte';
10
11
 
11
- let {
12
- src,
13
- alt,
14
- seed,
15
- aspect = '1/1',
16
- size,
17
- radius = 'md',
18
- fit = 'cover',
19
- fallback = 'initials',
20
- icon = 'image',
21
- status,
22
- hover = false,
23
- onerror,
24
- class: klass = '',
25
- ...rest
26
- }: {
12
+ type Own = {
27
13
  src?: string | null;
28
14
  alt: string;
29
15
  /** Drives the gradient hue and the initials. Defaults to `alt`. */
@@ -43,8 +29,23 @@
43
29
  hover?: boolean;
44
30
  onerror?: () => void;
45
31
  class?: string;
46
- [key: string]: unknown;
47
- } = $props();
32
+ };
33
+ let {
34
+ src,
35
+ alt,
36
+ seed,
37
+ aspect = '1/1',
38
+ size,
39
+ radius = 'md',
40
+ fit = 'cover',
41
+ fallback = 'initials',
42
+ icon = 'image',
43
+ status,
44
+ hover = false,
45
+ onerror,
46
+ class: klass = '',
47
+ ...rest
48
+ }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
48
49
 
49
50
  let failed = $state(false);
50
51
  $effect(() => {
@@ -1,6 +1,7 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { IconName } from './Icon.svelte';
3
- type $$ComponentProps = {
4
+ type Own = {
4
5
  src?: string | null;
5
6
  alt: string;
6
7
  /** Drives the gradient hue and the initials. Defaults to `alt`. */
@@ -20,8 +21,8 @@ type $$ComponentProps = {
20
21
  hover?: boolean;
21
22
  onerror?: () => void;
22
23
  class?: string;
23
- [key: string]: unknown;
24
24
  };
25
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
25
26
  declare const Artwork: import("svelte").Component<$$ComponentProps, {}, "">;
26
27
  type Artwork = ReturnType<typeof Artwork>;
27
28
  export default Artwork;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
2
3
  // Inline label primitive — the project's single pill. Covers three jobs via
3
4
  // props rather than separate components:
4
5
  // • state → `tone` semantic palette, or `color` for any CSS colour
@@ -23,31 +24,21 @@
23
24
  violet: 'var(--c-violet)',
24
25
  };
25
26
 
26
- let {
27
- tone = 'neutral',
28
- color,
29
- as = 'span',
30
- size = 'md',
31
- variant = 'chip',
32
- mono = false,
33
- uppercase = false,
34
- numeric = false,
35
- truncate = false,
36
- maxWidth,
37
- border = true,
38
- active = false,
39
- dot = false,
40
- icon,
41
- removable = false,
42
- onremove,
43
- class: klass = '',
44
- children,
45
- ...rest
46
- }: {
27
+ type Own = {
47
28
  tone?: Tone;
29
+ /** Fill the free space of a flex row (`flex: 1 1 0`). */
30
+ grow?: boolean;
31
+ /** `false` pins the box (`flex: none`) so a flex row cannot squeeze it. */
32
+ shrink?: boolean;
33
+ /** Full-width block. */
34
+ block?: boolean;
48
35
  // Any CSS colour (or var()) for a one-off tint; overrides `tone`.
49
36
  color?: string;
50
- as?: 'span' | 'button';
37
+ as?: 'span' | 'button' | 'a';
38
+ /** Renders an anchor (implies `as="a"`). */
39
+ href?: string;
40
+ /** Opens in a new tab with a trailing arrow glyph. */
41
+ external?: boolean;
51
42
  // `xs` is the densest form for counters in tight rows.
52
43
  size?: 'xs' | 'sm' | 'md';
53
44
  // `text` drops the chip (no fill, ring or padding) but keeps badge
@@ -74,16 +65,47 @@
74
65
  onremove?: (e: MouseEvent) => void;
75
66
  class?: string;
76
67
  children?: Snippet;
77
- [key: string]: unknown;
78
- } = $props();
68
+ };
69
+ let {
70
+ tone = 'neutral',
71
+ color,
72
+ as = 'span',
73
+ href,
74
+ external = false,
75
+ size = 'md',
76
+ variant = 'chip',
77
+ mono = false,
78
+ uppercase = false,
79
+ numeric = false,
80
+ truncate = false,
81
+ maxWidth,
82
+ border = true,
83
+ active = false,
84
+ dot = false,
85
+ icon,
86
+ removable = false,
87
+ onremove,
88
+ class: klass = '',
89
+ grow = false,
90
+ shrink = true,
91
+ block = false,
92
+ children,
93
+ ...rest
94
+ }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
79
95
 
80
96
  const toneColor = $derived(color ?? (tone === 'neutral' ? undefined : TONE_COLOR[tone]));
81
97
  </script>
82
98
 
83
99
  <svelte:element
84
- this={as}
100
+ this={href ? 'a' : as}
101
+ {href}
102
+ target={href && external ? '_blank' : undefined}
103
+ rel={href && external ? 'noopener noreferrer' : undefined}
85
104
  data-tsu="Badge"
86
105
  class="badge {klass}"
106
+ class:grow={grow}
107
+ class:no-shrink={!shrink}
108
+ class:block={block}
87
109
  class:badge-ok={tone === 'ok'}
88
110
  class:badge-warn={tone === 'warn'}
89
111
  class:badge-danger={tone === 'danger'}
@@ -98,7 +120,7 @@
98
120
  class:truncate
99
121
  class:borderless={!border}
100
122
  class:active
101
- class:interactive={as === 'button'}
123
+ class:interactive={as === 'button' || !!href}
102
124
  style:--badge-tone={toneColor}
103
125
  style:--badge-max-width={maxWidth}
104
126
  {...rest}
@@ -114,6 +136,7 @@
114
136
  {:else}
115
137
  {@render children?.()}
116
138
  {/if}
139
+ {#if href && external}<Icon name="external" />{/if}
117
140
  {#if removable}
118
141
  <button
119
142
  type="button"
@@ -127,6 +150,17 @@
127
150
  </svelte:element>
128
151
 
129
152
  <style>
153
+ .grow {
154
+ flex: 1 1 0;
155
+ min-width: 0;
156
+ }
157
+ .no-shrink {
158
+ flex: none;
159
+ }
160
+ .block {
161
+ display: flex;
162
+ width: 100%;
163
+ }
130
164
  .badge {
131
165
  display: inline-flex;
132
166
  align-items: center;
@@ -1,10 +1,21 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
1
2
  import type { Snippet } from 'svelte';
2
3
  import { type IconName } from './Icon.svelte';
3
4
  type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info' | 'accent' | 'muted' | 'violet';
4
- type $$ComponentProps = {
5
+ type Own = {
5
6
  tone?: Tone;
7
+ /** Fill the free space of a flex row (`flex: 1 1 0`). */
8
+ grow?: boolean;
9
+ /** `false` pins the box (`flex: none`) so a flex row cannot squeeze it. */
10
+ shrink?: boolean;
11
+ /** Full-width block. */
12
+ block?: boolean;
6
13
  color?: string;
7
- as?: 'span' | 'button';
14
+ as?: 'span' | 'button' | 'a';
15
+ /** Renders an anchor (implies `as="a"`). */
16
+ href?: string;
17
+ /** Opens in a new tab with a trailing arrow glyph. */
18
+ external?: boolean;
8
19
  size?: 'xs' | 'sm' | 'md';
9
20
  variant?: 'chip' | 'text';
10
21
  mono?: boolean;
@@ -20,8 +31,8 @@ type $$ComponentProps = {
20
31
  onremove?: (e: MouseEvent) => void;
21
32
  class?: string;
22
33
  children?: Snippet;
23
- [key: string]: unknown;
24
34
  };
35
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
25
36
  declare const Badge: import("svelte").Component<$$ComponentProps, {}, "">;
26
37
  type Badge = ReturnType<typeof Badge>;
27
38
  export default Badge;
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import type { ControlSize } from '../../size';
3
+ import { canonicalTone, type Tone } from '../../tone';
2
4
  import type { Snippet } from 'svelte';
3
5
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
6
 
@@ -9,8 +11,8 @@
9
11
  // Semantic state tint layered on top of the variant: tints text/border and
10
12
  // adds a subtle fill on hover. For stateful controls — an "on"/active toggle
11
13
  // (`accent`), positive actions (`success`), or cost/severity states.
12
- tone?: 'none' | 'accent' | 'success' | 'info' | 'warn' | 'danger';
13
- size?: 'sm' | 'md' | 'lg';
14
+ tone?: Tone | 'none';
15
+ size?: ControlSize;
14
16
  control?: boolean;
15
17
  // Fully rounded ends (`--r-pill`) on any size/variant.
16
18
  pill?: boolean;
@@ -39,8 +41,8 @@
39
41
  // Square text/icon control whose side equals the current height contract:
40
42
  // `size` tier, or `--control-height` with `control`.
41
43
  square?: boolean;
42
- // Outlined `--box-lg` icon-chip (header/toolbar action). Pairs with `tone`
43
- // for tinted severity chips (back/archive/interrupt/more).
44
+ // Outlined `--box-lg`-tall chip (header/toolbar action): a square for a lone
45
+ // glyph, otherwise sized to its text. Pairs with `tone` for tinted severity chips.
44
46
  chip?: boolean;
45
47
  iconInline?: boolean;
46
48
  hoverDanger?: boolean;
@@ -86,6 +88,7 @@
86
88
  children,
87
89
  ...rest
88
90
  }: ButtonProps = $props();
91
+ const t = $derived(canonicalTone(tone));
89
92
 
90
93
  // A link-flavoured Button is a real <a>; native `disabled`/`type` don't apply,
91
94
  // so a disabled link is expressed via aria-disabled + inert pointer handling.
@@ -121,7 +124,7 @@
121
124
  class:btn-grow={grow}
122
125
  class:btn-primary={variant === 'primary'}
123
126
  class:btn-ghost={variant === 'ghost'}
124
- class:btn-danger={variant === 'danger' || (tone === 'danger' && variant === 'default')}
127
+ class:btn-danger={variant === 'danger' || (t === 'danger' && variant === 'default')}
125
128
  class:btn-link={variant === 'link'}
126
129
  class:btn-pill={pill}
127
130
  class:no-shrink={!shrink}
@@ -137,11 +140,11 @@
137
140
  class:btn-collapse-container={collapseLabel === 'container'}
138
141
  class:hit-compact={hitArea === 'compact'}
139
142
  style:--btn-box={box ? `var(--box-${box})` : undefined}
140
- class:btn-tone-accent={tone === 'accent'}
141
- class:btn-tone-success={tone === 'success'}
142
- class:btn-tone-info={tone === 'info'}
143
- class:btn-tone-warn={tone === 'warn'}
144
- class:btn-tone-danger={tone === 'danger'}
143
+ class:btn-tone-accent={t === 'accent'}
144
+ class:btn-tone-success={t === 'ok'}
145
+ class:btn-tone-info={t === 'info'}
146
+ class:btn-tone-warn={t === 'warn'}
147
+ class:btn-tone-danger={t === 'danger'}
145
148
  class:btn-icon-inline={iconInline}
146
149
  class:hover-danger={hoverDanger}
147
150
  class:loading
@@ -277,11 +280,16 @@
277
280
  min-height: var(--box-lg);
278
281
  min-width: var(--box-lg);
279
282
  height: var(--box-lg);
280
- width: var(--box-lg);
281
- padding: 0;
283
+ width: auto;
284
+ padding: 0 var(--sp-2);
285
+ gap: var(--sp-1);
282
286
  flex: none;
283
287
  border-radius: var(--r-md);
284
288
  }
289
+ .btn-chip:has(> :global(svg):only-child) {
290
+ width: var(--box-lg);
291
+ padding: 0;
292
+ }
285
293
 
286
294
  /* Uniform-height control: icon buttons, inputs and action buttons that share a
287
295
  toolbar/composer row line up exactly. */
@@ -334,6 +342,16 @@
334
342
  background: color-mix(in srgb, var(--ok) 14%, transparent);
335
343
  border-color: var(--ok);
336
344
  }
345
+ .btn-control.btn-danger,
346
+ .btn-control.btn-tone-danger {
347
+ color: var(--danger);
348
+ border-color: color-mix(in srgb, var(--danger) 50%, var(--border));
349
+ }
350
+ .btn-control.btn-danger:hover:not(:disabled),
351
+ .btn-control.btn-tone-danger:hover:not(:disabled) {
352
+ background: color-mix(in srgb, var(--danger) 14%, transparent);
353
+ border-color: var(--danger);
354
+ }
337
355
  /* A positive primary action uses success as its fill, not merely as the
338
356
  neutral tint. This follows the control override so both heights match. */
339
357
  .btn-primary.btn-tone-success {
@@ -1,9 +1,11 @@
1
+ import type { ControlSize } from '../../size';
2
+ import { type Tone } from '../../tone';
1
3
  import type { Snippet } from 'svelte';
2
4
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
5
  type ButtonProps = HTMLButtonAttributes & {
4
6
  variant?: 'default' | 'primary' | 'ghost' | 'danger' | 'link';
5
- tone?: 'none' | 'accent' | 'success' | 'info' | 'warn' | 'danger';
6
- size?: 'sm' | 'md' | 'lg';
7
+ tone?: Tone | 'none';
8
+ size?: ControlSize;
7
9
  control?: boolean;
8
10
  pill?: boolean;
9
11
  block?: boolean;
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { canonicalTone, type Tone as SharedTone } from '../../tone';
2
4
  // Elevated surface primitive — the canonical card/panel container. Owns its
3
5
  // background/border/radius/padding from theme tokens. `tap` adds the
4
6
  // interactive hover/active affordance for tappable list items (e.g. session
@@ -25,8 +27,38 @@
25
27
  import type { Snippet } from 'svelte';
26
28
  import SectionHeader from '../molecules/SectionHeader.svelte';
27
29
 
28
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info' | 'attention';
30
+ type Tone = SharedTone | 'attention';
29
31
 
32
+ type Own = {
33
+ /** Polymorphic attributes for `as="a"` / `as="button"`. */
34
+ href?: string;
35
+ target?: string;
36
+ rel?: string;
37
+ type?: 'button' | 'submit' | 'reset';
38
+ tap?: boolean;
39
+ interactive?: boolean;
40
+ as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
41
+ padding?: 'none' | 'sm' | 'md' | 'lg';
42
+ surface?: 'base' | 'raised' | 'sunken';
43
+ tone?: Tone;
44
+ stacked?: boolean;
45
+ stackTone?: Tone;
46
+ stackY?: number;
47
+ stackX?: number;
48
+ title?: string;
49
+ subtitle?: string;
50
+ gap?: string;
51
+ /** `hidden` clips children to the rounded box. */
52
+ overflow?: 'visible' | 'hidden';
53
+ maxWidth?: string;
54
+ onclick?: (e: MouseEvent | KeyboardEvent) => void;
55
+ class?: string;
56
+ style?: string;
57
+ header?: Snippet;
58
+ footer?: Snippet;
59
+ actions?: Snippet;
60
+ children?: Snippet;
61
+ };
30
62
  let {
31
63
  tap = false,
32
64
  interactive = false,
@@ -41,6 +73,7 @@
41
73
  title,
42
74
  subtitle,
43
75
  gap,
76
+ overflow,
44
77
  maxWidth,
45
78
  onclick,
46
79
  class: klass = '',
@@ -50,30 +83,8 @@
50
83
  actions,
51
84
  children,
52
85
  ...rest
53
- }: {
54
- tap?: boolean;
55
- interactive?: boolean;
56
- as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
57
- padding?: 'none' | 'sm' | 'md' | 'lg';
58
- surface?: 'base' | 'raised' | 'sunken';
59
- tone?: Tone;
60
- stacked?: boolean;
61
- stackTone?: Tone;
62
- stackY?: number;
63
- stackX?: number;
64
- title?: string;
65
- subtitle?: string;
66
- gap?: string;
67
- maxWidth?: string;
68
- onclick?: (e: MouseEvent | KeyboardEvent) => void;
69
- class?: string;
70
- style?: string;
71
- header?: Snippet;
72
- footer?: Snippet;
73
- actions?: Snippet;
74
- children?: Snippet;
75
- [key: string]: unknown;
76
- } = $props();
86
+ }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
87
+ const t = $derived(canonicalTone(tone));
77
88
 
78
89
  let stackStyle = $derived(
79
90
  stacked ? `--stack-y:${stackY}px;--stack-x:${stackX}px;` : ''
@@ -112,11 +123,12 @@
112
123
  class:surface-sunken={surface === 'sunken'}
113
124
  class:card-tap={tap || interactive}
114
125
  class:card-max={maxWidth !== undefined}
115
- class:card-ok={tone === 'ok'}
116
- class:card-warn={tone === 'warn'}
117
- class:card-danger={tone === 'danger'}
118
- class:card-info={tone === 'info'}
119
- class:card-attention={tone === 'attention'}
126
+ class:card-ok={t === 'ok'}
127
+ class:card-warn={t === 'warn'}
128
+ class:card-danger={t === 'danger'}
129
+ class:card-info={t === 'info'}
130
+ class:card-attention={t === 'attention'}
131
+ class:card-accent={t === 'accent'}
120
132
  class:card-stacked={stacked}
121
133
  class:stack-ok={stacked && stackTone === 'ok'}
122
134
  class:stack-warn={stacked && stackTone === 'warn'}
@@ -126,6 +138,7 @@
126
138
  class:card-gap={!framed && gap !== undefined}
127
139
  style:--card-gap={gap}
128
140
  style:max-width={maxWidth}
141
+ style:overflow={overflow}
129
142
  style={`${stackStyle}${style}`}
130
143
  role={interactive && !native ? 'button' : undefined}
131
144
  tabindex={interactive && !native ? 0 : undefined}
@@ -153,6 +166,7 @@
153
166
  <style>
154
167
  .card {
155
168
  --card-pad: var(--sp-4);
169
+ min-width: 0;
156
170
  background: var(--bg-elevated);
157
171
  border: 1px solid var(--border);
158
172
  border-radius: var(--r-lg);
@@ -234,6 +248,9 @@
234
248
  .card-attention {
235
249
  --card-tone: var(--attention-bar);
236
250
  }
251
+ .card-accent {
252
+ --card-tone: var(--accent);
253
+ }
237
254
  .card-ok,
238
255
  .card-warn,
239
256
  .card-danger,
@@ -1,6 +1,13 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import { type Tone as SharedTone } from '../../tone';
1
3
  import type { Snippet } from 'svelte';
2
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info' | 'attention';
3
- type $$ComponentProps = {
4
+ type Tone = SharedTone | 'attention';
5
+ type Own = {
6
+ /** Polymorphic attributes for `as="a"` / `as="button"`. */
7
+ href?: string;
8
+ target?: string;
9
+ rel?: string;
10
+ type?: 'button' | 'submit' | 'reset';
4
11
  tap?: boolean;
5
12
  interactive?: boolean;
6
13
  as?: 'div' | 'button' | 'a' | 'li' | 'section' | 'form';
@@ -14,6 +21,8 @@ type $$ComponentProps = {
14
21
  title?: string;
15
22
  subtitle?: string;
16
23
  gap?: string;
24
+ /** `hidden` clips children to the rounded box. */
25
+ overflow?: 'visible' | 'hidden';
17
26
  maxWidth?: string;
18
27
  onclick?: (e: MouseEvent | KeyboardEvent) => void;
19
28
  class?: string;
@@ -22,8 +31,8 @@ type $$ComponentProps = {
22
31
  footer?: Snippet;
23
32
  actions?: Snippet;
24
33
  children?: Snippet;
25
- [key: string]: unknown;
26
34
  };
35
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
27
36
  declare const Card: import("svelte").Component<$$ComponentProps, {}, "">;
28
37
  type Card = ReturnType<typeof Card>;
29
38
  export default Card;
@@ -11,6 +11,7 @@
11
11
  indeterminate = false,
12
12
  invalid = false,
13
13
  label,
14
+ labelHidden = false,
14
15
  id,
15
16
  'aria-describedby': ariaDescribedby,
16
17
  'aria-invalid': ariaInvalid,
@@ -23,6 +24,8 @@
23
24
  /** Error state: danger box border + aria-invalid. */
24
25
  invalid?: boolean;
25
26
  label: string;
27
+ /** Keep `label` for AT only (sr-only). */
28
+ labelHidden?: boolean;
26
29
  id?: string;
27
30
  'aria-describedby'?: string | null;
28
31
  'aria-invalid'?: HTMLInputAttributes['aria-invalid'];
@@ -49,7 +52,7 @@
49
52
  aria-invalid={ariaInvalid ?? (isInvalid ? 'true' : undefined)}
50
53
  />
51
54
  <span class="box" aria-hidden="true"></span>
52
- <span class="label-text">{label}</span>
55
+ <span class="label-text" class:sr-only={labelHidden}>{label}</span>
53
56
  </label>
54
57
 
55
58
  <style>
@@ -108,7 +111,7 @@
108
111
  clip-path: polygon(22% 42%, 78% 42%, 78% 58%, 22% 58%);
109
112
  }
110
113
  input:focus-visible + .box {
111
- outline: 2px solid var(--accent);
114
+ outline: var(--focus-ring);
112
115
  outline-offset: 2px;
113
116
  }
114
117
  input[aria-invalid='true']:not(:checked):not(:indeterminate) + .box {
@@ -120,4 +123,15 @@
120
123
  .checkbox:has(input:disabled) {
121
124
  cursor: not-allowed;
122
125
  }
126
+ .sr-only {
127
+ position: absolute;
128
+ width: 1px;
129
+ height: 1px;
130
+ padding: 0;
131
+ margin: -1px;
132
+ overflow: hidden;
133
+ clip: rect(0, 0, 0, 0);
134
+ white-space: nowrap;
135
+ border: 0;
136
+ }
123
137
  </style>
@@ -5,6 +5,8 @@ type $$ComponentProps = HTMLInputAttributes & {
5
5
  /** Error state: danger box border + aria-invalid. */
6
6
  invalid?: boolean;
7
7
  label: string;
8
+ /** Keep `label` for AT only (sr-only). */
9
+ labelHidden?: boolean;
8
10
  id?: string;
9
11
  'aria-describedby'?: string | null;
10
12
  'aria-invalid'?: HTMLInputAttributes['aria-invalid'];
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
2
3
  // Status dot — a small coloured disc, optionally followed by a label. Owns its
3
4
  // own styling (it does not rely on any global `.dot` rules). Colour comes from
4
5
  // either:
@@ -20,34 +21,39 @@
20
21
  hibernated: 'var(--dot-hibernated)',
21
22
  };
22
23
 
24
+ type Own = {
25
+ status?: Status;
26
+ color?: string;
27
+ label?: string;
28
+ glow?: boolean;
29
+ ring?: boolean;
30
+ /** Focusable (`tabindex=0`, `role=img`) so it can anchor a Tooltip. */
31
+ interactive?: boolean;
32
+ class?: string;
33
+ };
23
34
  let {
24
35
  status = 'active',
25
36
  color,
26
37
  label,
27
38
  glow = false,
28
39
  ring = false,
40
+ interactive = false,
29
41
  class: klass = '',
30
42
  ...rest
31
- }: {
32
- status?: Status;
33
- color?: string;
34
- label?: string;
35
- glow?: boolean;
36
- ring?: boolean;
37
- class?: string;
38
- [key: string]: unknown;
39
- } = $props();
43
+ }: Omit<HTMLAttributes<HTMLSpanElement>, keyof Own> & Own = $props();
40
44
 
41
45
  const resolved = $derived(color ?? STATUS_COLOR[status]);
42
46
  </script>
43
47
 
44
48
  {#if label}
45
- <span class="dot-row {klass}" data-tsu="Dot" {...rest}>
49
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
50
+ <span class="dot-row {klass}" data-tsu="Dot" role={interactive ? 'img' : undefined} tabindex={interactive ? 0 : undefined} aria-label={interactive ? label : undefined} {...rest}>
46
51
  <span class="dot" class:glow class:ring style="--dot-color:{resolved}"></span>
47
52
  <Text variant="caption">{label}</Text>
48
53
  </span>
49
54
  {:else}
50
- <span class="dot {klass}" class:glow class:ring style="--dot-color:{resolved}" data-tsu="Dot" {...rest}></span>
55
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
56
+ <span class="dot {klass}" class:glow class:ring style="--dot-color:{resolved}" data-tsu="Dot" role={interactive ? 'img' : undefined} tabindex={interactive ? 0 : undefined} {...rest}></span>
51
57
  {/if}
52
58
 
53
59
  <style>