@runwisp/ui 1.3.0 → 1.5.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 (73) hide show
  1. package/package.json +5 -6
  2. package/src/lib/actions/floating.ts +73 -0
  3. package/src/lib/actions/portal.ts +5 -1
  4. package/src/lib/components/Accordion.svelte +6 -2
  5. package/src/lib/components/AccordionItem.svelte +80 -23
  6. package/src/lib/components/Avatar.svelte +0 -1
  7. package/src/lib/components/CodeBlock.svelte +84 -35
  8. package/src/lib/components/CronInput.svelte +5 -2
  9. package/src/lib/components/DataGrid.svelte +95 -133
  10. package/src/lib/components/Drawer.svelte +2 -6
  11. package/src/lib/components/Dropdown.svelte +19 -57
  12. package/src/lib/components/EmptyState.svelte +2 -1
  13. package/src/lib/components/ErrorState.svelte +2 -1
  14. package/src/lib/components/Heading.svelte +19 -12
  15. package/src/lib/components/Kbd.svelte +25 -10
  16. package/src/lib/components/Link.svelte +7 -1
  17. package/src/lib/components/LogConsole.svelte +37 -34
  18. package/src/lib/components/Modal.svelte +44 -33
  19. package/src/lib/components/NotificationBell.svelte +2 -1
  20. package/src/lib/components/PageContainer.svelte +1 -10
  21. package/src/lib/components/PageHeader.svelte +8 -4
  22. package/src/lib/components/Popover.svelte +9 -51
  23. package/src/lib/components/Progress.svelte +19 -6
  24. package/src/lib/components/Radio.svelte +16 -4
  25. package/src/lib/components/RadioGroup.svelte +15 -2
  26. package/src/lib/components/Select.svelte +13 -45
  27. package/src/lib/components/Skeleton.svelte +6 -6
  28. package/src/lib/components/Table.svelte +13 -4
  29. package/src/lib/components/TableBody.svelte +2 -1
  30. package/src/lib/components/TableCell.svelte +38 -8
  31. package/src/lib/components/TableHead.svelte +12 -4
  32. package/src/lib/components/TableRow.svelte +23 -3
  33. package/src/lib/components/TaskCard.svelte +1 -1
  34. package/src/lib/components/TimezoneSelect.svelte +2 -2
  35. package/src/lib/components/accordion.svelte.ts +19 -0
  36. package/src/lib/components/dashboard/RunDetailPanel.svelte +141 -67
  37. package/src/lib/components/dashboard/RunFilterPopover.svelte +7 -16
  38. package/src/lib/components/dashboard/RunsList.svelte +57 -37
  39. package/src/lib/components/dashboard/cron-format.ts +1 -1
  40. package/src/lib/components/dashboard/run-filters.test.ts +2 -58
  41. package/src/lib/components/dashboard/run-filters.ts +5 -53
  42. package/src/lib/components/dashboard/run-helpers.test.ts +17 -7
  43. package/src/lib/components/dashboard/run-helpers.ts +12 -1
  44. package/src/lib/components/dashboard/status-config.ts +38 -83
  45. package/src/lib/components/dashboard/types.ts +0 -26
  46. package/src/lib/components/radio-group.ts +11 -0
  47. package/src/lib/index.ts +8 -43
  48. package/src/lib/log-console/LogFetcher.svelte.ts +3 -14
  49. package/src/lib/log-console/highlight-scroll.test.ts +59 -0
  50. package/src/lib/log-console/highlight-scroll.ts +41 -0
  51. package/src/lib/theme.css +10 -4
  52. package/src/lib/utils/clipboard.svelte.test.ts +92 -0
  53. package/src/lib/utils/clipboard.svelte.ts +73 -0
  54. package/src/lib/utils/format.ts +65 -94
  55. package/src/lib/utils/id.test.ts +3 -7
  56. package/src/lib/utils/id.ts +3 -2
  57. package/src/lib/utils/theme.svelte.test.ts +0 -6
  58. package/src/lib/utils/theme.svelte.ts +3 -5
  59. package/src/lib/utils/toast.svelte.ts +3 -11
  60. package/src/stories/Accordion.stories.svelte +61 -0
  61. package/src/stories/Button.stories.svelte +2 -2
  62. package/src/stories/CodeBlock.stories.svelte +55 -0
  63. package/src/stories/DataGrid.stories.svelte +2 -7
  64. package/src/stories/Dropdown.stories.svelte +3 -3
  65. package/src/stories/Heading.stories.svelte +31 -0
  66. package/src/stories/Kbd.stories.svelte +36 -0
  67. package/src/stories/Link.stories.svelte +38 -0
  68. package/src/stories/PageHeader.stories.svelte +31 -0
  69. package/src/stories/Progress.stories.svelte +26 -0
  70. package/src/stories/RadioGroup.stories.svelte +43 -0
  71. package/src/stories/Table.stories.svelte +69 -0
  72. package/src/svelte-shims.d.ts +0 -5
  73. package/src/lib/components/dashboard/status-config.test.ts +0 -18
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  // Signed magnitude thresholds for Intl.RelativeTimeFormat, largest-first
5
- // division. Mirrors the buckets date-fns' formatDistance used to pick.
5
+ // division. The buckets match date-fns' formatDistance.
6
6
  const RELATIVE_DIVISIONS: [amount: number, unit: Intl.RelativeTimeFormatUnit][] = [
7
7
  [60, "seconds"],
8
8
  [60, "minutes"],
@@ -13,105 +13,88 @@ const RELATIVE_DIVISIONS: [amount: number, unit: Intl.RelativeTimeFormatUnit][]
13
13
  [Number.POSITIVE_INFINITY, "years"],
14
14
  ];
15
15
 
16
- // formatDistance renders a signed date delta as "N units ago" / "in N units"
17
- // using the platform Intl formatter. Negative delta (date before base) reads as
18
- // past, positive as future.
19
- function formatDistance(date: Date, base: Date): string {
20
- const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "always" });
21
- let delta = (date.getTime() - base.getTime()) / 1000;
16
+ const relativeTime = new Intl.RelativeTimeFormat(undefined, { numeric: "always" });
17
+
18
+ /** "N units ago" / "in N units" for `date` relative to `now`. */
19
+ export function formatRelativeTime(date: string | Date, now: Date = new Date()): string {
20
+ let delta = (new Date(date).getTime() - now.getTime()) / 1000;
22
21
  for (const [amount, unit] of RELATIVE_DIVISIONS) {
23
22
  if (Math.abs(delta) < amount) {
24
- return rtf.format(Math.round(delta), unit);
23
+ return relativeTime.format(Math.round(delta), unit);
25
24
  }
26
25
  delta /= amount;
27
26
  }
28
- return rtf.format(Math.round(delta), "years");
27
+ return relativeTime.format(Math.round(delta), "years");
29
28
  }
30
29
 
31
- export function formatBytes(bytes: number): string {
32
- const units: [string, ...string[]] = ["B", "KB", "MB", "GB", "TB", "PB"];
33
- let value = bytes;
34
- let unitIndex = 0;
35
- while (value >= 1024 && unitIndex < units.length - 1) {
36
- value /= 1024;
37
- unitIndex++;
38
- }
39
- const unit = units[unitIndex] ?? "B";
40
- const digits = value < 10 && unitIndex > 0 ? 1 : 0;
41
- return `${new Intl.NumberFormat("en", { maximumFractionDigits: digits }).format(value)} ${unit}`;
30
+ /** A locale date/time formatter for one fixed option set, built once. */
31
+ function dateFormatter(options: Intl.DateTimeFormatOptions): (date: string | Date) => string {
32
+ const format = new Intl.DateTimeFormat(undefined, options);
33
+ return (date) => format.format(new Date(date));
42
34
  }
43
35
 
44
- export function formatRelativeTime(dateStr: string | Date, now: Date = new Date()): string {
45
- return formatDistance(new Date(dateStr), now);
46
- }
36
+ export const formatDateTime = dateFormatter({
37
+ year: "numeric",
38
+ month: "short",
39
+ day: "numeric",
40
+ hour: "numeric",
41
+ minute: "2-digit",
42
+ });
47
43
 
48
- export function formatRelativeTimeWithAbsolute(
49
- dateStr: string | Date,
50
- now: Date = new Date(),
51
- ): string {
52
- const date = new Date(dateStr);
53
- const relative = formatDistance(date, now);
54
- const diffMs = Math.abs(now.getTime() - date.getTime());
55
- const diffDays = diffMs / (1000 * 60 * 60 * 24);
44
+ /** Wall-clock time of day, seconds included, 24-hour — e.g. "17:15:02". */
45
+ export const formatClockTime = dateFormatter({
46
+ hour: "2-digit",
47
+ minute: "2-digit",
48
+ second: "2-digit",
49
+ hour12: false,
50
+ });
56
51
 
57
- let absolute: string;
58
- if (diffDays < 1) {
59
- absolute = date.toLocaleTimeString(undefined, {
60
- hour: "numeric",
61
- minute: "2-digit",
62
- hour12: false,
63
- });
64
- } else {
65
- absolute = date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
66
- }
52
+ /** Calendar date without the time — e.g. "22 Jun 2026". */
53
+ export const formatCalendarDate = dateFormatter({
54
+ year: "numeric",
55
+ month: "short",
56
+ day: "numeric",
57
+ });
67
58
 
68
- return `${relative} (${absolute})`;
69
- }
59
+ /** Time of day, no seconds, 24-hour — e.g. "17:15". */
60
+ export const formatTimeHM = dateFormatter({ hour: "2-digit", minute: "2-digit", hour12: false });
70
61
 
71
- export function formatDateTime(dateStr: string): string {
72
- return new Date(dateStr).toLocaleString(undefined, {
73
- year: "numeric",
74
- month: "short",
75
- day: "numeric",
76
- hour: "numeric",
77
- minute: "2-digit",
78
- });
79
- }
62
+ /** Day and month, no year — e.g. "22 Jun". */
63
+ export const formatDayMonth = dateFormatter({ month: "short", day: "numeric" });
80
64
 
81
- /** Wall-clock time of day, seconds included, 24-hour — e.g. "17:15:02". */
82
- export function formatClockTime(dateStr: string): string {
83
- return new Date(dateStr).toLocaleTimeString(undefined, {
84
- hour: "2-digit",
85
- minute: "2-digit",
86
- second: "2-digit",
87
- hour12: false,
88
- });
89
- }
65
+ export const formatFullDateTime = dateFormatter({
66
+ year: "numeric",
67
+ month: "short",
68
+ day: "2-digit",
69
+ hour: "2-digit",
70
+ minute: "2-digit",
71
+ second: "2-digit",
72
+ hour12: false,
73
+ });
90
74
 
91
- /** Calendar date without the time — e.g. "22 Jun 2026". */
92
- export function formatCalendarDate(dateStr: string): string {
93
- return new Date(dateStr).toLocaleDateString(undefined, {
94
- year: "numeric",
95
- month: "short",
96
- day: "numeric",
97
- });
98
- }
75
+ const formatShortTime = dateFormatter({ hour: "numeric", minute: "2-digit", hour12: false });
99
76
 
100
- /** Time of day, no seconds, 24-hour — e.g. "17:15". */
101
- export function formatTimeHM(dateStr: string): string {
102
- return new Date(dateStr).toLocaleTimeString(undefined, {
103
- hour: "2-digit",
104
- minute: "2-digit",
105
- hour12: false,
106
- });
77
+ export function formatRelativeTimeWithAbsolute(
78
+ dateStr: string | Date,
79
+ now: Date = new Date(),
80
+ ): string {
81
+ const date = new Date(dateStr);
82
+ const diffDays = Math.abs(now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24);
83
+ const absolute = diffDays < 1 ? formatShortTime(date) : formatDayMonth(date);
84
+ return `${formatRelativeTime(date, now)} (${absolute})`;
107
85
  }
108
86
 
109
- /** Day and month, no year — e.g. "22 Jun". */
110
- export function formatDayMonth(dateStr: string): string {
111
- return new Date(dateStr).toLocaleDateString(undefined, {
112
- month: "short",
113
- day: "numeric",
114
- });
87
+ export function formatBytes(bytes: number): string {
88
+ const units: [string, ...string[]] = ["B", "KB", "MB", "GB", "TB", "PB"];
89
+ let value = bytes;
90
+ let unitIndex = 0;
91
+ while (value >= 1024 && unitIndex < units.length - 1) {
92
+ value /= 1024;
93
+ unitIndex++;
94
+ }
95
+ const unit = units[unitIndex] ?? "B";
96
+ const digits = value < 10 && unitIndex > 0 ? 1 : 0;
97
+ return `${new Intl.NumberFormat("en", { maximumFractionDigits: digits }).format(value)} ${unit}`;
115
98
  }
116
99
 
117
100
  export function formatDuration(ms: number): string {
@@ -125,15 +108,3 @@ export function formatDuration(ms: number): string {
125
108
  const remM = m % 60;
126
109
  return remM > 0 ? String(h) + "h " + String(remM) + "m" : String(h) + "h";
127
110
  }
128
-
129
- export function formatFullDateTime(date: Date | string): string {
130
- return new Intl.DateTimeFormat(undefined, {
131
- year: "numeric",
132
- month: "short",
133
- day: "2-digit",
134
- hour: "2-digit",
135
- minute: "2-digit",
136
- second: "2-digit",
137
- hour12: false,
138
- }).format(new Date(date));
139
- }
@@ -5,16 +5,12 @@ import { describe, expect, it } from "vitest";
5
5
  import { formatShortId } from "./id.js";
6
6
 
7
7
  describe("formatShortId", () => {
8
- it("returns the last 8 characters by default", () => {
8
+ it("returns the last 8 characters", () => {
9
9
  expect(formatShortId("01HVZ4XYZ123456ABCDEF")).toBe("56ABCDEF");
10
10
  });
11
11
 
12
- it("honours a custom length", () => {
13
- expect(formatShortId("01HVZ4XYZ123456ABCDEF", 4)).toBe("CDEF");
14
- });
15
-
16
- it("returns the whole string when shorter than the requested length", () => {
17
- expect(formatShortId("abc", 8)).toBe("abc");
12
+ it("returns the whole string when shorter than 8", () => {
13
+ expect(formatShortId("abc")).toBe("abc");
18
14
  });
19
15
 
20
16
  it("returns an empty string for an empty input", () => {
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: PoppyCake, s.r.o.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export function formatShortId(id: string, length = 8): string {
5
- return id.slice(-length);
4
+ /** The last 8 characters of a ULID, the part that differs between runs. */
5
+ export function formatShortId(id: string): string {
6
+ return id.slice(-8);
6
7
  }
@@ -89,12 +89,6 @@ describe("themeStore", () => {
89
89
  expect(themeStore.resolved).toBe("dark");
90
90
  });
91
91
 
92
- it("falls back to the legacy storage key", async () => {
93
- stubBrowser({ storage: { "runwisp-theme": "light" } });
94
- const { themeStore } = await import("./theme.svelte.js");
95
- expect(themeStore.preference).toBe("light");
96
- });
97
-
98
92
  it("ignores an invalid stored value", async () => {
99
93
  stubBrowser({ storage: { "runwisp:theme": "purple" } });
100
94
  const { themeStore } = await import("./theme.svelte.js");
@@ -2,13 +2,11 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  export type ThemePreference = "auto" | "light" | "dark";
5
- export type ResolvedTheme = "light" | "dark";
5
+ type ResolvedTheme = "light" | "dark";
6
6
 
7
7
  // Shared by every RunWisp surface. Keep byte-for-byte in sync with the
8
8
  // no-flash <head> scripts in each app's app.html / layout.
9
- export const THEME_STORAGE_KEY = "runwisp:theme";
10
- // The daemon dashboard used this key before the store moved here.
11
- const LEGACY_STORAGE_KEY = "runwisp-theme";
9
+ const THEME_STORAGE_KEY = "runwisp:theme";
12
10
 
13
11
  function isPreference(value: string | null): value is ThemePreference {
14
12
  return value === "auto" || value === "light" || value === "dark";
@@ -16,7 +14,7 @@ function isPreference(value: string | null): value is ThemePreference {
16
14
 
17
15
  function readStored(): string | null {
18
16
  try {
19
- return localStorage.getItem(THEME_STORAGE_KEY) ?? localStorage.getItem(LEGACY_STORAGE_KEY);
17
+ return localStorage.getItem(THEME_STORAGE_KEY);
20
18
  } catch {
21
19
  return null; // private mode / embedded contexts
22
20
  }
@@ -3,9 +3,9 @@
3
3
 
4
4
  import { generateUlid } from "@runwisp/common";
5
5
 
6
- export type ToastType = "success" | "error" | "warning" | "info";
6
+ type ToastType = "success" | "error" | "warning" | "info";
7
7
 
8
- export interface ToastAction {
8
+ interface ToastAction {
9
9
  label: string;
10
10
  onClick: () => void;
11
11
  }
@@ -18,7 +18,7 @@ export interface Toast {
18
18
  action?: ToastAction;
19
19
  }
20
20
 
21
- export interface ToastOptions {
21
+ interface ToastOptions {
22
22
  duration?: number;
23
23
  action?: ToastAction;
24
24
  }
@@ -88,14 +88,6 @@ class ToastStore {
88
88
  info(message: string, opts?: ToastOptions): string {
89
89
  return this.add("info", message, opts);
90
90
  }
91
-
92
- clear() {
93
- for (const timer of this.timers.values()) {
94
- clearTimeout(timer);
95
- }
96
- this.timers.clear();
97
- this.items = [];
98
- }
99
91
  }
100
92
 
101
93
  export const toast = new ToastStore();
@@ -0,0 +1,61 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import { LifeBuoy, Trash } from "@lucide/svelte";
7
+ import Accordion from "$lib/components/Accordion.svelte";
8
+ import AccordionItem from "$lib/components/AccordionItem.svelte";
9
+ import Toggle from "$lib/components/Toggle.svelte";
10
+
11
+ const { Story } = defineMeta({
12
+ title: "Navigation/Accordion",
13
+ component: Accordion,
14
+ tags: ["autodocs"],
15
+ });
16
+ </script>
17
+
18
+ <Story name="Multiple" asChild>
19
+ <Accordion class="max-w-lg">
20
+ <AccordionItem title="Retries" open>Retry a failed run up to three times.</AccordionItem>
21
+ <AccordionItem title="Timeouts">Kill the run after 10 minutes.</AccordionItem>
22
+ <AccordionItem title="Notifications">Send failures to Slack.</AccordionItem>
23
+ </Accordion>
24
+ </Story>
25
+
26
+ <Story name="Single" asChild>
27
+ <Accordion type="single" class="max-w-lg">
28
+ <AccordionItem title="Retries" open>Opening another section closes this one.</AccordionItem>
29
+ <AccordionItem title="Timeouts">Kill the run after 10 minutes.</AccordionItem>
30
+ <AccordionItem title="Notifications">Send failures to Slack.</AccordionItem>
31
+ </Accordion>
32
+ </Story>
33
+
34
+ <Story name="Framed, default open" asChild>
35
+ <AccordionItem variant="framed" open class="max-w-lg">
36
+ {#snippet title()}
37
+ <span class="flex items-center gap-2.5">
38
+ <LifeBuoy size={16} class="text-on-surface-faint" />
39
+ Schedule recovery
40
+ <span class="font-sans text-xs text-on-surface-faint">Missed ticks & jitter</span>
41
+ </span>
42
+ {/snippet}
43
+ What to do with cron ticks missed while the scheduler was offline.
44
+ </AccordionItem>
45
+ </Story>
46
+
47
+ <Story name="Framed with actions" asChild>
48
+ <AccordionItem variant="framed" title="Node.js 22" class="max-w-lg">
49
+ {#snippet actions()}
50
+ <Toggle checked size="sm" />
51
+ <button
52
+ type="button"
53
+ aria-label="Remove"
54
+ class="text-on-surface-faint hover:text-danger-surface"
55
+ >
56
+ <Trash size={14} />
57
+ </button>
58
+ {/snippet}
59
+ <pre class="font-mono text-xs">RUN apk add --no-cache nodejs npm</pre>
60
+ </AccordionItem>
61
+ </Story>
@@ -4,7 +4,7 @@
4
4
  <script module>
5
5
  import { defineMeta } from "@storybook/addon-svelte-csf";
6
6
  import Button from "$lib/components/Button.svelte";
7
- import { Play, Plus, ArrowRight, Download, Trash2 } from "@lucide/svelte";
7
+ import { Play, Plus, ArrowRight, Download, Trash } from "@lucide/svelte";
8
8
 
9
9
  const { Story } = defineMeta({
10
10
  title: "Core/Button",
@@ -71,7 +71,7 @@
71
71
  Export
72
72
  </Button>
73
73
  <Button variant="danger">
74
- {#snippet icon()}<Trash2 size={16} />{/snippet}
74
+ {#snippet icon()}<Trash size={16} />{/snippet}
75
75
  Delete
76
76
  </Button>
77
77
  </div>
@@ -0,0 +1,55 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import CodeBlock from "$lib/components/CodeBlock.svelte";
7
+
8
+ const helloTask = '[tasks.hello]\ncron = "*/5 * * * *"\nrun = "echo hello"';
9
+ const backupTask = '[tasks.backup]\ncron = "0 3 * * *"\nkeep_runs = 60';
10
+
11
+ const { Story } = defineMeta({
12
+ title: "Data Display/CodeBlock",
13
+ component: CodeBlock,
14
+ tags: ["autodocs"],
15
+ argTypes: {
16
+ variant: { control: "select", options: ["terminal", "surface"] },
17
+ size: { control: "select", options: ["sm", "md"] },
18
+ },
19
+ });
20
+ </script>
21
+
22
+ <Story
23
+ name="Default"
24
+ args={{
25
+ code: "export RUNWISP_STATION_TOKEN=rw_xxx\nexport RUNWISP_STATION_URL=https://app.runwisp.com\nrunwisp station",
26
+ }}
27
+ />
28
+
29
+ <Story
30
+ name="Filename"
31
+ args={{ code: "FROM alpine:3.20\nRUN apk add --no-cache curl", filename: "Dockerfile" }}
32
+ />
33
+
34
+ <Story
35
+ name="Shell prompt"
36
+ args={{ code: "curl -fsSL https://get.runwisp.com/install.sh | bash", prompt: "$" }}
37
+ />
38
+
39
+ <Story name="Surface variant" asChild>
40
+ <CodeBlock variant="surface" size="sm" copyable={false} code={helloTask} />
41
+ </Story>
42
+
43
+ <!-- Rich children render the markup; `code` keeps the copied text plain. -->
44
+ <Story name="Highlighted children" asChild>
45
+ <!-- No whitespace between the line spans: inside <pre> it would render as blank lines. -->
46
+ <CodeBlock filename="runwisp.toml" code={backupTask}
47
+ ><span class="block text-[var(--rw-syn-fn)]">[tasks.backup]</span><span class="block"
48
+ ><span class="text-[var(--rw-syn-kw)]">cron</span> =
49
+ <span class="text-[var(--rw-syn-str)]">"0 3 * * *"</span></span
50
+ ><span class="block"
51
+ ><span class="text-[var(--rw-syn-kw)]">keep_runs</span> =
52
+ <span class="text-[var(--rw-syn-num)]">60</span></span
53
+ ></CodeBlock
54
+ >
55
+ </Story>
@@ -9,7 +9,7 @@
9
9
  import FilterBar from "$lib/components/FilterBar.svelte";
10
10
  import { seedValues } from "$lib/components/filter-spec.js";
11
11
  import { applyFilters } from "$lib/utils/filter.js";
12
- import { Play, Pause, Trash2, SquarePen } from "@lucide/svelte";
12
+ import { Play, Pause, Trash, SquarePen } from "@lucide/svelte";
13
13
 
14
14
  const { Story } = defineMeta({
15
15
  title: "Data/DataGrid",
@@ -75,7 +75,7 @@
75
75
  { label: "Run Now", icon: Play },
76
76
  { label: "Pause", icon: Pause },
77
77
  { divider: true },
78
- { label: "Delete", icon: Trash2, danger: true },
78
+ { label: "Delete", icon: Trash, danger: true },
79
79
  ];
80
80
 
81
81
  /** @type {import("$lib/components/filter-spec.js").FilterField[]} */
@@ -138,11 +138,6 @@
138
138
 
139
139
  <Story name="Sortable" args={{ columns, data: tasks, sortKey: "name", sortDirection: "asc" }} />
140
140
 
141
- <Story
142
- name="Filterable"
143
- args={{ columns, data: tasks, filterable: true, filterPlaceholder: "Filter tasks…" }}
144
- />
145
-
146
141
  <Story name="Paginated" args={{ columns, data: tasks, paginate: true, pageSize: 2 }} />
147
142
 
148
143
  <Story name="Filters, Sort, Selection, Pagination" asChild>
@@ -4,7 +4,7 @@
4
4
  <script module>
5
5
  import { defineMeta } from "@storybook/addon-svelte-csf";
6
6
  import Dropdown from "$lib/components/Dropdown.svelte";
7
- import { Play, Pause, SquarePen, Copy, Trash2, Settings, ChevronDown } from "@lucide/svelte";
7
+ import { Play, Pause, SquarePen, Copy, Trash, Settings, ChevronDown } from "@lucide/svelte";
8
8
 
9
9
  const { Story } = defineMeta({
10
10
  title: "Core/Dropdown",
@@ -24,7 +24,7 @@
24
24
  { label: "Duplicate", icon: Copy, onClick: () => console.log("Duplicate") },
25
25
  { label: "Pause", icon: Pause, onClick: () => console.log("Pause") },
26
26
  { divider: true },
27
- { label: "Delete", icon: Trash2, danger: true, onClick: () => console.log("Delete") },
27
+ { label: "Delete", icon: Trash, danger: true, onClick: () => console.log("Delete") },
28
28
  ];
29
29
 
30
30
  const settingsMenu = [
@@ -63,7 +63,7 @@
63
63
  { label: "Edit", icon: SquarePen },
64
64
  { label: "Duplicate", icon: Copy, disabled: true },
65
65
  { divider: true },
66
- { label: "Delete", icon: Trash2, danger: true, disabled: true },
66
+ { label: "Delete", icon: Trash, danger: true, disabled: true },
67
67
  ],
68
68
  }}
69
69
  />
@@ -0,0 +1,31 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import Heading from "$lib/components/Heading.svelte";
7
+ import Text from "$lib/components/Text.svelte";
8
+
9
+ const { Story } = defineMeta({
10
+ title: "Core/Heading",
11
+ component: Heading,
12
+ tags: ["autodocs"],
13
+ });
14
+ </script>
15
+
16
+ <Story name="Levels" asChild>
17
+ <div class="space-y-3">
18
+ <Heading level={1}>Level 1</Heading>
19
+ <Heading level={2}>Level 2</Heading>
20
+ <Heading level={3}>Level 3</Heading>
21
+ <Heading level={4}>Level 4</Heading>
22
+ </div>
23
+ </Story>
24
+
25
+ <!-- A card title: the semantic level stays, the size shrinks. -->
26
+ <Story name="Card title" asChild>
27
+ <div class="max-w-sm rounded-[4px] border border-outline bg-surface-raised p-4">
28
+ <Heading level={2} size="sm" id="resources-title">System resources</Heading>
29
+ <Text size="sm" color="muted" class="mt-2">CPU and memory over the last hour.</Text>
30
+ </div>
31
+ </Story>
@@ -0,0 +1,36 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import Kbd from "$lib/components/Kbd.svelte";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Core/Kbd",
10
+ component: Kbd,
11
+ tags: ["autodocs"],
12
+ argTypes: {
13
+ size: { control: "select", options: ["xs", "sm"] },
14
+ tone: { control: "select", options: ["default", "faint", "console"] },
15
+ },
16
+ });
17
+ </script>
18
+
19
+ <Story name="Default" args={{ keys: ["Ctrl", "K"] }} />
20
+
21
+ <Story name="Faint in an input" asChild>
22
+ <div
23
+ class="flex h-9 max-w-xs items-center justify-between rounded-[3px] border border-outline bg-surface-sunken/60 px-3 font-mono text-sm text-on-surface-faint"
24
+ >
25
+ Search tasks…
26
+ <Kbd keys="⌘K" size="xs" tone="faint" />
27
+ </div>
28
+ </Story>
29
+
30
+ <Story name="Console" asChild>
31
+ <div
32
+ class="flex items-center gap-1.5 bg-[var(--rw-con-bg)] px-3 py-2 font-mono text-xs text-[var(--rw-con-dim)]"
33
+ >
34
+ Restore <Kbd keys="Esc" size="xs" tone="console" />
35
+ </div>
36
+ </Story>
@@ -0,0 +1,38 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import Link from "$lib/components/Link.svelte";
7
+
8
+ const { Story } = defineMeta({
9
+ title: "Core/Link",
10
+ component: Link,
11
+ tags: ["autodocs"],
12
+ argTypes: {
13
+ variant: { control: "select", options: ["default", "muted", "primary", "danger"] },
14
+ underline: { control: "select", options: ["always", "hover", "none"] },
15
+ },
16
+ });
17
+ </script>
18
+
19
+ <Story name="Variants" asChild>
20
+ <div class="flex flex-col gap-2 text-sm">
21
+ <Link href="#tasks">Default</Link>
22
+ <Link href="#tasks" variant="muted">Muted</Link>
23
+ <Link href="#tasks" variant="primary">Primary</Link>
24
+ <Link href="#tasks" variant="danger">Danger</Link>
25
+ </div>
26
+ </Story>
27
+
28
+ <Story name="External" asChild>
29
+ <Link
30
+ href="https://docs.runwisp.com/configuration/tasks/"
31
+ external
32
+ variant="primary"
33
+ title="Opens in a new tab"
34
+ class="text-sm font-medium"
35
+ >
36
+ Task configuration docs →
37
+ </Link>
38
+ </Story>
@@ -0,0 +1,31 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script module>
5
+ import { defineMeta } from "@storybook/addon-svelte-csf";
6
+ import PageHeader from "$lib/components/PageHeader.svelte";
7
+ import Button from "$lib/components/Button.svelte";
8
+
9
+ const { Story } = defineMeta({
10
+ title: "Layout/PageHeader",
11
+ component: PageHeader,
12
+ tags: ["autodocs"],
13
+ });
14
+ </script>
15
+
16
+ <Story
17
+ name="Default"
18
+ args={{ title: "Tasks", subtitle: "Scheduled tasks and background processes." }}
19
+ />
20
+
21
+ <Story name="Rich subtitle and actions" asChild>
22
+ <PageHeader title="Notifications">
23
+ {#snippet subtitle()}
24
+ Recent notifications across all tasks ·
25
+ <span class="font-mono font-medium text-primary-soft-text tabular-nums">3 unread</span>
26
+ {/snippet}
27
+ {#snippet actions()}
28
+ <Button size="sm" variant="secondary">Mark all read</Button>
29
+ {/snippet}
30
+ </PageHeader>
31
+ </Story>