@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
@@ -1,4 +1,18 @@
1
+ <script lang="ts" module>
2
+ import type { IconName } from '../atoms/Icon.svelte';
3
+ import type { Tone } from '../../tone';
4
+
5
+ export type MetricSegment = {
6
+ glyph?: string;
7
+ icon?: IconName;
8
+ value: string | number;
9
+ tone?: Tone;
10
+ hint?: string;
11
+ };
12
+ </script>
13
+
1
14
  <script lang="ts">
15
+ import { canonicalTone } from '../../tone';
2
16
  // KPI / stat tile — the dashboard's repeating "one number" card. Composes on
3
17
  // top of `Card` and lays out four parts:
4
18
  // • an uppercase, letter-spaced micro-label (top-left)
@@ -9,9 +23,8 @@
9
23
  // `neutral` keeps the chip on the plain accent. Everything is token-driven.
10
24
  import type { Snippet } from 'svelte';
11
25
  import Card from '../atoms/Card.svelte';
12
- import Icon, { type IconName } from '../atoms/Icon.svelte';
13
-
14
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
26
+ import Icon from '../atoms/Icon.svelte';
27
+ import Tooltip from './Tooltip.svelte';
15
28
 
16
29
  let {
17
30
  label,
@@ -25,6 +38,12 @@
25
38
  // Theme-aware surface shade, forwarded to the composed Card so tiles can
26
39
  // opt into the same shade as sibling panels (no app-level :global override).
27
40
  surface = 'base',
41
+ size = 'md',
42
+ layout = 'card',
43
+ href,
44
+ external = false,
45
+ onclick,
46
+ segments,
28
47
  class: klass = '',
29
48
  // Raw SVG markup for a custom icon — passed through to `Icon` so any glyph
30
49
  // outside the registry can fill the chip.
@@ -39,25 +58,78 @@
39
58
  tone?: Tone;
40
59
  tintValue?: boolean;
41
60
  surface?: 'base' | 'raised' | 'sunken';
61
+ /** `sm` is a dense tile (smaller value, tighter padding). */
62
+ size?: 'sm' | 'md';
63
+ /** Render the tile as a link / button with tap feedback. */
64
+ href?: string;
65
+ /** With `href`: new tab plus an arrow glyph after the label. */
66
+ external?: boolean;
67
+ onclick?: (e: MouseEvent | KeyboardEvent) => void;
68
+ /** `inline` drops the Card and renders a single stat row. */
69
+ layout?: 'card' | 'inline';
70
+ /** Extra stats after the value, each with an optional Tooltip hint. */
71
+ segments?: MetricSegment[];
42
72
  class?: string;
43
73
  iconChildren?: Snippet;
44
74
  [key: string]: unknown;
45
75
  } = $props();
46
76
  </script>
47
77
 
78
+ {#snippet segment(seg: MetricSegment)}
79
+ {@const c = canonicalTone(seg.tone ?? 'neutral')}
80
+ <span class="metric-seg metric-seg-{c}">
81
+ {#if seg.icon}<Icon name={seg.icon} />{:else if seg.glyph}<span aria-hidden="true">{seg.glyph}</span>{/if}
82
+ <span class="metric-seg-val">{seg.value}</span>
83
+ </span>
84
+ {/snippet}
85
+
86
+ {#snippet segmentRow()}
87
+ {#if segments?.length}
88
+ <div class="metric-segments">
89
+ {#each segments as seg, i (i)}
90
+ {#if seg.hint}
91
+ <Tooltip text={seg.hint}>{#snippet trigger()}{@render segment(seg)}{/snippet}</Tooltip>
92
+ {:else}
93
+ {@render segment(seg)}
94
+ {/if}
95
+ {/each}
96
+ </div>
97
+ {/if}
98
+ {/snippet}
99
+
100
+ {#if layout === 'inline'}
101
+ <div
102
+ data-tsu="Metric"
103
+ class="metric metric-inline metric-{canonicalTone(tone)} {tintValue ? 'metric-tint' : ''} {klass}"
104
+ class:metric-sm={size === 'sm'}
105
+ {...rest}
106
+ >
107
+ <span class="metric-label">{label}</span>
108
+ <span class="metric-value">
109
+ <span class="metric-num">{value}</span>
110
+ {#if unit}<span class="metric-unit">{unit}</span>{/if}
111
+ </span>
112
+ {@render segmentRow()}
113
+ {#if sub}<span class="metric-sub">{sub}</span>{/if}
114
+ </div>
115
+ {:else}
48
116
  <Card
49
117
  data-tsu="Metric"
50
- class="metric metric-{tone} {tintValue ? 'metric-tint' : ''} {klass}"
118
+ class="metric metric-{canonicalTone(tone)} {tintValue ? 'metric-tint' : ''} {size === 'sm' ? 'metric-sm' : ''} {klass}"
119
+ padding={size === 'sm' ? 'sm' : undefined}
51
120
  {surface}
121
+ as={href ? 'a' : onclick ? 'button' : 'div'}
122
+ {href}
123
+ target={href && external ? '_blank' : undefined}
124
+ rel={href && external ? 'noopener noreferrer' : undefined}
125
+ {onclick}
126
+ tap={!!(href || onclick)}
52
127
  {...rest}
53
128
  >
54
129
  <div class="metric-head">
55
- <span class="metric-label">{label}</span>
130
+ <span class="metric-label">{label}{#if href && external}<Icon name="external" />{/if}</span>
56
131
  {#if icon || iconChildren}
57
132
  <span class="metric-chip" aria-hidden="true">
58
- <!-- Only forward a children snippet when a custom icon was actually
59
- provided; otherwise Icon's `children` branch always wins and the
60
- registry `name` glyph never renders (empty chip). -->
61
133
  {#if iconChildren}
62
134
  <Icon name={icon}>{@render iconChildren()}</Icon>
63
135
  {:else}
@@ -70,8 +142,10 @@
70
142
  <span class="metric-num">{value}</span>
71
143
  {#if unit}<span class="metric-unit">{unit}</span>{/if}
72
144
  </div>
145
+ {@render segmentRow()}
73
146
  {#if sub}<div class="metric-sub">{sub}</div>{/if}
74
147
  </Card>
148
+ {/if}
75
149
 
76
150
  <style>
77
151
  :global(.metric) {
@@ -130,6 +204,8 @@
130
204
  line-height: 1.1;
131
205
  }
132
206
  .metric-num {
207
+ min-width: 0;
208
+ overflow-wrap: anywhere;
133
209
  font-size: var(--fs-2xl);
134
210
  font-weight: var(--fw-semibold);
135
211
  color: var(--text);
@@ -148,4 +224,54 @@
148
224
  color: var(--text-faint);
149
225
  line-height: 1.4;
150
226
  }
227
+ :global(.metric-sm) .metric-num {
228
+ font-size: var(--fs-lg);
229
+ }
230
+ :global(.metric-sm) .metric-chip {
231
+ width: 1.5rem;
232
+ height: 1.5rem;
233
+ font-size: 0.875rem;
234
+ }
235
+ .metric-inline {
236
+ flex-direction: row;
237
+ align-items: baseline;
238
+ flex-wrap: wrap;
239
+ gap: var(--sp-2) var(--sp-3);
240
+ min-width: 0;
241
+ }
242
+ .metric-inline .metric-num {
243
+ font-size: var(--fs-md);
244
+ }
245
+ .metric-segments {
246
+ display: flex;
247
+ flex-wrap: wrap;
248
+ align-items: baseline;
249
+ gap: var(--sp-1) var(--sp-3);
250
+ font-family: var(--font-mono);
251
+ font-size: var(--fs-xs);
252
+ color: var(--text-muted);
253
+ }
254
+ .metric-seg {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ gap: var(--sp-1);
258
+ --seg-tone: var(--text-muted);
259
+ color: var(--seg-tone);
260
+ font-variant-numeric: tabular-nums;
261
+ }
262
+ .metric-seg-ok {
263
+ --seg-tone: var(--ok);
264
+ }
265
+ .metric-seg-warn {
266
+ --seg-tone: var(--warn);
267
+ }
268
+ .metric-seg-danger {
269
+ --seg-tone: var(--danger);
270
+ }
271
+ .metric-seg-info {
272
+ --seg-tone: var(--info);
273
+ }
274
+ .metric-seg-accent {
275
+ --seg-tone: var(--accent);
276
+ }
151
277
  </style>
@@ -1,6 +1,13 @@
1
+ import type { IconName } from '../atoms/Icon.svelte';
2
+ import type { Tone } from '../../tone';
3
+ export type MetricSegment = {
4
+ glyph?: string;
5
+ icon?: IconName;
6
+ value: string | number;
7
+ tone?: Tone;
8
+ hint?: string;
9
+ };
1
10
  import type { Snippet } from 'svelte';
2
- import { type IconName } from '../atoms/Icon.svelte';
3
- type Tone = 'neutral' | 'ok' | 'warn' | 'danger' | 'info';
4
11
  type $$ComponentProps = {
5
12
  label: string;
6
13
  value: string | number;
@@ -10,6 +17,17 @@ type $$ComponentProps = {
10
17
  tone?: Tone;
11
18
  tintValue?: boolean;
12
19
  surface?: 'base' | 'raised' | 'sunken';
20
+ /** `sm` is a dense tile (smaller value, tighter padding). */
21
+ size?: 'sm' | 'md';
22
+ /** Render the tile as a link / button with tap feedback. */
23
+ href?: string;
24
+ /** With `href`: new tab plus an arrow glyph after the label. */
25
+ external?: boolean;
26
+ onclick?: (e: MouseEvent | KeyboardEvent) => void;
27
+ /** `inline` drops the Card and renders a single stat row. */
28
+ layout?: 'card' | 'inline';
29
+ /** Extra stats after the value, each with an optional Tooltip hint. */
30
+ segments?: MetricSegment[];
13
31
  class?: string;
14
32
  iconChildren?: Snippet;
15
33
  [key: string]: unknown;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { canonicalTone, type Tone } from '../../tone';
2
3
  // Accessible dialog / bottom-sheet built on the native <dialog> element, so
3
4
  // the platform gives us the hard parts for free: top-layer rendering above
4
5
  // everything (no z-index races), a real focus trap, inert background, initial
@@ -12,11 +13,12 @@
12
13
  import Spinner from '../atoms/Spinner.svelte';
13
14
  import IconButton from './IconButton.svelte';
14
15
 
15
- type Tone = 'neutral' | 'danger' | 'warn' | 'info';
16
- const TONE_ICON: Record<Exclude<Tone, 'neutral'>, IconName> = {
16
+ const TONE_ICON: Record<Exclude<Tone, 'neutral' | 'success'>, IconName> = {
17
17
  danger: 'warning',
18
18
  warn: 'warning',
19
- info: 'info'
19
+ info: 'info',
20
+ ok: 'check',
21
+ accent: 'info'
20
22
  };
21
23
 
22
24
  let {
@@ -28,7 +30,11 @@
28
30
  size = 'md',
29
31
  tone = 'neutral',
30
32
  busy = false,
31
- resizeKey
33
+ resizeKey,
34
+ class: klass = '',
35
+ style: styleProp = '',
36
+ bodyClass = '',
37
+ maxHeight,
32
38
  }: {
33
39
  title: string;
34
40
  /** Controlled visibility. When provided the `<dialog>` stays mounted and
@@ -49,7 +55,14 @@
49
55
  /** When set, the sheet is horizontally resizable on desktop and the chosen
50
56
  * width persists under this localStorage key. */
51
57
  resizeKey?: string;
58
+ class?: string;
59
+ style?: string;
60
+ /** Class on the inner sheet (the visible panel). */
61
+ bodyClass?: string;
62
+ /** Cap the sheet height; the body scrolls inside it. */
63
+ maxHeight?: string;
52
64
  } = $props();
65
+ const t = $derived(canonicalTone(tone));
53
66
 
54
67
  const titleId = `modal-title-${Math.random().toString(36).slice(2, 8)}`;
55
68
 
@@ -135,12 +148,12 @@
135
148
  <dialog
136
149
  bind:this={dialogEl}
137
150
  data-tsu="Modal"
138
- class="modal"
151
+ class="modal {klass}"
139
152
  class:resizing
140
153
  aria-labelledby={titleId}
141
154
  aria-busy={busy || undefined}
142
- data-tone={tone === 'neutral' ? undefined : tone}
143
- style={width != null ? `--sheet-w: ${width}px` : undefined}
155
+ data-tone={t === 'neutral' ? undefined : t}
156
+ style="{width != null ? `--sheet-w: ${width}px;` : ''}{styleProp}"
144
157
  oncancel={(e) => {
145
158
  e.preventDefault(); /* keep parent the source of truth for open state */
146
159
  requestClose();
@@ -151,16 +164,17 @@
151
164
  onclick={onDialogClick}
152
165
  >
153
166
  <div
154
- class="sheet"
167
+ class="sheet {bodyClass}"
168
+ style:max-height={maxHeight}
155
169
  class:sheet-sm={size === 'sm'}
156
170
  class:sheet-lg={size === 'lg'}
157
171
  class:sheet-xl={size === 'xl'}
158
- class:sheet-toned={tone !== 'neutral'}
159
- style:--modal-tone={tone === 'neutral' ? undefined : `var(--${tone})`}
172
+ class:sheet-toned={t !== 'neutral'}
173
+ style:--modal-tone={t === 'neutral' ? undefined : `var(--${t})`}
160
174
  >
161
175
  <div class="sheet-head">
162
- {#if tone !== 'neutral'}
163
- <span class="sheet-tone-icon"><Icon name={TONE_ICON[tone]} size={18} /></span>
176
+ {#if t !== 'neutral'}
177
+ <span class="sheet-tone-icon"><Icon name={TONE_ICON[t]} size={18} /></span>
164
178
  {/if}
165
179
  <span id={titleId} class="sheet-title truncate">{title}</span>
166
180
  {#if busy}<Spinner label="Working" />{/if}
@@ -1,5 +1,5 @@
1
+ import { type Tone } from '../../tone';
1
2
  import type { Snippet } from 'svelte';
2
- type Tone = 'neutral' | 'danger' | 'warn' | 'info';
3
3
  type $$ComponentProps = {
4
4
  title: string;
5
5
  /** Controlled visibility. When provided the `<dialog>` stays mounted and
@@ -20,6 +20,12 @@ type $$ComponentProps = {
20
20
  /** When set, the sheet is horizontally resizable on desktop and the chosen
21
21
  * width persists under this localStorage key. */
22
22
  resizeKey?: string;
23
+ class?: string;
24
+ style?: string;
25
+ /** Class on the inner sheet (the visible panel). */
26
+ bodyClass?: string;
27
+ /** Cap the sheet height; the body scrolls inside it. */
28
+ maxHeight?: string;
23
29
  };
24
30
  declare const Modal: import("svelte").Component<$$ComponentProps, {}, "open">;
25
31
  type Modal = ReturnType<typeof Modal>;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { ControlSize } from '../../size';
2
3
  // Page navigation in two flavours: page mode (`page` + `pageCount`) or
3
4
  // offset mode (`offset` + `limit` + `total`, page/pageCount derived and
4
5
  // `offset` written back). Numbered buttons collapse to prev / "3 / 12" /
@@ -17,6 +18,9 @@
17
18
  showEdges = true,
18
19
  showRange = false,
19
20
  size = 'md',
21
+ grow = false,
22
+ shrink = true,
23
+ block = false,
20
24
  label = 'Pagination',
21
25
  class: klass = ''
22
26
  }: {
@@ -34,7 +38,13 @@
34
38
  showEdges?: boolean;
35
39
  /** Show "1–20 / 412" (offset mode) or "3 / 12" next to the buttons. */
36
40
  showRange?: boolean;
37
- size?: 'sm' | 'md';
41
+ size?: ControlSize;
42
+ /** Fill the free space of a flex row (`flex: 1 1 0`). */
43
+ grow?: boolean;
44
+ /** `false` pins the box (`flex: none`) so a flex row cannot squeeze it. */
45
+ shrink?: boolean;
46
+ /** Full-width block. */
47
+ block?: boolean;
38
48
  label?: string;
39
49
  class?: string;
40
50
  } = $props();
@@ -95,6 +105,9 @@
95
105
  <nav
96
106
  data-tsu="Pagination"
97
107
  class="pagination {klass}"
108
+ class:grow={grow}
109
+ class:no-shrink={!shrink}
110
+ class:block={block}
98
111
  class:pagination-sm={size === 'sm'}
99
112
  aria-label={label}
100
113
  >
@@ -139,6 +152,17 @@
139
152
  </nav>
140
153
 
141
154
  <style>
155
+ .grow {
156
+ flex: 1 1 0;
157
+ min-width: 0;
158
+ }
159
+ .no-shrink {
160
+ flex: none;
161
+ }
162
+ .block {
163
+ display: flex;
164
+ width: 100%;
165
+ }
142
166
  .pagination {
143
167
  container-type: inline-size;
144
168
  display: flex;
@@ -1,3 +1,4 @@
1
+ import type { ControlSize } from '../../size';
1
2
  type $$ComponentProps = {
2
3
  /** 1-based current page. */
3
4
  page?: number;
@@ -13,10 +14,16 @@ type $$ComponentProps = {
13
14
  showEdges?: boolean;
14
15
  /** Show "1–20 / 412" (offset mode) or "3 / 12" next to the buttons. */
15
16
  showRange?: boolean;
16
- size?: 'sm' | 'md';
17
+ size?: ControlSize;
18
+ /** Fill the free space of a flex row (`flex: 1 1 0`). */
19
+ grow?: boolean;
20
+ /** `false` pins the box (`flex: none`) so a flex row cannot squeeze it. */
21
+ shrink?: boolean;
22
+ /** Full-width block. */
23
+ block?: boolean;
17
24
  label?: string;
18
25
  class?: string;
19
26
  };
20
- declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "page" | "offset">;
27
+ declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "offset" | "page">;
21
28
  type Pagination = ReturnType<typeof Pagination>;
22
29
  export default Pagination;
@@ -38,7 +38,11 @@
38
38
  role = 'dialog',
39
39
  haspopup = 'dialog',
40
40
  onopen,
41
- onclose
41
+ onclose,
42
+ class: klass = '',
43
+ style: styleProp = '',
44
+ panelClass = '',
45
+ panelStyle = '',
42
46
  }: {
43
47
  placement?: Placement;
44
48
  gap?: number;
@@ -74,6 +78,11 @@
74
78
  haspopup?: HasPopup;
75
79
  onopen?: () => void;
76
80
  onclose?: () => void;
81
+ class?: string;
82
+ style?: string;
83
+ /** Class / inline style on the floating panel. */
84
+ panelClass?: string;
85
+ panelStyle?: string;
77
86
  } = $props();
78
87
 
79
88
  const canonicalChrome = $derived(
@@ -118,7 +127,8 @@
118
127
  bind:this={triggerEl}
119
128
  data-tsu="Popover"
120
129
  type="button"
121
- class="pop-trigger {triggerClass}"
130
+ class="pop-trigger {triggerClass} {klass}"
131
+ style={styleProp}
122
132
  class:bare
123
133
  class:canonical={canonicalChrome}
124
134
  class:trigger-primary={variant === 'primary'}
@@ -149,7 +159,8 @@
149
159
  bind:this={panelEl}
150
160
  {id}
151
161
  popover="auto"
152
- class="pop-panel"
162
+ class="pop-panel {panelClass}"
163
+ style={panelStyle}
153
164
  {role}
154
165
  aria-label={label}
155
166
  ontoggle={onToggle}
@@ -40,6 +40,11 @@ type $$ComponentProps = {
40
40
  haspopup?: HasPopup;
41
41
  onopen?: () => void;
42
42
  onclose?: () => void;
43
+ class?: string;
44
+ style?: string;
45
+ /** Class / inline style on the floating panel. */
46
+ panelClass?: string;
47
+ panelStyle?: string;
43
48
  };
44
49
  declare const Popover: import("svelte").Component<$$ComponentProps, {}, "">;
45
50
  type Popover = ReturnType<typeof Popover>;
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ // Markdown style contract: headings, paragraphs, inline/block code, tables,
3
+ // quotes, lists and images, driven by the --md-* tokens. Feed it rendered
4
+ // `html` (already sanitised upstream) or children markup.
5
+ import type { Snippet } from 'svelte';
6
+
7
+ let {
8
+ html,
9
+ children,
10
+ compact = false,
11
+ class: klass = '',
12
+ style: styleProp = ''
13
+ }: {
14
+ /** Pre-rendered, trusted HTML. */
15
+ html?: string;
16
+ children?: Snippet;
17
+ /** Tighter vertical rhythm for chat bubbles. */
18
+ compact?: boolean;
19
+ class?: string;
20
+ style?: string;
21
+ } = $props();
22
+ </script>
23
+
24
+ <div data-tsu="Prose" class="prose {klass}" class:compact style={styleProp}>
25
+ {#if html !== undefined}{@html html}{:else}{@render children?.()}{/if}
26
+ </div>
27
+
28
+ <style>
29
+ .prose {
30
+ color: var(--md-text, var(--text));
31
+ font-size: var(--fs-sm);
32
+ line-height: var(--lh-normal);
33
+ overflow-wrap: anywhere;
34
+ --prose-gap: var(--sp-3);
35
+ }
36
+ .prose.compact {
37
+ --prose-gap: var(--sp-2);
38
+ }
39
+ .prose :global(> * + *) {
40
+ margin-top: var(--prose-gap);
41
+ }
42
+ .prose :global(p) {
43
+ margin: 0;
44
+ }
45
+ .prose :global(h1),
46
+ .prose :global(h2),
47
+ .prose :global(h3),
48
+ .prose :global(h4) {
49
+ margin: 0;
50
+ color: var(--md-heading, var(--text));
51
+ font-weight: var(--fw-semibold);
52
+ line-height: var(--lh-tight);
53
+ }
54
+ .prose :global(h1) {
55
+ font-size: var(--fs-xl);
56
+ }
57
+ .prose :global(h2) {
58
+ font-size: var(--fs-lg);
59
+ }
60
+ .prose :global(h3) {
61
+ font-size: var(--fs-md);
62
+ }
63
+ .prose :global(h4) {
64
+ font-size: var(--fs-base);
65
+ }
66
+ .prose :global(strong) {
67
+ color: var(--md-strong, var(--text));
68
+ font-weight: var(--fw-semibold);
69
+ }
70
+ .prose :global(a) {
71
+ color: var(--accent);
72
+ text-decoration: underline;
73
+ text-underline-offset: 0.15em;
74
+ }
75
+ .prose :global(code) {
76
+ font-family: var(--font-mono);
77
+ font-size: 0.92em;
78
+ color: var(--md-code, var(--text));
79
+ background: var(--md-code-bg, var(--bg-elevated-2));
80
+ padding: 0.1em 0.35em;
81
+ border-radius: var(--r-sm);
82
+ }
83
+ .prose :global(pre) {
84
+ margin: 0;
85
+ padding: var(--sp-3);
86
+ background: var(--bg-elevated);
87
+ border: 1px solid var(--border);
88
+ border-radius: var(--r-md);
89
+ overflow-x: auto;
90
+ font-family: var(--font-mono);
91
+ font-size: var(--fs-xs);
92
+ line-height: 1.5;
93
+ }
94
+ .prose :global(pre code) {
95
+ padding: 0;
96
+ background: none;
97
+ color: inherit;
98
+ font-size: inherit;
99
+ }
100
+ .prose :global(blockquote) {
101
+ margin: 0;
102
+ padding-inline-start: var(--sp-3);
103
+ border-inline-start: 3px solid var(--border-strong);
104
+ color: var(--text-muted);
105
+ }
106
+ .prose :global(ul),
107
+ .prose :global(ol) {
108
+ margin: 0;
109
+ padding-inline-start: 1.4em;
110
+ }
111
+ .prose :global(li + li) {
112
+ margin-top: var(--sp-1);
113
+ }
114
+ .prose :global(table) {
115
+ border-collapse: collapse;
116
+ width: 100%;
117
+ font-size: var(--fs-xs);
118
+ }
119
+ .prose :global(th),
120
+ .prose :global(td) {
121
+ padding: var(--sp-1) var(--sp-2);
122
+ border: 1px solid var(--border);
123
+ text-align: left;
124
+ vertical-align: top;
125
+ }
126
+ .prose :global(th) {
127
+ background: var(--bg-elevated-2);
128
+ color: var(--text-muted);
129
+ font-weight: var(--fw-semibold);
130
+ }
131
+ .prose :global(img) {
132
+ max-width: 100%;
133
+ height: auto;
134
+ border-radius: var(--r-md);
135
+ }
136
+ .prose :global(hr) {
137
+ border: 0;
138
+ border-top: 1px solid var(--border);
139
+ }
140
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** Pre-rendered, trusted HTML. */
4
+ html?: string;
5
+ children?: Snippet;
6
+ /** Tighter vertical rhythm for chat bubbles. */
7
+ compact?: boolean;
8
+ class?: string;
9
+ style?: string;
10
+ };
11
+ declare const Prose: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type Prose = ReturnType<typeof Prose>;
13
+ export default Prose;