@sentropic/design-system-svelte 0.7.0 → 0.9.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 (81) hide show
  1. package/dist/Accordion.svelte +11 -3
  2. package/dist/Accordion.svelte.d.ts.map +1 -1
  3. package/dist/Alert.svelte +1 -1
  4. package/dist/AreaChart.svelte +414 -0
  5. package/dist/AreaChart.svelte.d.ts +18 -0
  6. package/dist/AreaChart.svelte.d.ts.map +1 -0
  7. package/dist/BarChart.svelte +383 -0
  8. package/dist/BarChart.svelte.d.ts +18 -0
  9. package/dist/BarChart.svelte.d.ts.map +1 -0
  10. package/dist/ChatComposer.svelte +238 -0
  11. package/dist/ChatComposer.svelte.d.ts +57 -0
  12. package/dist/ChatComposer.svelte.d.ts.map +1 -0
  13. package/dist/ChatMessage.svelte +271 -0
  14. package/dist/ChatMessage.svelte.d.ts +19 -0
  15. package/dist/ChatMessage.svelte.d.ts.map +1 -0
  16. package/dist/ChatThread.svelte +120 -0
  17. package/dist/ChatThread.svelte.d.ts +13 -0
  18. package/dist/ChatThread.svelte.d.ts.map +1 -0
  19. package/dist/Combobox.svelte +16 -2
  20. package/dist/Combobox.svelte.d.ts.map +1 -1
  21. package/dist/CopyButton.svelte +3 -7
  22. package/dist/CopyButton.svelte.d.ts.map +1 -1
  23. package/dist/Drawer.svelte +23 -3
  24. package/dist/Drawer.svelte.d.ts +1 -1
  25. package/dist/Drawer.svelte.d.ts.map +1 -1
  26. package/dist/Dropdown.svelte +38 -2
  27. package/dist/Dropdown.svelte.d.ts.map +1 -1
  28. package/dist/FileUploader.svelte +119 -4
  29. package/dist/FileUploader.svelte.d.ts +1 -0
  30. package/dist/FileUploader.svelte.d.ts.map +1 -1
  31. package/dist/IconButton.svelte +103 -0
  32. package/dist/IconButton.svelte.d.ts +15 -0
  33. package/dist/IconButton.svelte.d.ts.map +1 -0
  34. package/dist/InlineLoading.svelte +22 -14
  35. package/dist/InlineLoading.svelte.d.ts.map +1 -1
  36. package/dist/LineChart.svelte +397 -0
  37. package/dist/LineChart.svelte.d.ts +19 -0
  38. package/dist/LineChart.svelte.d.ts.map +1 -0
  39. package/dist/Menu.svelte +164 -24
  40. package/dist/Menu.svelte.d.ts +26 -4
  41. package/dist/Menu.svelte.d.ts.map +1 -1
  42. package/dist/MenuPopover.svelte +180 -0
  43. package/dist/MenuPopover.svelte.d.ts +17 -0
  44. package/dist/MenuPopover.svelte.d.ts.map +1 -0
  45. package/dist/MenuTriggerButton.svelte +50 -0
  46. package/dist/MenuTriggerButton.svelte.d.ts +16 -0
  47. package/dist/MenuTriggerButton.svelte.d.ts.map +1 -0
  48. package/dist/MessageActions.svelte +89 -0
  49. package/dist/MessageActions.svelte.d.ts +22 -0
  50. package/dist/MessageActions.svelte.d.ts.map +1 -0
  51. package/dist/MessageStatusBadge.svelte +52 -0
  52. package/dist/MessageStatusBadge.svelte.d.ts +12 -0
  53. package/dist/MessageStatusBadge.svelte.d.ts.map +1 -0
  54. package/dist/Modal.svelte +83 -3
  55. package/dist/Modal.svelte.d.ts.map +1 -1
  56. package/dist/MultiSelect.svelte +17 -3
  57. package/dist/MultiSelect.svelte.d.ts.map +1 -1
  58. package/dist/OverflowMenu.svelte +111 -24
  59. package/dist/OverflowMenu.svelte.d.ts +21 -2
  60. package/dist/OverflowMenu.svelte.d.ts.map +1 -1
  61. package/dist/PaginationNav.svelte +6 -21
  62. package/dist/PaginationNav.svelte.d.ts.map +1 -1
  63. package/dist/PasswordInput.svelte +3 -9
  64. package/dist/PasswordInput.svelte.d.ts.map +1 -1
  65. package/dist/ProgressIndicator.svelte +3 -19
  66. package/dist/ProgressIndicator.svelte.d.ts.map +1 -1
  67. package/dist/Search.svelte +3 -6
  68. package/dist/Search.svelte.d.ts.map +1 -1
  69. package/dist/Sparkline.svelte +123 -0
  70. package/dist/Sparkline.svelte.d.ts +15 -0
  71. package/dist/Sparkline.svelte.d.ts.map +1 -0
  72. package/dist/StreamingMessage.svelte +292 -0
  73. package/dist/StreamingMessage.svelte.d.ts +51 -0
  74. package/dist/StreamingMessage.svelte.d.ts.map +1 -0
  75. package/dist/Tag.svelte +2 -1
  76. package/dist/Tag.svelte.d.ts.map +1 -1
  77. package/dist/Toast.svelte +2 -2
  78. package/dist/index.d.ts +19 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +13 -0
  81. package/package.json +3 -2
@@ -0,0 +1,397 @@
1
+ <script lang="ts" module>
2
+ export type LineChartTone =
3
+ | "category1"
4
+ | "category2"
5
+ | "category3"
6
+ | "category4"
7
+ | "category5"
8
+ | "category6"
9
+ | "category7"
10
+ | "category8";
11
+
12
+ export type LineChartDatum = {
13
+ x: number | string;
14
+ y: number;
15
+ };
16
+ </script>
17
+
18
+ <script lang="ts">
19
+ type LineChartProps = {
20
+ data: LineChartDatum[];
21
+ width?: number;
22
+ height?: number;
23
+ tone?: LineChartTone;
24
+ smooth?: boolean;
25
+ area?: boolean;
26
+ label: string;
27
+ class?: string;
28
+ };
29
+
30
+ let {
31
+ data,
32
+ width = 480,
33
+ height = 240,
34
+ tone = "category1",
35
+ smooth = false,
36
+ area = false,
37
+ label,
38
+ class: className
39
+ }: LineChartProps = $props();
40
+
41
+ const MARGIN = { top: 12, right: 16, bottom: 32, left: 44 };
42
+
43
+ function niceTicks(min: number, max: number, target = 5): number[] {
44
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) {
45
+ const base = Number.isFinite(max) ? max : 0;
46
+ return [base];
47
+ }
48
+ const range = max - min;
49
+ const rough = range / Math.max(target - 1, 1);
50
+ const pow = Math.pow(10, Math.floor(Math.log10(rough)));
51
+ const norm = rough / pow;
52
+ let step: number;
53
+ if (norm < 1.5) step = 1 * pow;
54
+ else if (norm < 3) step = 2 * pow;
55
+ else if (norm < 7) step = 5 * pow;
56
+ else step = 10 * pow;
57
+ const start = Math.floor(min / step) * step;
58
+ const end = Math.ceil(max / step) * step;
59
+ const ticks: number[] = [];
60
+ for (let v = start; v <= end + step / 2; v += step) {
61
+ ticks.push(Number(v.toFixed(10)));
62
+ }
63
+ return ticks;
64
+ }
65
+
66
+ function scaleLinear(v: number, d0: number, d1: number, r0: number, r1: number) {
67
+ if (d1 === d0) return r0;
68
+ return r0 + ((v - d0) * (r1 - r0)) / (d1 - d0);
69
+ }
70
+
71
+ function formatTick(v: number): string {
72
+ if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;
73
+ if (Number.isInteger(v)) return String(v);
74
+ return v.toFixed(1);
75
+ }
76
+
77
+ function isNumeric(x: number | string): x is number {
78
+ return typeof x === "number" && Number.isFinite(x);
79
+ }
80
+
81
+ let hoveredIndex: number | null = $state(null);
82
+
83
+ const plotWidth = $derived(Math.max(width - MARGIN.left - MARGIN.right, 1));
84
+ const plotHeight = $derived(Math.max(height - MARGIN.top - MARGIN.bottom, 1));
85
+
86
+ const xDomain = $derived.by(() => {
87
+ if (data.length === 0) return { kind: "ordinal" as const, values: [] as (number | string)[] };
88
+ const allNumeric = data.every((d) => isNumeric(d.x));
89
+ if (allNumeric) {
90
+ const xs = data.map((d) => d.x as number);
91
+ return { kind: "numeric" as const, min: Math.min(...xs), max: Math.max(...xs) };
92
+ }
93
+ return { kind: "ordinal" as const, values: data.map((d) => d.x) };
94
+ });
95
+
96
+ const yTicks = $derived.by(() => {
97
+ const ys = data.map((d) => d.y);
98
+ if (ys.length === 0) return [0];
99
+ const minRaw = Math.min(...ys);
100
+ const maxRaw = Math.max(...ys);
101
+ const padded = (maxRaw - minRaw) * 0.08 || Math.max(Math.abs(maxRaw), 1) * 0.1;
102
+ return niceTicks(minRaw - padded, maxRaw + padded, 5);
103
+ });
104
+
105
+ const yDomain = $derived.by(() => {
106
+ if (yTicks.length === 0) return { min: 0, max: 1 };
107
+ return { min: yTicks[0], max: yTicks[yTicks.length - 1] };
108
+ });
109
+
110
+ const points = $derived.by(() => {
111
+ if (data.length === 0) return [];
112
+ return data.map((d, i) => {
113
+ let x: number;
114
+ if (xDomain.kind === "numeric") {
115
+ x = scaleLinear(d.x as number, xDomain.min, xDomain.max, 0, plotWidth);
116
+ } else {
117
+ // ordinal: distribute evenly
118
+ const denom = Math.max(data.length - 1, 1);
119
+ x = data.length === 1 ? plotWidth / 2 : (i / denom) * plotWidth;
120
+ }
121
+ const y = scaleLinear(d.y, yDomain.min, yDomain.max, plotHeight, 0);
122
+ return {
123
+ x: MARGIN.left + x,
124
+ y: MARGIN.top + y,
125
+ datum: d,
126
+ index: i
127
+ };
128
+ });
129
+ });
130
+
131
+ function buildLinearPath(pts: { x: number; y: number }[]): string {
132
+ return pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(" ");
133
+ }
134
+
135
+ function buildSmoothPath(pts: { x: number; y: number }[]): string {
136
+ if (pts.length < 2) return buildLinearPath(pts);
137
+ const t = 0.18;
138
+ let d = `M${pts[0].x.toFixed(2)},${pts[0].y.toFixed(2)}`;
139
+ for (let i = 0; i < pts.length - 1; i++) {
140
+ const p0 = pts[i - 1] ?? pts[i];
141
+ const p1 = pts[i];
142
+ const p2 = pts[i + 1];
143
+ const p3 = pts[i + 2] ?? p2;
144
+ const c1x = p1.x + (p2.x - p0.x) * t;
145
+ const c1y = p1.y + (p2.y - p0.y) * t;
146
+ const c2x = p2.x - (p3.x - p1.x) * t;
147
+ const c2y = p2.y - (p3.y - p1.y) * t;
148
+ d += ` C${c1x.toFixed(2)},${c1y.toFixed(2)} ${c2x.toFixed(2)},${c2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)}`;
149
+ }
150
+ return d;
151
+ }
152
+
153
+ const linePath = $derived(
154
+ points.length === 0 ? "" : smooth ? buildSmoothPath(points) : buildLinearPath(points)
155
+ );
156
+
157
+ const areaPath = $derived.by(() => {
158
+ if (!area || points.length === 0) return "";
159
+ const base = MARGIN.top + plotHeight;
160
+ const first = points[0];
161
+ const last = points[points.length - 1];
162
+ return `${linePath} L${last.x.toFixed(2)},${base.toFixed(2)} L${first.x.toFixed(2)},${base.toFixed(2)} Z`;
163
+ });
164
+
165
+ const gridLines = $derived(
166
+ yTicks.map((tick) => ({
167
+ value: tick,
168
+ y: MARGIN.top + scaleLinear(tick, yDomain.min, yDomain.max, plotHeight, 0)
169
+ }))
170
+ );
171
+
172
+ const xTickEntries = $derived.by(() => {
173
+ if (data.length === 0) return [];
174
+ if (xDomain.kind === "ordinal") {
175
+ return points.map((p, i) => ({
176
+ x: p.x,
177
+ label: String(data[i].x)
178
+ }));
179
+ }
180
+ const target = Math.min(5, data.length);
181
+ const stride = Math.max(1, Math.round((data.length - 1) / (target - 1 || 1)));
182
+ const entries: { x: number; label: string }[] = [];
183
+ for (let i = 0; i < data.length; i += stride) {
184
+ entries.push({ x: points[i].x, label: String(data[i].x) });
185
+ }
186
+ const lastIdx = data.length - 1;
187
+ if (entries[entries.length - 1]?.label !== String(data[lastIdx].x)) {
188
+ entries.push({ x: points[lastIdx].x, label: String(data[lastIdx].x) });
189
+ }
190
+ return entries;
191
+ });
192
+
193
+ function handleEnter(i: number) {
194
+ hoveredIndex = i;
195
+ }
196
+ function handleLeave() {
197
+ hoveredIndex = null;
198
+ }
199
+
200
+ const classes = () =>
201
+ ["st-lineChart", `st-lineChart--${tone}`, className].filter(Boolean).join(" ");
202
+ </script>
203
+
204
+ <div class={classes()} role="img" aria-label={label}>
205
+ <svg
206
+ viewBox="0 0 {width} {height}"
207
+ preserveAspectRatio="xMidYMid meet"
208
+ width="100%"
209
+ height="100%"
210
+ focusable="false"
211
+ aria-hidden="true"
212
+ >
213
+ <!-- gridlines + Y axis ticks -->
214
+ {#each gridLines as g (g.value)}
215
+ <line
216
+ class="st-lineChart__grid"
217
+ x1={MARGIN.left}
218
+ x2={width - MARGIN.right}
219
+ y1={g.y}
220
+ y2={g.y}
221
+ />
222
+ <text
223
+ class="st-lineChart__tickLabel"
224
+ x={MARGIN.left - 6}
225
+ y={g.y}
226
+ text-anchor="end"
227
+ dominant-baseline="middle"
228
+ >
229
+ {formatTick(g.value)}
230
+ </text>
231
+ {/each}
232
+
233
+ <!-- axes -->
234
+ <line
235
+ class="st-lineChart__axis"
236
+ x1={MARGIN.left}
237
+ x2={MARGIN.left}
238
+ y1={MARGIN.top}
239
+ y2={height - MARGIN.bottom}
240
+ />
241
+ <line
242
+ class="st-lineChart__axis"
243
+ x1={MARGIN.left}
244
+ x2={width - MARGIN.right}
245
+ y1={height - MARGIN.bottom}
246
+ y2={height - MARGIN.bottom}
247
+ />
248
+
249
+ <!-- X tick labels -->
250
+ {#each xTickEntries as tick, i (i)}
251
+ <text
252
+ class="st-lineChart__tickLabel"
253
+ x={tick.x}
254
+ y={height - MARGIN.bottom + 16}
255
+ text-anchor="middle"
256
+ >
257
+ {tick.label}
258
+ </text>
259
+ {/each}
260
+
261
+ {#if area && areaPath}
262
+ <path class="st-lineChart__area" d={areaPath} />
263
+ {/if}
264
+ {#if linePath}
265
+ <path
266
+ class="st-lineChart__line"
267
+ d={linePath}
268
+ fill="none"
269
+ stroke-width="2"
270
+ stroke-linecap="round"
271
+ stroke-linejoin="round"
272
+ />
273
+ {/if}
274
+
275
+ {#each points as p (p.index)}
276
+ <circle
277
+ class="st-lineChart__dot"
278
+ cx={p.x}
279
+ cy={p.y}
280
+ r="4"
281
+ tabindex="0"
282
+ role="img"
283
+ aria-label="{p.datum.x}: {p.datum.y}"
284
+ onmouseenter={() => handleEnter(p.index)}
285
+ onmouseleave={handleLeave}
286
+ onfocus={() => handleEnter(p.index)}
287
+ onblur={handleLeave}
288
+ />
289
+ {/each}
290
+ </svg>
291
+
292
+ {#if hoveredIndex !== null && points[hoveredIndex]}
293
+ {@const p = points[hoveredIndex]}
294
+ <div
295
+ class="st-lineChart__tooltip"
296
+ role="presentation"
297
+ style="left: {(p.x / width) * 100}%; top: {(p.y / height) * 100}%"
298
+ >
299
+ <span class="st-lineChart__tooltipLabel">{p.datum.x}</span>
300
+ <span class="st-lineChart__tooltipValue">{p.datum.y}</span>
301
+ </div>
302
+ {/if}
303
+ </div>
304
+
305
+ <style>
306
+ .st-lineChart {
307
+ color: var(--st-semantic-data-category1);
308
+ display: block;
309
+ font-family: inherit;
310
+ position: relative;
311
+ width: 100%;
312
+ }
313
+
314
+ .st-lineChart--category1 { color: var(--st-semantic-data-category1); }
315
+ .st-lineChart--category2 { color: var(--st-semantic-data-category2); }
316
+ .st-lineChart--category3 { color: var(--st-semantic-data-category3); }
317
+ .st-lineChart--category4 { color: var(--st-semantic-data-category4); }
318
+ .st-lineChart--category5 { color: var(--st-semantic-data-category5); }
319
+ .st-lineChart--category6 { color: var(--st-semantic-data-category6); }
320
+ .st-lineChart--category7 { color: var(--st-semantic-data-category7); }
321
+ .st-lineChart--category8 { color: var(--st-semantic-data-category8); }
322
+
323
+ .st-lineChart svg {
324
+ display: block;
325
+ overflow: visible;
326
+ }
327
+
328
+ .st-lineChart__grid {
329
+ stroke: var(--st-component-lineChart-gridStroke, var(--st-semantic-border-subtle));
330
+ stroke-dasharray: 2 3;
331
+ stroke-width: 1;
332
+ opacity: 0.7;
333
+ }
334
+
335
+ .st-lineChart__axis {
336
+ stroke: var(--st-component-lineChart-axisStroke, var(--st-semantic-border-subtle));
337
+ stroke-width: 1;
338
+ }
339
+
340
+ .st-lineChart__tickLabel {
341
+ fill: var(--st-component-lineChart-labelColor, var(--st-semantic-text-secondary));
342
+ font-size: 0.6875rem;
343
+ }
344
+
345
+ .st-lineChart__line {
346
+ stroke: currentColor;
347
+ }
348
+
349
+ .st-lineChart__area {
350
+ fill: currentColor;
351
+ opacity: 0.18;
352
+ stroke: none;
353
+ }
354
+
355
+ .st-lineChart__dot {
356
+ fill: currentColor;
357
+ stroke: var(--st-semantic-surface-default);
358
+ stroke-width: 1.5;
359
+ cursor: pointer;
360
+ transition: r 120ms ease;
361
+ }
362
+
363
+ .st-lineChart__dot:hover,
364
+ .st-lineChart__dot:focus-visible {
365
+ r: 5.5;
366
+ }
367
+
368
+ .st-lineChart__dot:focus-visible {
369
+ outline: 2px solid var(--st-semantic-border-interactive);
370
+ outline-offset: 1px;
371
+ }
372
+
373
+ .st-lineChart__tooltip {
374
+ background: var(--st-component-lineChart-tooltipBackground, var(--st-semantic-surface-inverse));
375
+ border-radius: var(--st-radius-sm, 0.25rem);
376
+ color: var(--st-component-lineChart-tooltipText, var(--st-semantic-text-inverse));
377
+ display: inline-flex;
378
+ flex-direction: column;
379
+ font-size: 0.75rem;
380
+ gap: 0.125rem;
381
+ line-height: 1.2;
382
+ padding: 0.375rem 0.5rem;
383
+ pointer-events: none;
384
+ position: absolute;
385
+ transform: translate(-50%, calc(-100% - 8px));
386
+ white-space: nowrap;
387
+ z-index: 1;
388
+ }
389
+
390
+ .st-lineChart__tooltipLabel {
391
+ font-weight: 600;
392
+ }
393
+
394
+ .st-lineChart__tooltipValue {
395
+ opacity: 0.85;
396
+ }
397
+ </style>
@@ -0,0 +1,19 @@
1
+ export type LineChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
2
+ export type LineChartDatum = {
3
+ x: number | string;
4
+ y: number;
5
+ };
6
+ type LineChartProps = {
7
+ data: LineChartDatum[];
8
+ width?: number;
9
+ height?: number;
10
+ tone?: LineChartTone;
11
+ smooth?: boolean;
12
+ area?: boolean;
13
+ label: string;
14
+ class?: string;
15
+ };
16
+ declare const LineChart: import("svelte").Component<LineChartProps, {}, "">;
17
+ type LineChart = ReturnType<typeof LineChart>;
18
+ export default LineChart;
19
+ //# sourceMappingURL=LineChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LineChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/LineChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkOJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
package/dist/Menu.svelte CHANGED
@@ -1,40 +1,133 @@
1
- <script lang="ts">
2
- import type { HTMLAttributes } from "svelte/elements";
1
+ <script lang="ts" module>
2
+ import type { Component } from "svelte";
3
+
4
+ export type MenuIconProps = {
5
+ size?: number | string;
6
+ strokeWidth?: number | string;
7
+ color?: string;
8
+ class?: string;
9
+ } & Record<`data-${string}`, unknown>;
10
+
11
+ export type MenuIcon = Component<MenuIconProps>;
3
12
 
4
- export type MenuItem = {
13
+ export interface MenuActionItem {
14
+ kind?: "item";
5
15
  label: string;
6
16
  value: string;
7
17
  disabled?: boolean;
8
- };
18
+ danger?: boolean;
19
+ icon?: MenuIcon;
20
+ }
21
+
22
+ export interface MenuDividerItem {
23
+ kind: "divider";
24
+ }
25
+
26
+ export interface MenuGroupItem {
27
+ kind: "group";
28
+ label: string;
29
+ }
30
+
31
+ export type MenuItem = MenuActionItem | MenuDividerItem | MenuGroupItem;
32
+
33
+ let groupIdCounter = 0;
34
+ function nextGroupId(): string {
35
+ groupIdCounter += 1;
36
+ return `st-menu-group-${groupIdCounter}`;
37
+ }
38
+ </script>
39
+
40
+ <script lang="ts">
41
+ import type { HTMLAttributes } from "svelte/elements";
9
42
 
10
43
  type MenuProps = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
11
44
  label: string;
12
45
  items: MenuItem[];
46
+ open?: boolean;
47
+ dismissOnSelect?: boolean;
13
48
  class?: string;
49
+ dense?: boolean;
14
50
  onselect?: (value: string) => void;
15
51
  };
16
52
 
17
- let { label, items, class: className, onselect, ...rest }: MenuProps = $props();
18
- const classes = () => ["st-menu", className].filter(Boolean).join(" ");
19
- const selectItem = (item: MenuItem) => {
20
- if (!item.disabled) onselect?.(item.value);
21
- };
53
+ let {
54
+ label,
55
+ items,
56
+ open = $bindable(true),
57
+ dismissOnSelect = false,
58
+ class: className,
59
+ dense = false,
60
+ onselect,
61
+ ...rest
62
+ }: MenuProps = $props();
63
+
64
+ let host: HTMLDivElement | undefined = $state();
65
+
66
+ const classes = () =>
67
+ ["st-menu", dense ? "st-menu--dense" : null, className].filter(Boolean).join(" ");
68
+
69
+ function selectItem(item: MenuActionItem) {
70
+ if (item.disabled) return;
71
+ onselect?.(item.value);
72
+ if (dismissOnSelect) open = false;
73
+ }
74
+
75
+ function isAction(item: MenuItem): item is MenuActionItem {
76
+ return item.kind === undefined || item.kind === "item";
77
+ }
78
+
79
+ function onWindowKeydown(event: KeyboardEvent) {
80
+ if (event.key === "Escape" && open && dismissOnSelect) {
81
+ event.preventDefault();
82
+ open = false;
83
+ }
84
+ }
85
+
86
+ function onWindowPointerDown(event: MouseEvent) {
87
+ if (!open || !dismissOnSelect) return;
88
+ const target = event.target as Node | null;
89
+ if (host && target && !host.contains(target)) open = false;
90
+ }
22
91
  </script>
23
92
 
24
- <div {...rest} class={classes()} role="menu" aria-label={label}>
25
- {#each items as item (item.value)}
26
- <button
27
- class="st-menu__item"
28
- type="button"
29
- role="menuitem"
30
- aria-disabled={item.disabled ? "true" : undefined}
31
- disabled={item.disabled}
32
- onclick={() => selectItem(item)}
33
- >
34
- {item.label}
35
- </button>
36
- {/each}
37
- </div>
93
+ <svelte:window onkeydown={onWindowKeydown} onpointerdown={onWindowPointerDown} />
94
+
95
+ {#if open}
96
+ <div {...rest} bind:this={host} class={classes()} role="menu" aria-label={label}>
97
+ {#each items as item, index (index)}
98
+ {#if isAction(item)}
99
+ {@const Icon = item.icon}
100
+ <button
101
+ class="st-menu__item"
102
+ class:st-menu__item--danger={item.danger}
103
+ type="button"
104
+ role="menuitem"
105
+ aria-disabled={item.disabled ? "true" : undefined}
106
+ disabled={item.disabled}
107
+ onclick={() => selectItem(item)}
108
+ >
109
+ {#if Icon}
110
+ <span class="st-menu__itemIcon" aria-hidden="true">
111
+ <Icon size={16} strokeWidth={2} />
112
+ </span>
113
+ {/if}
114
+ <span class="st-menu__itemLabel">{item.label}</span>
115
+ </button>
116
+ {:else if item.kind === "divider"}
117
+ <div class="st-menu__divider" role="separator" aria-hidden="true"></div>
118
+ {:else}
119
+ {@const groupId = nextGroupId()}
120
+ <div
121
+ class="st-menu__group"
122
+ id={groupId}
123
+ role="presentation"
124
+ >
125
+ {item.label}
126
+ </div>
127
+ {/if}
128
+ {/each}
129
+ </div>
130
+ {/if}
38
131
 
39
132
  <style>
40
133
  .st-menu {
@@ -44,19 +137,28 @@
44
137
  box-shadow: var(--st-component-menu-shadow, 0 8px 24px rgb(15 23 42 / 0.14));
45
138
  color: var(--st-component-menu-text, var(--st-semantic-text-primary));
46
139
  display: grid;
47
- min-width: 12rem;
140
+ min-width: var(--st-component-menu-minWidth, 12rem);
141
+ max-width: var(--st-component-menu-maxWidth, 18rem);
48
142
  padding: var(--st-spacing-1, 0.25rem);
49
143
  }
50
144
 
51
145
  .st-menu__item {
146
+ align-items: center;
52
147
  background: transparent;
53
148
  border: 0;
54
149
  border-radius: var(--st-radius-small, 0.375rem);
55
150
  color: inherit;
56
151
  cursor: pointer;
152
+ display: flex;
57
153
  font: inherit;
154
+ gap: var(--st-spacing-2, 0.5rem);
58
155
  padding: var(--st-spacing-2, 0.5rem) var(--st-spacing-3, 0.75rem);
59
156
  text-align: left;
157
+ width: 100%;
158
+ }
159
+
160
+ .st-menu--dense .st-menu__item {
161
+ padding: 0.3rem 0.6rem;
60
162
  }
61
163
 
62
164
  .st-menu__item:hover:not(:disabled),
@@ -69,4 +171,42 @@
69
171
  color: var(--st-component-menu-disabledText, var(--st-semantic-text-muted));
70
172
  cursor: not-allowed;
71
173
  }
174
+
175
+ .st-menu__item--danger {
176
+ color: var(--st-component-menu-dangerText, var(--st-semantic-feedback-error, #b91c1c));
177
+ }
178
+
179
+ .st-menu__item--danger:hover:not(:disabled),
180
+ .st-menu__item--danger:focus-visible {
181
+ background: var(--st-component-menu-dangerHoverBackground, rgba(185, 28, 28, 0.08));
182
+ }
183
+
184
+ .st-menu__itemIcon {
185
+ align-items: center;
186
+ display: inline-flex;
187
+ flex: 0 0 auto;
188
+ justify-content: center;
189
+ }
190
+
191
+ .st-menu__itemLabel {
192
+ flex: 1 1 auto;
193
+ overflow: hidden;
194
+ text-overflow: ellipsis;
195
+ white-space: nowrap;
196
+ }
197
+
198
+ .st-menu__divider {
199
+ background: var(--st-component-menu-border, var(--st-semantic-border-subtle));
200
+ height: 1px;
201
+ margin: 0.25rem 0;
202
+ }
203
+
204
+ .st-menu__group {
205
+ color: var(--st-component-menu-groupText, var(--st-semantic-text-muted));
206
+ font-size: 0.72rem;
207
+ font-weight: 650;
208
+ letter-spacing: 0.04em;
209
+ padding: 0.45rem 0.75rem 0.25rem;
210
+ text-transform: uppercase;
211
+ }
72
212
  </style>
@@ -1,16 +1,38 @@
1
- import type { HTMLAttributes } from "svelte/elements";
2
- export type MenuItem = {
1
+ import type { Component } from "svelte";
2
+ export type MenuIconProps = {
3
+ size?: number | string;
4
+ strokeWidth?: number | string;
5
+ color?: string;
6
+ class?: string;
7
+ } & Record<`data-${string}`, unknown>;
8
+ export type MenuIcon = Component<MenuIconProps>;
9
+ export interface MenuActionItem {
10
+ kind?: "item";
3
11
  label: string;
4
12
  value: string;
5
13
  disabled?: boolean;
6
- };
14
+ danger?: boolean;
15
+ icon?: MenuIcon;
16
+ }
17
+ export interface MenuDividerItem {
18
+ kind: "divider";
19
+ }
20
+ export interface MenuGroupItem {
21
+ kind: "group";
22
+ label: string;
23
+ }
24
+ export type MenuItem = MenuActionItem | MenuDividerItem | MenuGroupItem;
25
+ import type { HTMLAttributes } from "svelte/elements";
7
26
  type MenuProps = Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
8
27
  label: string;
9
28
  items: MenuItem[];
29
+ open?: boolean;
30
+ dismissOnSelect?: boolean;
10
31
  class?: string;
32
+ dense?: boolean;
11
33
  onselect?: (value: string) => void;
12
34
  };
13
- declare const Menu: import("svelte").Component<MenuProps, {}, "">;
35
+ declare const Menu: Component<MenuProps, {}, "open">;
14
36
  type Menu = ReturnType<typeof Menu>;
15
37
  export default Menu;
16
38
  //# sourceMappingURL=Menu.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Menu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAyBJ,QAAA,MAAM,IAAI,+CAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Menu.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Menu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAAC,QAAQ,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;AAEtC,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAC,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAS1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AA8EJ,QAAA,MAAM,IAAI,kCAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}