@runwisp/ui 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 (52) hide show
  1. package/package.json +4 -12
  2. package/src/lib/components/Alert.svelte +4 -4
  3. package/src/lib/components/Avatar.svelte +8 -8
  4. package/src/lib/components/Badge.svelte +23 -23
  5. package/src/lib/components/CronInput.svelte +1 -1
  6. package/src/lib/components/Dropdown.svelte +16 -3
  7. package/src/lib/components/DurationInput.svelte +4 -2
  8. package/src/lib/components/EmptyState.svelte +1 -1
  9. package/src/lib/components/FormField.svelte +4 -2
  10. package/src/lib/components/Input.svelte +4 -2
  11. package/src/lib/components/LogConsole.svelte +22 -29
  12. package/src/lib/components/Progress.svelte +4 -4
  13. package/src/lib/components/RadioGroup.svelte +3 -1
  14. package/src/lib/components/Select.svelte +1 -1
  15. package/src/lib/components/Sparkline.svelte +11 -3
  16. package/src/lib/components/TaskCard.svelte +4 -4
  17. package/src/lib/components/Textarea.svelte +2 -2
  18. package/src/lib/components/ToastContainer.svelte +4 -4
  19. package/src/lib/components/Tooltip.svelte +11 -2
  20. package/src/lib/components/dashboard/RunDetailPanel.svelte +34 -18
  21. package/src/lib/components/dashboard/RunsList.svelte +19 -2
  22. package/src/lib/components/dashboard/cron-format.test.ts +67 -0
  23. package/src/lib/components/dashboard/cron-format.ts +71 -0
  24. package/src/lib/components/dashboard/run-helpers.test.ts +25 -1
  25. package/src/lib/components/dashboard/run-helpers.ts +15 -0
  26. package/src/lib/components/dashboard/status-config.test.ts +18 -0
  27. package/src/lib/components/dashboard/status-config.ts +41 -0
  28. package/src/lib/index.ts +2 -5
  29. package/src/lib/log-console/ansi.test.ts +50 -0
  30. package/src/lib/log-console/ansi.ts +39 -0
  31. package/src/lib/theme-tokens.css +33 -0
  32. package/src/lib/theme.css +6 -0
  33. package/src/lib/utils/error.test.ts +34 -0
  34. package/src/lib/utils/format.test.ts +37 -1
  35. package/src/lib/utils/format.ts +8 -6
  36. package/src/lib/utils/id.test.ts +23 -0
  37. package/src/stories/Avatar.stories.svelte +1 -1
  38. package/src/stories/Breadcrumb.stories.svelte +3 -3
  39. package/src/stories/Card.stories.svelte +21 -19
  40. package/src/stories/Checkbox.stories.svelte +2 -2
  41. package/src/stories/DataGrid.stories.svelte +1 -1
  42. package/src/stories/Dropdown.stories.svelte +3 -3
  43. package/src/stories/EmptyState.stories.svelte +1 -1
  44. package/src/stories/LogConsole.stories.svelte +75 -0
  45. package/src/stories/LogConsoleDemo.svelte +60 -0
  46. package/src/stories/Logo.stories.svelte +9 -9
  47. package/src/stories/Modal.stories.svelte +12 -10
  48. package/src/stories/Progress.stories.svelte +6 -6
  49. package/src/stories/Tabs.stories.svelte +16 -12
  50. package/src/stories/Toggle.stories.svelte +2 -2
  51. package/src/stories/Tooltip.stories.svelte +5 -5
  52. package/src/lib/utils/toast.ts +0 -60
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@runwisp/ui",
3
3
  "description": "Svelte 5 UI component library for RunWisp dashboards and admin interfaces.",
4
- "version": "0.7.0",
4
+ "version": "0.9.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,14 +15,6 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "scripts": {
19
- "dev": "storybook dev -p 6006 --no-open",
20
- "build": "echo '@runwisp/ui: no build step (raw source)'",
21
- "check": "bun run check:types && bun run check:lint",
22
- "check:types": "svelte-check --tsconfig ./tsconfig.json",
23
- "check:lint": "prettier --check . && eslint .",
24
- "format": "prettier --write ."
25
- },
26
18
  "files": [
27
19
  "src"
28
20
  ],
@@ -58,8 +50,8 @@
58
50
  "@chromatic-com/storybook": "^4.1.3",
59
51
  "@eslint/compat": "^1.4.0",
60
52
  "@eslint/js": "^9.39.1",
61
- "@runwisp/eslint-config": "0.7.0",
62
- "@runwisp/typescript-config": "0.7.0",
53
+ "@runwisp/eslint-config": "0.9.0",
54
+ "@runwisp/typescript-config": "0.9.0",
63
55
  "@storybook/addon-a11y": "^10.1.9",
64
56
  "@storybook/addon-docs": "^10.1.9",
65
57
  "@storybook/addon-svelte-csf": "^5.0.10",
@@ -107,7 +99,7 @@
107
99
  "@floating-ui/dom": "^1.7.4",
108
100
  "@fontsource/tasa-orbiter": "^5.2.2",
109
101
  "@lucide/svelte": "^0.561.0",
110
- "@runwisp/common": "0.7.0",
102
+ "@runwisp/common": "0.9.0",
111
103
  "@tanstack/svelte-virtual": "^3.13.25",
112
104
  "ansi-to-html": "^0.7.2",
113
105
  "cron-parser": "^5.4.0",
@@ -35,25 +35,25 @@
35
35
  > = {
36
36
  info: {
37
37
  bg: "bg-info-soft",
38
- border: "border-aurora-200",
38
+ border: "border-info-soft-border",
39
39
  icon: Info,
40
40
  iconColor: "text-info",
41
41
  },
42
42
  success: {
43
43
  bg: "bg-success-soft",
44
- border: "border-success-200",
44
+ border: "border-success-soft-border",
45
45
  icon: CircleCheckBig,
46
46
  iconColor: "text-success-surface",
47
47
  },
48
48
  warning: {
49
49
  bg: "bg-warning-soft",
50
- border: "border-warning-200",
50
+ border: "border-warning-soft-border",
51
51
  icon: TriangleAlert,
52
52
  iconColor: "text-warning-surface",
53
53
  },
54
54
  danger: {
55
55
  bg: "bg-danger-soft",
56
- border: "border-danger-200",
56
+ border: "border-danger-soft-border",
57
57
  icon: CircleX,
58
58
  iconColor: "text-danger-surface",
59
59
  },
@@ -30,10 +30,10 @@
30
30
  };
31
31
 
32
32
  const statusClasses: Record<string, string> = {
33
- online: "bg-success-500",
33
+ online: "bg-success-surface",
34
34
  offline: "bg-on-surface-faint",
35
- busy: "bg-danger-500",
36
- away: "bg-warning-500",
35
+ busy: "bg-danger-surface",
36
+ away: "bg-warning-surface",
37
37
  };
38
38
 
39
39
  const statusSizes: Record<string, string> = {
@@ -46,11 +46,11 @@
46
46
 
47
47
  // Generate a consistent color based on name
48
48
  const colors = [
49
- "bg-wisp-500",
50
- "bg-aurora-500",
51
- "bg-success-500",
52
- "bg-warning-500",
53
- "bg-danger-500",
49
+ "bg-primary",
50
+ "bg-info",
51
+ "bg-success-surface",
52
+ "bg-warning-surface",
53
+ "bg-danger-surface",
54
54
  ];
55
55
 
56
56
  const colorIndex = $derived(
@@ -35,28 +35,28 @@
35
35
 
36
36
  const variantClasses: Record<BadgeVariant, { solid: string; outline: string }> = {
37
37
  default: {
38
- solid: "bg-mist-100 text-mist-700",
39
- outline: "bg-transparent border border-mist-300 text-mist-700",
38
+ solid: "bg-surface-sunken text-on-surface-muted",
39
+ outline: "bg-transparent border border-outline-hover text-on-surface-muted",
40
40
  },
41
41
  primary: {
42
- solid: "bg-wisp-100 text-wisp-700",
43
- outline: "bg-transparent border border-wisp-300 text-wisp-700",
42
+ solid: "bg-primary-soft text-primary-soft-text",
43
+ outline: "bg-transparent border border-wisp-300 text-primary-soft-text",
44
44
  },
45
45
  success: {
46
- solid: "bg-success-100 text-success-700",
47
- outline: "bg-transparent border border-success-300 text-success-700",
46
+ solid: "bg-success-soft text-success-soft-text",
47
+ outline: "bg-transparent border border-success-300 text-success-soft-text",
48
48
  },
49
49
  warning: {
50
- solid: "bg-warning-100 text-warning-700",
51
- outline: "bg-transparent border border-warning-400 text-warning-700",
50
+ solid: "bg-warning-soft text-warning-soft-text",
51
+ outline: "bg-transparent border border-warning-400 text-warning-soft-text",
52
52
  },
53
53
  danger: {
54
- solid: "bg-danger-100 text-danger-700",
55
- outline: "bg-transparent border border-danger-300 text-danger-700",
54
+ solid: "bg-danger-soft text-danger-soft-text",
55
+ outline: "bg-transparent border border-danger-300 text-danger-soft-text",
56
56
  },
57
57
  info: {
58
- solid: "bg-aurora-100 text-aurora-700",
59
- outline: "bg-transparent border border-aurora-300 text-aurora-700",
58
+ solid: "bg-info-soft text-info-soft-text",
59
+ outline: "bg-transparent border border-aurora-300 text-info-soft-text",
60
60
  },
61
61
  };
62
62
 
@@ -68,20 +68,20 @@
68
68
 
69
69
  const dotColors: Record<BadgeVariant, string> = {
70
70
  default: "bg-mist-500",
71
- primary: "bg-wisp-500",
72
- success: "bg-success-500",
73
- warning: "bg-warning-500",
74
- danger: "bg-danger-500",
75
- info: "bg-aurora-500",
71
+ primary: "bg-primary",
72
+ success: "bg-success-surface",
73
+ warning: "bg-warning-surface",
74
+ danger: "bg-danger-surface",
75
+ info: "bg-info",
76
76
  };
77
77
 
78
78
  const indicatorTextColors: Record<BadgeVariant, string> = {
79
- default: "text-mist-500",
80
- primary: "text-wisp-500",
81
- success: "text-success-500",
82
- warning: "text-warning-500",
83
- danger: "text-danger-500",
84
- info: "text-aurora-500",
79
+ default: "text-on-surface-muted",
80
+ primary: "text-primary",
81
+ success: "text-success-soft-text",
82
+ warning: "text-warning-soft-text",
83
+ danger: "text-danger-soft-text",
84
+ info: "text-info",
85
85
  };
86
86
 
87
87
  const spinnerSizeClasses: Record<BadgeSize, string> = {
@@ -88,7 +88,7 @@
88
88
  <!-- Live Feedback -->
89
89
  <div
90
90
  class="flex items-start gap-3 rounded-md border p-3 transition-colors duration-200
91
- {isError ? 'border-danger-200 bg-danger-soft' : 'border-outline bg-surface-sunken'}"
91
+ {isError ? 'border-danger-soft-border bg-danger-soft' : 'border-outline bg-surface-sunken'}"
92
92
  >
93
93
  <Info size={18} class="mt-0.5 {isError ? 'text-danger-surface' : 'text-primary'}" />
94
94
  <div>
@@ -5,7 +5,7 @@
5
5
  import type { Snippet } from "svelte";
6
6
  import type { Component } from "svelte";
7
7
  import { tick, onDestroy } from "svelte";
8
- import { EllipsisVertical } from "@lucide/svelte";
8
+ import { EllipsisVertical, Check } from "@lucide/svelte";
9
9
  import { scale } from "svelte/transition";
10
10
  import { quintOut } from "svelte/easing";
11
11
  import { computePosition, autoUpdate, flip, shift, offset } from "@floating-ui/dom";
@@ -18,6 +18,7 @@
18
18
  danger?: boolean;
19
19
  disabled?: boolean;
20
20
  divider?: boolean;
21
+ selected?: boolean;
21
22
  title?: string;
22
23
  }
23
24
 
@@ -26,10 +27,18 @@
26
27
  trigger?: Snippet;
27
28
  children?: Snippet;
28
29
  align?: "left" | "right";
30
+ triggerLabel?: string;
29
31
  class?: string;
30
32
  }
31
33
 
32
- let { items = [], trigger, children, align = "right", class: className = "" }: Props = $props();
34
+ let {
35
+ items = [],
36
+ trigger,
37
+ children,
38
+ align = "right",
39
+ triggerLabel = "Options",
40
+ class: className = "",
41
+ }: Props = $props();
33
42
 
34
43
  let open = $state(false);
35
44
  let triggerEl = $state<HTMLElement | null>(null);
@@ -116,7 +125,8 @@
116
125
  toggle(!open);
117
126
  }}
118
127
  bind:this={triggerEl}
119
- aria-label="Options"
128
+ aria-label={triggerLabel}
129
+ aria-expanded={open}
120
130
  class="rounded-lg p-2 text-on-surface-muted transition-colors hover:bg-surface-sunken hover:text-on-surface focus:ring-2 focus:ring-outline focus:outline-none active:bg-surface-sunken"
121
131
  >
122
132
  {#if trigger}
@@ -186,6 +196,9 @@
186
196
  <Icon size={16} class="opacity-70" />
187
197
  {/if}
188
198
  {item.label}
199
+ {#if item.selected}
200
+ <Check size={16} class="ml-auto text-primary" />
201
+ {/if}
189
202
  </button>
190
203
  {/if}
191
204
  {/each}
@@ -147,7 +147,7 @@
147
147
 
148
148
  {#if error}
149
149
  <div
150
- class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-500"
150
+ class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-soft-text"
151
151
  >
152
152
  <CircleAlert size={18} />
153
153
  </div>
@@ -161,7 +161,9 @@
161
161
  {parsedHuman}
162
162
  </p>
163
163
  {:else if error}
164
- <p class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-600 duration-200">
164
+ <p
165
+ class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
166
+ >
165
167
  {error}
166
168
  </p>
167
169
  {:else if hint}
@@ -8,7 +8,7 @@
8
8
 
9
9
  interface Props {
10
10
  title?: string;
11
- description?: string;
11
+ description?: string | undefined;
12
12
  icon?: Component;
13
13
  iconSize?: number;
14
14
  actions?: Snippet;
@@ -32,7 +32,7 @@
32
32
  <label for={generatedId} class="block text-sm font-medium text-on-surface-muted">
33
33
  {label}
34
34
  {#if required}
35
- <span class="text-danger-500">*</span>
35
+ <span class="text-danger-soft-text">*</span>
36
36
  {/if}
37
37
  </label>
38
38
  {/if}
@@ -44,7 +44,9 @@
44
44
  {@render children()}
45
45
 
46
46
  {#if error}
47
- <p class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-600 duration-200">
47
+ <p
48
+ class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
49
+ >
48
50
  {error}
49
51
  </p>
50
52
  {:else if hint}
@@ -91,7 +91,7 @@
91
91
 
92
92
  {#if error}
93
93
  <div
94
- class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-500"
94
+ class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-danger-soft-text"
95
95
  >
96
96
  <CircleAlert size={18} />
97
97
  </div>
@@ -105,7 +105,9 @@
105
105
  </div>
106
106
 
107
107
  {#if error}
108
- <p class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-600 duration-200">
108
+ <p
109
+ class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
110
+ >
109
111
  {error}
110
112
  </p>
111
113
  {:else if hint}
@@ -3,19 +3,12 @@
3
3
 
4
4
  <script lang="ts">
5
5
  import { untrack } from "svelte";
6
- import Convert from "ansi-to-html";
6
+ import { ansiLineToHtml } from "../log-console/ansi.js";
7
7
  import { LogCache } from "../log-console/LogCache.svelte.js";
8
8
  import { LogFetcher } from "../log-console/LogFetcher.svelte.js";
9
9
  import type { FetchLogsFn, LogEvent } from "../log-console/types.js";
10
10
  import { formatBytes } from "../utils/format.js";
11
11
 
12
- // Convert ANSI escape sequences in a single log line to safe HTML spans.
13
- // A fresh converter per call ensures no colour state leaks between
14
- // independently virtualised lines.
15
- function ansiLineToHtml(text: string): string {
16
- return new Convert({ escapeXML: true }).toHtml(text);
17
- }
18
-
19
12
  interface Props {
20
13
  fetchLogs?: FetchLogsFn;
21
14
  chunkSize?: number;
@@ -256,13 +249,13 @@
256
249
  </script>
257
250
 
258
251
  <div
259
- class="log-console relative flex h-full w-full flex-col overflow-hidden bg-slate-950 font-mono text-sm {className}"
252
+ class="log-console relative flex h-full w-full flex-col overflow-hidden bg-mist-950 font-mono text-sm {className}"
260
253
  >
261
254
  <div bind:this={containerEl} class="flex-1 overflow-auto" onscroll={onScroll}>
262
255
  <div class="relative" style="height: {totalHeight}px; min-height: 100%;">
263
256
  {#if cache.firstAvailableLine > 0}
264
257
  <div
265
- class="absolute right-0 left-0 flex items-center bg-amber-950/60 text-amber-400"
258
+ class="absolute right-0 left-0 flex items-center bg-warning-700/20 text-warning-400"
266
259
  style="top: 0px; height: {lineHeight}px;"
267
260
  >
268
261
  <div
@@ -280,24 +273,24 @@
280
273
 
281
274
  {#each renderedLines as line (line.num)}
282
275
  <div
283
- class="log-line absolute right-0 left-0 flex items-center hover:bg-slate-900/50 {flashLine ===
276
+ class="log-line absolute right-0 left-0 flex items-center hover:bg-mist-900/50 {flashLine ===
284
277
  line.num
285
278
  ? 'log-line--flash'
286
279
  : ''}"
287
280
  style="top: {lineTop(line.num)}px; height: {lineHeight}px;"
288
281
  >
289
282
  <div
290
- class="flex-shrink-0 pr-3 text-right text-slate-600 select-none"
283
+ class="flex-shrink-0 pr-3 text-right text-mist-500 select-none"
291
284
  style="width: {gutterWidth}px;"
292
285
  >
293
286
  {line.num + 1}
294
287
  </div>
295
- <div class="flex-1 truncate pr-4 text-slate-300">
288
+ <div class="flex-1 truncate pr-4 text-mist-200">
296
289
  {#if line.text !== undefined}
297
290
  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
298
291
  <span class="whitespace-pre">{@html ansiLineToHtml(line.text)}</span>
299
292
  {:else}
300
- <span class="text-slate-700 italic">Loading...</span>
293
+ <span class="text-mist-600 italic">Loading...</span>
301
294
  {/if}
302
295
  </div>
303
296
  </div>
@@ -312,7 +305,7 @@
312
305
  class="flex-shrink-0 pr-3 text-right select-none"
313
306
  style="width: {gutterWidth}px;"
314
307
  ></div>
315
- <div class="flex items-center gap-1 text-slate-500">
308
+ <div class="flex items-center gap-1 text-mist-400">
316
309
  <span class="dot" style="animation-delay: 0ms;"></span>
317
310
  <span class="dot" style="animation-delay: 150ms;"></span>
318
311
  <span class="dot" style="animation-delay: 300ms;"></span>
@@ -329,7 +322,7 @@
329
322
  )}px; height: {lineHeight}px;"
330
323
  >
331
324
  <div
332
- class="flex-shrink-0 pr-3 text-right text-slate-800 opacity-0 select-none"
325
+ class="flex-shrink-0 pr-3 text-right text-mist-700 opacity-0 select-none"
333
326
  style="width: {gutterWidth}px;"
334
327
  >
335
328
  ~
@@ -339,16 +332,16 @@
339
332
  {/if}
340
333
 
341
334
  {#if cache.totalLines === 0 && !fetcher?.isFetching}
342
- <div class="absolute inset-0 flex items-center justify-center text-slate-600">
335
+ <div class="absolute inset-0 flex items-center justify-center text-mist-500">
343
336
  <div class="text-center">
344
337
  <div class="mb-1 text-lg">No output yet</div>
345
- <div class="text-sm text-slate-700">Waiting for logs...</div>
338
+ <div class="text-sm text-mist-600">Waiting for logs...</div>
346
339
  </div>
347
340
  </div>
348
341
  {/if}
349
342
 
350
343
  {#if cache.totalLines === 0 && fetcher?.isFetching}
351
- <div class="absolute inset-0 flex items-center justify-center text-slate-600">
344
+ <div class="absolute inset-0 flex items-center justify-center text-mist-500">
352
345
  <div class="flex items-center gap-2">
353
346
  <span class="dot" style="animation-delay: 0ms;"></span>
354
347
  <span class="dot" style="animation-delay: 150ms;"></span>
@@ -363,11 +356,11 @@
363
356
  {#if userScrolledUp && cache.totalLines > 0}
364
357
  <button
365
358
  class="absolute right-4 bottom-12 z-10 flex items-center gap-2 rounded-full border
366
- border-slate-700 bg-slate-800
359
+ border-mist-700 bg-mist-800
367
360
  px-3 py-1.5 text-xs
368
- font-medium text-slate-300 shadow-lg
361
+ font-medium text-mist-200 shadow-lg
369
362
  transition-all duration-200 hover:scale-105
370
- hover:bg-slate-700 active:scale-95"
363
+ hover:bg-mist-700 active:scale-95"
371
364
  onclick={enableAutoScroll}
372
365
  >
373
366
  <svg
@@ -384,20 +377,20 @@
384
377
  {/if}
385
378
 
386
379
  <div
387
- class="flex flex-shrink-0 items-center justify-between border-t border-slate-800
388
- bg-slate-900/80 px-3 py-1.5 text-xs text-slate-500"
380
+ class="flex flex-shrink-0 items-center justify-between border-t border-mist-800
381
+ bg-mist-900/80 px-3 py-1.5 text-xs text-mist-400"
389
382
  >
390
383
  <div class="flex items-center gap-3">
391
384
  {#if isStreaming}
392
- <div class="flex items-center gap-1.5 text-aurora-400">
385
+ <div class="flex items-center gap-1.5 text-info">
393
386
  <div class="h-1.5 w-1.5 animate-pulse rounded-full bg-aurora-400"></div>
394
387
  Streaming
395
388
  </div>
396
389
  {:else if cache.finished}
397
- <span class="text-slate-600">Stream ended</span>
390
+ <span class="text-mist-500">Stream ended</span>
398
391
  {/if}
399
392
  {#if fetcher?.isFetching}
400
- <span class="text-slate-600">Fetching...</span>
393
+ <span class="text-mist-500">Fetching...</span>
401
394
  {/if}
402
395
  </div>
403
396
  <div class="flex items-center gap-4">
@@ -406,7 +399,7 @@
406
399
  <span>{formatBytes(cache.totalBytes)}</span>
407
400
  {/if}
408
401
  {#if isAutoScroll}
409
- <span class="text-aurora-500">Auto-scroll</span>
402
+ <span class="text-info">Auto-scroll</span>
410
403
  {/if}
411
404
  </div>
412
405
  </div>
@@ -423,7 +416,7 @@
423
416
 
424
417
  @keyframes log-line-flash {
425
418
  0% {
426
- background-color: rgba(251, 191, 36, 0.45);
419
+ background-color: oklch(0.8 0.18 70 / 0.45);
427
420
  }
428
421
  100% {
429
422
  background-color: transparent;
@@ -28,10 +28,10 @@
28
28
  const percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
29
29
 
30
30
  const variantClasses: Record<ProgressVariant, string> = {
31
- default: "bg-wisp-500",
32
- success: "bg-success-500",
33
- warning: "bg-warning-500",
34
- danger: "bg-danger-500",
31
+ default: "bg-primary",
32
+ success: "bg-success-surface",
33
+ warning: "bg-warning-surface",
34
+ danger: "bg-danger-surface",
35
35
  };
36
36
 
37
37
  const sizeClasses: Record<ProgressSize, string> = {
@@ -44,7 +44,9 @@
44
44
  </div>
45
45
 
46
46
  {#if error}
47
- <p class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-600 duration-200">
47
+ <p
48
+ class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-soft-text duration-200"
49
+ >
48
50
  {error}
49
51
  </p>
50
52
  {:else if hint}
@@ -385,7 +385,7 @@
385
385
  {/if}
386
386
 
387
387
  {#if error}
388
- <p class="animate-in slide-in-from-top-1 fade-in mt-1.5 text-sm text-danger-600">
388
+ <p class="animate-in slide-in-from-top-1 fade-in mt-1.5 text-sm text-danger-soft-text">
389
389
  {error}
390
390
  </p>
391
391
  {:else if hint}
@@ -6,14 +6,19 @@
6
6
  data: number[];
7
7
  color?: string;
8
8
  fillColor?: string;
9
+ fillOpacity?: number;
9
10
  height?: number;
10
11
  class?: string;
11
12
  }
12
13
 
14
+ // `currentColor` by default so the caller can theme us with a Tailwind
15
+ // text-* class — that's the only way to flip on .dark without burning a
16
+ // hardcoded hex in here.
13
17
  let {
14
18
  data,
15
- color = "#6366f1",
19
+ color = "currentColor",
16
20
  fillColor,
21
+ fillOpacity = 0.12,
17
22
  height = 40,
18
23
  class: className = "",
19
24
  }: Props = $props();
@@ -42,7 +47,10 @@
42
47
  return `${pathD} L${lastX.toFixed(1)},${height} L0,${height} Z`;
43
48
  });
44
49
 
45
- let resolvedFill = $derived(fillColor ?? color + "18");
50
+ // Use `fill-opacity` rather than appending "18" to a hex — that trick
51
+ // breaks for `currentColor` / `oklch()` / `var(...)` values.
52
+ let resolvedFill = $derived(fillColor ?? color);
53
+ let resolvedFillOpacity = $derived(fillColor === undefined ? fillOpacity : 1);
46
54
  </script>
47
55
 
48
56
  <svg
@@ -52,7 +60,7 @@
52
60
  style="height: {height}px"
53
61
  >
54
62
  {#if data.length >= 2}
55
- <path d={fillD} fill={resolvedFill} />
63
+ <path d={fillD} fill={resolvedFill} fill-opacity={resolvedFillOpacity} />
56
64
  <path d={pathD} fill="none" stroke={color} stroke-width="1.5" stroke-linejoin="round" />
57
65
  {/if}
58
66
  </svg>
@@ -16,15 +16,15 @@
16
16
  let { accent = "wisp", onclick, children, class: className = "" }: Props = $props();
17
17
 
18
18
  const accentClasses: Record<TaskCardAccent, string> = {
19
- danger: "hover:border-danger-200",
20
- wisp: "hover:border-wisp-200",
21
- aurora: "hover:border-aurora-200",
19
+ danger: "hover:border-danger-soft-border",
20
+ wisp: "hover:border-primary-soft-border",
21
+ aurora: "hover:border-info-soft-border",
22
22
  };
23
23
  </script>
24
24
 
25
25
  <button
26
26
  type="button"
27
- class="group w-full rounded-lg border border-mist-100 bg-surface-raised p-3 text-left transition-all hover:shadow-sm {accentClasses[
27
+ class="group w-full rounded-lg border border-outline-faint bg-surface-raised p-3 text-left transition-all hover:shadow-sm {accentClasses[
28
28
  accent
29
29
  ]} {className}"
30
30
  {onclick}
@@ -67,14 +67,14 @@
67
67
  ></textarea>
68
68
 
69
69
  {#if error}
70
- <div class="pointer-events-none absolute top-3 right-3 text-danger-500">
70
+ <div class="pointer-events-none absolute top-3 right-3 text-danger-soft-text">
71
71
  <CircleAlert size={18} />
72
72
  </div>
73
73
  {/if}
74
74
  </div>
75
75
 
76
76
  {#if error}
77
- <p class="text-sm text-danger-600">{error}</p>
77
+ <p class="text-sm text-danger-soft-text">{error}</p>
78
78
  {:else if hint}
79
79
  <p class="text-sm text-on-surface-muted">{hint}</p>
80
80
  {/if}
@@ -26,13 +26,13 @@
26
26
  function getColorClasses(type: Toast["type"]) {
27
27
  switch (type) {
28
28
  case "success":
29
- return "border-success-200 text-success-700";
29
+ return "border-success-soft-border text-success-soft-text";
30
30
  case "error":
31
- return "border-danger-200 text-danger-700";
31
+ return "border-danger-soft-border text-danger-soft-text";
32
32
  case "warning":
33
- return "border-warning-200 text-warning-700";
33
+ return "border-warning-soft-border text-warning-soft-text";
34
34
  case "info":
35
- return "border-wisp-200 text-wisp-700";
35
+ return "border-primary-soft-border text-primary-soft-text";
36
36
  }
37
37
  }
38
38
 
@@ -11,9 +11,17 @@
11
11
  position?: TooltipPosition;
12
12
  children: Snippet;
13
13
  class?: string;
14
+ /** Wrap long content across lines instead of one nowrap line. */
15
+ wide?: boolean;
14
16
  }
15
17
 
16
- let { content, position = "top", children, class: className = "" }: Props = $props();
18
+ let {
19
+ content,
20
+ position = "top",
21
+ children,
22
+ class: className = "",
23
+ wide = false,
24
+ }: Props = $props();
17
25
 
18
26
  const positionClasses: Record<TooltipPosition, string> = {
19
27
  top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
@@ -39,7 +47,8 @@
39
47
  pointer-events-none invisible rounded-lg
40
48
  bg-mist-800 px-2.5 py-1.5 text-xs
41
49
  font-medium
42
- whitespace-nowrap text-white opacity-0 transition-opacity
50
+ {wide ? 'w-max max-w-xs whitespace-normal' : 'whitespace-nowrap'}
51
+ text-white opacity-0 transition-opacity
43
52
  duration-150 group-hover:visible
44
53
  group-hover:opacity-100
45
54
  "