@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
@@ -27,7 +27,9 @@
27
27
  onclick,
28
28
  badge,
29
29
  badgeTone = 'neutral',
30
+ orientation = 'horizontal',
30
31
  children,
32
+ class: klass = '',
31
33
  ...rest
32
34
  }: {
33
35
  icon?: IconName;
@@ -48,8 +50,13 @@
48
50
  badge?: string | number;
49
51
  /** Tone for the trailing badge when the item is not active. */
50
52
  badgeTone?: BadgeTone;
53
+ /** `vertical` stacks the icon over the label with the badge overlaid on the
54
+ * icon corner (bottom tab bars). */
55
+ orientation?: 'horizontal' | 'vertical';
51
56
  children?: Snippet;
52
57
  [key: string]: unknown;
58
+ class?: string;
59
+ style?: string;
53
60
  } = $props();
54
61
 
55
62
  // Treat 0 / '' as "no badge" so callers can pass a raw count without guarding.
@@ -61,7 +68,8 @@
61
68
  data-tsu="NavItem"
62
69
  {href}
63
70
  type={href ? undefined : 'button'}
64
- class="nav-item"
71
+ class="nav-item {klass}"
72
+ class:vertical={orientation === 'vertical'}
65
73
  class:active
66
74
  class:bar={activeStyle === 'bar'}
67
75
  title={label}
@@ -148,6 +156,19 @@
148
156
  .nav-trail {
149
157
  flex: none;
150
158
  }
159
+ .nav-item.vertical {
160
+ flex-direction: column;
161
+ justify-content: center;
162
+ gap: 2px;
163
+ padding: var(--sp-1) var(--sp-2);
164
+ font-size: var(--fs-xs);
165
+ text-align: center;
166
+ }
167
+ .nav-item.vertical .nav-trail {
168
+ position: absolute;
169
+ top: 0;
170
+ left: calc(50% + 0.4rem);
171
+ }
151
172
  /* Dot is the rail-width stand-in for the badge: hidden at full width. */
152
173
  .nav-dot {
153
174
  display: none;
@@ -20,8 +20,13 @@ type $$ComponentProps = {
20
20
  badge?: string | number;
21
21
  /** Tone for the trailing badge when the item is not active. */
22
22
  badgeTone?: BadgeTone;
23
+ /** `vertical` stacks the icon over the label with the badge overlaid on the
24
+ * icon corner (bottom tab bars). */
25
+ orientation?: 'horizontal' | 'vertical';
23
26
  children?: Snippet;
24
27
  [key: string]: unknown;
28
+ class?: string;
29
+ style?: string;
25
30
  };
26
31
  declare const NavItem: import("svelte").Component<$$ComponentProps, {}, "">;
27
32
  type NavItem = ReturnType<typeof NavItem>;
@@ -10,6 +10,7 @@
10
10
  let {
11
11
  label,
12
12
  children,
13
+ class: klass = '',
13
14
  ...rest
14
15
  }: {
15
16
  /** Optional section heading; rendered uppercase + mono. Omit for an
@@ -17,10 +18,12 @@
17
18
  label?: string;
18
19
  children?: Snippet;
19
20
  [key: string]: unknown;
21
+ class?: string;
22
+ style?: string;
20
23
  } = $props();
21
24
  </script>
22
25
 
23
- <div class="nav-section" data-tsu="NavSection" {...rest}>
26
+ <div class="nav-section {klass}" data-tsu="NavSection" {...rest}>
24
27
  {#if label}
25
28
  <div class="nav-section-label">{label}</div>
26
29
  {/if}
@@ -5,6 +5,8 @@ type $$ComponentProps = {
5
5
  label?: string;
6
6
  children?: Snippet;
7
7
  [key: string]: unknown;
8
+ class?: string;
9
+ style?: string;
8
10
  };
9
11
  declare const NavSection: import("svelte").Component<$$ComponentProps, {}, "">;
10
12
  type NavSection = ReturnType<typeof NavSection>;
@@ -26,7 +26,10 @@
26
26
  onclose,
27
27
  scrim,
28
28
  fullWidthBelow,
29
- clampToViewport = true
29
+ clampToViewport = true,
30
+ collapseControl,
31
+ class: klass = '',
32
+ style: styleProp = '',
30
33
  }: {
31
34
  /** Content shown while the panel is expanded. */
32
35
  panel: Snippet;
@@ -55,6 +58,9 @@
55
58
  /** Keep the collapse handle in view when the panel scrolls past the
56
59
  * viewport, repositioning on scroll/resize via requestAnimationFrame. */
57
60
  stickyHandle?: boolean;
61
+ /** Render the edge chevron. Defaults to on inline, off in overlay mode
62
+ * (Escape and the scrim already close a drawer). */
63
+ collapseControl?: boolean;
58
64
  /** `inline` shares the row with `children`; `overlay` fixes the panel to
59
65
  * its viewport edge as a non-modal drawer above the page. */
60
66
  mode?: 'inline' | 'overlay';
@@ -69,6 +75,8 @@
69
75
  fullWidthBelow?: string;
70
76
  /** Overlay mode: cap the width at the viewport and re-clamp on window resize. */
71
77
  clampToViewport?: boolean;
78
+ class?: string;
79
+ style?: string;
72
80
  } = $props();
73
81
 
74
82
  let root: HTMLDivElement;
@@ -83,6 +91,7 @@
83
91
  let fullBleed = $state(false);
84
92
 
85
93
  const overlay = $derived(mode === 'overlay');
94
+ const showCollapseControl = $derived(collapseControl ?? !overlay);
86
95
  const shown = $derived(overlay ? open : !collapsed);
87
96
  const showScrim = $derived(overlay && open && (scrim ?? true));
88
97
 
@@ -268,14 +277,14 @@
268
277
 
269
278
  <div
270
279
  bind:this={root}
271
- class="panel-layout"
280
+ class="panel-layout {klass}"
272
281
  class:left={side === 'left'}
273
282
  class:right={side === 'right'}
274
283
  class:collapsed={!overlay && collapsed}
275
284
  class:overlay
276
285
  class:full-bleed={fullBleed}
277
286
  class:resizing
278
- style="--panel-width: {currentWidth}px"
287
+ style="--panel-width: {currentWidth}px; {styleProp}"
279
288
  data-tsu="ResizablePanel"
280
289
  >
281
290
  {#if showScrim}
@@ -319,6 +328,7 @@
319
328
  ></div>
320
329
  {/if}
321
330
 
331
+ {#if showCollapseControl}
322
332
  <button
323
333
  bind:this={handleEl}
324
334
  type="button"
@@ -333,6 +343,7 @@
333
343
  >
334
344
  <Icon name={toggleIcon} size={14} />
335
345
  </button>
346
+ {/if}
336
347
  </svelte:element>
337
348
  {/if}
338
349
 
@@ -27,6 +27,9 @@ type $$ComponentProps = {
27
27
  /** Keep the collapse handle in view when the panel scrolls past the
28
28
  * viewport, repositioning on scroll/resize via requestAnimationFrame. */
29
29
  stickyHandle?: boolean;
30
+ /** Render the edge chevron. Defaults to on inline, off in overlay mode
31
+ * (Escape and the scrim already close a drawer). */
32
+ collapseControl?: boolean;
30
33
  /** `inline` shares the row with `children`; `overlay` fixes the panel to
31
34
  * its viewport edge as a non-modal drawer above the page. */
32
35
  mode?: 'inline' | 'overlay';
@@ -41,6 +44,8 @@ type $$ComponentProps = {
41
44
  fullWidthBelow?: string;
42
45
  /** Overlay mode: cap the width at the viewport and re-clamp on window resize. */
43
46
  clampToViewport?: boolean;
47
+ class?: string;
48
+ style?: string;
44
49
  };
45
50
  declare const ResizablePanel: import("svelte").Component<$$ComponentProps, {}, "open" | "collapsed">;
46
51
  type ResizablePanel = ReturnType<typeof ResizablePanel>;
@@ -10,6 +10,10 @@
10
10
  gap = 'var(--sp-3)',
11
11
  align,
12
12
  justify,
13
+ grow = false,
14
+ shrink = true,
15
+ fill = false,
16
+ push,
13
17
  class: klass = '',
14
18
  children,
15
19
  ...rest
@@ -18,7 +22,16 @@
18
22
  gap?: string;
19
23
  align?: string;
20
24
  justify?: string;
25
+ /** `flex: 1 1 0` inside a parent row/column. */
26
+ grow?: boolean;
27
+ /** `false` pins the stack (`flex: none`). */
28
+ shrink?: boolean;
29
+ /** `height: 100%` to fill a flex parent. */
30
+ fill?: boolean;
31
+ /** Auto margin pushing the stack to the start/end of its parent. */
32
+ push?: 'start' | 'end';
21
33
  class?: string;
34
+ style?: string;
22
35
  children?: Snippet;
23
36
  [key: string]: unknown;
24
37
  } = $props();
@@ -28,6 +41,11 @@
28
41
  this={as}
29
42
  data-tsu="Stack"
30
43
  class="stack-c {klass}"
44
+ class:grow
45
+ class:no-shrink={!shrink}
46
+ class:fill
47
+ class:push-start={push === 'start'}
48
+ class:push-end={push === 'end'}
31
49
  style:gap
32
50
  style:align-items={align}
33
51
  style:justify-content={justify}
@@ -42,4 +60,27 @@
42
60
  flex-direction: column;
43
61
  min-width: 0;
44
62
  }
63
+ .stack-c > :global([data-grow]) {
64
+ flex: 1 1 0;
65
+ min-height: 0;
66
+ }
67
+ .stack-c > :global([data-shrink='false']) {
68
+ flex: none;
69
+ }
70
+ .grow {
71
+ flex: 1 1 0;
72
+ min-height: 0;
73
+ }
74
+ .no-shrink {
75
+ flex: none;
76
+ }
77
+ .fill {
78
+ height: 100%;
79
+ }
80
+ .push-start {
81
+ margin-block-end: auto;
82
+ }
83
+ .push-end {
84
+ margin-block-start: auto;
85
+ }
45
86
  </style>
@@ -4,7 +4,16 @@ type $$ComponentProps = {
4
4
  gap?: string;
5
5
  align?: string;
6
6
  justify?: string;
7
+ /** `flex: 1 1 0` inside a parent row/column. */
8
+ grow?: boolean;
9
+ /** `false` pins the stack (`flex: none`). */
10
+ shrink?: boolean;
11
+ /** `height: 100%` to fill a flex parent. */
12
+ fill?: boolean;
13
+ /** Auto margin pushing the stack to the start/end of its parent. */
14
+ push?: 'start' | 'end';
7
15
  class?: string;
16
+ style?: string;
8
17
  children?: Snippet;
9
18
  [key: string]: unknown;
10
19
  };
@@ -0,0 +1,122 @@
1
+ <script lang="ts">
2
+ // Horizontal action bar. Below `collapseBelow` (measured on the bar's own box)
3
+ // every child marked `data-overflow` is hidden and a `…` button opens a Menu
4
+ // with `items` (or the `overflow` snippet in a Popover) that stands in for
5
+ // them. `sticky` pins the bar under `stickyOffset`.
6
+ import type { Snippet } from 'svelte';
7
+ import IconButton from '../molecules/IconButton.svelte';
8
+ import Menu, { type MenuItem } from '../molecules/Menu.svelte';
9
+ import Popover from '../molecules/Popover.svelte';
10
+
11
+ let {
12
+ children,
13
+ items,
14
+ overflow,
15
+ collapseBelow,
16
+ sticky = false,
17
+ stickyOffset = '0px',
18
+ density = 'default',
19
+ overflowLabel = 'More',
20
+ label,
21
+ class: klass = '',
22
+ style: styleProp = ''
23
+ }: {
24
+ children: Snippet;
25
+ /** Menu rows standing in for the collapsed `data-overflow` children. */
26
+ items?: MenuItem[];
27
+ /** Free-form panel content instead of `items`. */
28
+ overflow?: Snippet;
29
+ /** Width (px/em/rem) under which `data-overflow` children collapse. */
30
+ collapseBelow?: string;
31
+ sticky?: boolean;
32
+ stickyOffset?: string;
33
+ density?: 'compact' | 'default';
34
+ overflowLabel?: string;
35
+ /** Accessible name of the bar (`role="toolbar"`). */
36
+ label?: string;
37
+ class?: string;
38
+ style?: string;
39
+ } = $props();
40
+
41
+ let el = $state<HTMLDivElement | null>(null);
42
+ let collapsed = $state(false);
43
+
44
+ function toPx(length: string, node: HTMLElement): number {
45
+ const n = Number.parseFloat(length);
46
+ const fontSize = (x: Element) => Number.parseFloat(getComputedStyle(x).fontSize);
47
+ if (length.endsWith('rem')) return n * fontSize(document.documentElement);
48
+ if (length.endsWith('em')) return n * fontSize(node);
49
+ return n;
50
+ }
51
+
52
+ $effect(() => {
53
+ if (!collapseBelow || !el) {
54
+ collapsed = false;
55
+ return;
56
+ }
57
+ const node = el;
58
+ const limit = toPx(collapseBelow, node);
59
+ const ro = new ResizeObserver(([entry]) => {
60
+ collapsed = entry.contentRect.width < limit;
61
+ });
62
+ ro.observe(node);
63
+ return () => ro.disconnect();
64
+ });
65
+
66
+ const hasOverflow = $derived(!!items?.length || !!overflow);
67
+ </script>
68
+
69
+ <div
70
+ bind:this={el}
71
+ data-tsu="Toolbar"
72
+ role="toolbar"
73
+ aria-label={label}
74
+ class="toolbar density-{density} {klass}"
75
+ class:sticky
76
+ class:collapsed
77
+ data-collapsed={collapsed || undefined}
78
+ style="--toolbar-top: {stickyOffset}; {styleProp}"
79
+ >
80
+ {@render children()}
81
+ {#if collapsed && hasOverflow}
82
+ {#if items?.length}
83
+ <Menu label={overflowLabel} {items} placement="bottom-end" box="sm">
84
+ {#snippet trigger()}<IconButton icon="more" label={overflowLabel} box="sm" />{/snippet}
85
+ </Menu>
86
+ {:else if overflow}
87
+ <Popover label={overflowLabel} placement="bottom-end" box="sm">
88
+ {#snippet trigger()}<IconButton icon="more" label={overflowLabel} box="sm" />{/snippet}
89
+ <div class="overflow-panel">{@render overflow()}</div>
90
+ </Popover>
91
+ {/if}
92
+ {/if}
93
+ </div>
94
+
95
+ <style>
96
+ .toolbar {
97
+ display: flex;
98
+ align-items: center;
99
+ flex-wrap: nowrap;
100
+ gap: var(--sp-2);
101
+ min-width: 0;
102
+ container-type: inline-size;
103
+ }
104
+ .density-compact {
105
+ gap: var(--sp-1);
106
+ }
107
+ .sticky {
108
+ position: sticky;
109
+ top: var(--toolbar-top, 0);
110
+ z-index: calc(var(--z-header) - 1);
111
+ background: var(--bg);
112
+ }
113
+ .collapsed :global([data-overflow]) {
114
+ display: none;
115
+ }
116
+ .overflow-panel {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: var(--sp-1);
120
+ padding: var(--sp-1);
121
+ }
122
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type MenuItem } from '../molecules/Menu.svelte';
3
+ type $$ComponentProps = {
4
+ children: Snippet;
5
+ /** Menu rows standing in for the collapsed `data-overflow` children. */
6
+ items?: MenuItem[];
7
+ /** Free-form panel content instead of `items`. */
8
+ overflow?: Snippet;
9
+ /** Width (px/em/rem) under which `data-overflow` children collapse. */
10
+ collapseBelow?: string;
11
+ sticky?: boolean;
12
+ stickyOffset?: string;
13
+ density?: 'compact' | 'default';
14
+ overflowLabel?: string;
15
+ /** Accessible name of the bar (`role="toolbar"`). */
16
+ label?: string;
17
+ class?: string;
18
+ style?: string;
19
+ };
20
+ declare const Toolbar: import("svelte").Component<$$ComponentProps, {}, "">;
21
+ type Toolbar = ReturnType<typeof Toolbar>;
22
+ export default Toolbar;
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ // Removable file chips for a composer or upload form.
3
+ import Badge from '../atoms/Badge.svelte';
4
+
5
+ export type Attachment = { name: string; size?: number };
6
+
7
+ let {
8
+ files,
9
+ onremove,
10
+ class: klass = '',
11
+ style: styleProp = ''
12
+ }: {
13
+ files: (File | Attachment)[];
14
+ onremove?: (index: number) => void;
15
+ class?: string;
16
+ style?: string;
17
+ } = $props();
18
+
19
+ function fmt(size: number | undefined): string {
20
+ if (size === undefined) return '';
21
+ if (size < 1024) return `${size} B`;
22
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
23
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
24
+ }
25
+ </script>
26
+
27
+ {#if files.length}
28
+ <ul data-tsu="AttachmentList" class="attachments {klass}" style={styleProp}>
29
+ {#each files as f, i (`${f.name}-${i}`)}
30
+ <li>
31
+ <Badge
32
+ mono
33
+ truncate
34
+ maxWidth="16rem"
35
+ removable={!!onremove}
36
+ onremove={() => onremove?.(i)}
37
+ title={f.name}
38
+ >
39
+ {f.name}{#if f.size !== undefined}<span class="size">{fmt(f.size)}</span>{/if}
40
+ </Badge>
41
+ </li>
42
+ {/each}
43
+ </ul>
44
+ {/if}
45
+
46
+ <style>
47
+ .attachments {
48
+ display: flex;
49
+ flex-wrap: wrap;
50
+ gap: var(--sp-1);
51
+ margin: 0;
52
+ padding: 0;
53
+ list-style: none;
54
+ }
55
+ .size {
56
+ margin-inline-start: var(--sp-1);
57
+ color: var(--text-faint);
58
+ }
59
+ </style>
@@ -0,0 +1,13 @@
1
+ export type Attachment = {
2
+ name: string;
3
+ size?: number;
4
+ };
5
+ type $$ComponentProps = {
6
+ files: (File | Attachment)[];
7
+ onremove?: (index: number) => void;
8
+ class?: string;
9
+ style?: string;
10
+ };
11
+ declare const AttachmentList: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type AttachmentList = ReturnType<typeof AttachmentList>;
13
+ export default AttachmentList;
@@ -18,7 +18,9 @@
18
18
  items,
19
19
  char,
20
20
  separator,
21
- maxItems = 0
21
+ maxItems = 0,
22
+ class: klass = '',
23
+ style: styleProp = '',
22
24
  }: {
23
25
  items: BreadcrumbItem[];
24
26
  /** Separator character; ignored when the `separator` snippet is set. */
@@ -27,6 +29,8 @@
27
29
  /** Collapse the middle to an ellipsis when the trail is longer than this
28
30
  * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
29
31
  maxItems?: number;
32
+ class?: string;
33
+ style?: string;
30
34
  } = $props();
31
35
 
32
36
  type Crumb = BreadcrumbItem | { ellipsis: true };
@@ -38,7 +42,7 @@
38
42
  );
39
43
  </script>
40
44
 
41
- <nav aria-label="breadcrumb" data-tsu="Breadcrumb">
45
+ <nav aria-label="breadcrumb" data-tsu="Breadcrumb" class={klass} style={styleProp}>
42
46
  <ol class="crumbs">
43
47
  {#each shown as item, i (i)}
44
48
  <li class="crumb">
@@ -11,6 +11,8 @@ type $$ComponentProps = {
11
11
  /** Collapse the middle to an ellipsis when the trail is longer than this
12
12
  * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
13
13
  maxItems?: number;
14
+ class?: string;
15
+ style?: string;
14
16
  };
15
17
  declare const Breadcrumb: import("svelte").Component<$$ComponentProps, {}, "">;
16
18
  type Breadcrumb = ReturnType<typeof Breadcrumb>;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { canonicalTone, type Tone } from '../../tone';
2
3
  // Inline message banner — the one way to say "heads up" inside a page:
3
4
  // tone-tinted Card with a leading glyph, optional title, body, right-aligned
4
5
  // actions and an optional dismiss button. `busy` swaps the glyph for a
@@ -10,12 +11,13 @@
10
11
  import Spinner from '../atoms/Spinner.svelte';
11
12
  import IconButton from './IconButton.svelte';
12
13
 
13
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
14
14
 
15
15
  const DEFAULT_ICON: Record<Tone, IconName> = {
16
16
  neutral: 'info',
17
17
  info: 'info',
18
18
  ok: 'check',
19
+ success: 'check',
20
+ accent: 'info',
19
21
  warn: 'warning',
20
22
  danger: 'warning'
21
23
  };
@@ -45,15 +47,16 @@
45
47
  actions?: Snippet;
46
48
  [key: string]: unknown;
47
49
  } = $props();
50
+ const t = $derived(canonicalTone(tone));
48
51
 
49
52
  let glyph = $derived(icon ?? DEFAULT_ICON[tone]);
50
53
  </script>
51
54
 
52
55
  <Card
53
- {tone}
56
+ tone={t}
54
57
  padding="none"
55
- role={tone === 'danger' ? 'alert' : 'status'}
56
- class="callout callout-{tone} {klass}"
58
+ role={t === 'danger' ? 'alert' : 'status'}
59
+ class="callout callout-{t} {klass}"
57
60
  data-tsu="Callout"
58
61
  {...rest}
59
62
  >
@@ -99,6 +102,9 @@
99
102
  :global(.callout-info) {
100
103
  --callout-tone: var(--info);
101
104
  }
105
+ :global(.callout-accent) {
106
+ --callout-tone: var(--accent);
107
+ }
102
108
 
103
109
  .callout-icon {
104
110
  display: inline-flex;
@@ -1,6 +1,6 @@
1
+ import { type Tone } from '../../tone';
1
2
  import type { Snippet } from 'svelte';
2
3
  import { type IconName } from '../atoms/Icon.svelte';
3
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
4
4
  type $$ComponentProps = {
5
5
  tone?: Tone;
6
6
  icon?: IconName;
@@ -58,6 +58,7 @@
58
58
  const tone = $derived(capTone(value, cap, warnAt));
59
59
  const readoutText = $derived(hint ? `${value}% · ${hint}` : `${value}%`);
60
60
  const tip = $derived(tooltip ?? `cap ${cap}% — drag the bar`);
61
+ const readoutTitle = $derived(readout === undefined ? readoutText : typeof readout === 'string' ? readout : undefined);
61
62
  const ariaLabel = $derived(typeof label === 'string' ? `${label} cap` : 'cap');
62
63
 
63
64
  let trackEl = $state<HTMLDivElement | null>(null);
@@ -142,7 +143,7 @@
142
143
  onkeyup={keyUp}
143
144
  ></div>
144
145
  </div>
145
- <div class="readout">
146
+ <div class="readout" title={readoutTitle}>
146
147
  {#if readout === undefined}{readoutText}{:else if typeof readout === 'string'}{readout}{:else}{@render readout()}{/if}
147
148
  </div>
148
149
  {#if caption}
@@ -233,6 +234,9 @@
233
234
  opacity: 0.6;
234
235
  }
235
236
  .readout {
237
+ min-width: 0;
238
+ overflow: hidden;
239
+ text-overflow: ellipsis;
236
240
  text-align: right;
237
241
  font-variant-numeric: tabular-nums;
238
242
  color: var(--text-muted);