@runwisp/ui 0.4.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 (108) hide show
  1. package/LICENSE +215 -0
  2. package/README.md +20 -0
  3. package/package.json +128 -0
  4. package/src/app.d.ts +8 -0
  5. package/src/app.html +12 -0
  6. package/src/astro/CTABand.astro +32 -0
  7. package/src/astro/Hero.astro +58 -0
  8. package/src/astro/SiteFooter.astro +47 -0
  9. package/src/astro/SiteHeader.astro +46 -0
  10. package/src/lib/actions/portal.ts +16 -0
  11. package/src/lib/assets/runwisp-logo.svg +16 -0
  12. package/src/lib/components/Accordion.svelte +20 -0
  13. package/src/lib/components/AccordionItem.svelte +74 -0
  14. package/src/lib/components/Alert.svelte +107 -0
  15. package/src/lib/components/AlertDialog.svelte +63 -0
  16. package/src/lib/components/Avatar.svelte +88 -0
  17. package/src/lib/components/Badge.svelte +131 -0
  18. package/src/lib/components/Breadcrumb.svelte +58 -0
  19. package/src/lib/components/Button.svelte +69 -0
  20. package/src/lib/components/Card.svelte +70 -0
  21. package/src/lib/components/Checkbox.svelte +90 -0
  22. package/src/lib/components/CodeBlock.svelte +66 -0
  23. package/src/lib/components/CronInput.svelte +130 -0
  24. package/src/lib/components/DataGrid.svelte +368 -0
  25. package/src/lib/components/Drawer.svelte +156 -0
  26. package/src/lib/components/Dropdown.svelte +195 -0
  27. package/src/lib/components/DurationInput.svelte +170 -0
  28. package/src/lib/components/EmptyState.svelte +46 -0
  29. package/src/lib/components/ErrorState.svelte +73 -0
  30. package/src/lib/components/FeatureCard.svelte +35 -0
  31. package/src/lib/components/FeatureGrid.svelte +37 -0
  32. package/src/lib/components/FormField.svelte +53 -0
  33. package/src/lib/components/Heading.svelte +46 -0
  34. package/src/lib/components/Input.svelte +114 -0
  35. package/src/lib/components/Kbd.svelte +31 -0
  36. package/src/lib/components/Link.svelte +56 -0
  37. package/src/lib/components/LinkButton.svelte +55 -0
  38. package/src/lib/components/LogConsole.svelte +424 -0
  39. package/src/lib/components/Logo.svelte +34 -0
  40. package/src/lib/components/Modal.svelte +138 -0
  41. package/src/lib/components/PageContainer.svelte +38 -0
  42. package/src/lib/components/Pagination.svelte +96 -0
  43. package/src/lib/components/Popover.svelte +130 -0
  44. package/src/lib/components/Progress.svelte +68 -0
  45. package/src/lib/components/Prose.svelte +43 -0
  46. package/src/lib/components/Radio.svelte +82 -0
  47. package/src/lib/components/RadioGroup.svelte +53 -0
  48. package/src/lib/components/Select.svelte +454 -0
  49. package/src/lib/components/Separator.svelte +30 -0
  50. package/src/lib/components/Skeleton.svelte +75 -0
  51. package/src/lib/components/Sparkline.svelte +58 -0
  52. package/src/lib/components/Spinner.svelte +44 -0
  53. package/src/lib/components/StatusIndicator.svelte +203 -0
  54. package/src/lib/components/Table.svelte +19 -0
  55. package/src/lib/components/TableBody.svelte +17 -0
  56. package/src/lib/components/TableCell.svelte +34 -0
  57. package/src/lib/components/TableHead.svelte +19 -0
  58. package/src/lib/components/TableRow.svelte +22 -0
  59. package/src/lib/components/Tabs.svelte +148 -0
  60. package/src/lib/components/Text.svelte +57 -0
  61. package/src/lib/components/Textarea.svelte +81 -0
  62. package/src/lib/components/ThemeToggle.svelte +47 -0
  63. package/src/lib/components/TimezoneSelect.svelte +127 -0
  64. package/src/lib/components/ToastContainer.svelte +84 -0
  65. package/src/lib/components/Toggle.svelte +84 -0
  66. package/src/lib/components/Tooltip.svelte +56 -0
  67. package/src/lib/components/button-styles.ts +51 -0
  68. package/src/lib/components/dashboard/RunDetailPanel.svelte +252 -0
  69. package/src/lib/components/dashboard/RunsList.svelte +331 -0
  70. package/src/lib/components/dashboard/run-helpers.ts +12 -0
  71. package/src/lib/components/dashboard/status-config.ts +141 -0
  72. package/src/lib/components/dashboard/types.ts +35 -0
  73. package/src/lib/index.ts +122 -0
  74. package/src/lib/log-console/LogCache.svelte.ts +93 -0
  75. package/src/lib/log-console/LogFetcher.svelte.ts +179 -0
  76. package/src/lib/log-console/types.ts +24 -0
  77. package/src/lib/theme-tokens.css +235 -0
  78. package/src/lib/theme.css +276 -0
  79. package/src/lib/utils/error.ts +18 -0
  80. package/src/lib/utils/format.ts +76 -0
  81. package/src/lib/utils/id.ts +6 -0
  82. package/src/lib/utils/toast.svelte.ts +87 -0
  83. package/src/lib/utils/toast.ts +60 -0
  84. package/src/routes/+layout.svelte +10 -0
  85. package/src/routes/+page.svelte +5 -0
  86. package/src/routes/layout.css +3 -0
  87. package/src/stories/Alert.stories.svelte +64 -0
  88. package/src/stories/Avatar.stories.svelte +76 -0
  89. package/src/stories/Badge.stories.svelte +76 -0
  90. package/src/stories/Breadcrumb.stories.svelte +64 -0
  91. package/src/stories/Button.stories.svelte +101 -0
  92. package/src/stories/Card.stories.svelte +106 -0
  93. package/src/stories/Checkbox.stories.svelte +77 -0
  94. package/src/stories/DataGrid.stories.svelte +130 -0
  95. package/src/stories/Dropdown.stories.svelte +81 -0
  96. package/src/stories/EmptyState.stories.svelte +74 -0
  97. package/src/stories/Input.stories.svelte +88 -0
  98. package/src/stories/Logo.stories.svelte +82 -0
  99. package/src/stories/Modal.stories.svelte +101 -0
  100. package/src/stories/Progress.stories.svelte +65 -0
  101. package/src/stories/Select.stories.svelte +158 -0
  102. package/src/stories/Tabs.stories.svelte +103 -0
  103. package/src/stories/Textarea.stories.svelte +45 -0
  104. package/src/stories/Toggle.stories.svelte +74 -0
  105. package/src/stories/Tooltip.stories.svelte +77 -0
  106. package/src/stories/Welcome.mdx +241 -0
  107. package/src/stories/assets/runwisp_c.svg +16 -0
  108. package/src/svelte-shims.d.ts +18 -0
@@ -0,0 +1,96 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ import Button from "./Button.svelte";
6
+ import { ChevronDown } from "@lucide/svelte";
7
+
8
+ interface Props {
9
+ page: number;
10
+ pageSize: number;
11
+ totalItems: number;
12
+ pageSizeOptions?: number[] | undefined;
13
+ onPageChange?: ((page: number) => void) | undefined;
14
+ onPageSizeChange?: ((pageSize: number) => void) | undefined;
15
+ class?: string | undefined;
16
+ }
17
+
18
+ let {
19
+ page,
20
+ pageSize,
21
+ totalItems,
22
+ pageSizeOptions = [10, 20, 50, 100],
23
+ onPageChange,
24
+ onPageSizeChange,
25
+ class: className = "",
26
+ }: Props = $props();
27
+
28
+ const totalPages = $derived(Math.max(1, Math.ceil(totalItems / Math.max(1, pageSize))));
29
+ const safePage = $derived(Math.min(Math.max(1, page), totalPages));
30
+
31
+ const start = $derived(totalItems === 0 ? 0 : (safePage - 1) * pageSize + 1);
32
+ const end = $derived(totalItems === 0 ? 0 : Math.min(totalItems, safePage * pageSize));
33
+
34
+ function goTo(nextPage: number) {
35
+ const clamped = Math.min(Math.max(1, nextPage), totalPages);
36
+ onPageChange?.(clamped);
37
+ }
38
+
39
+ function handlePageSizeChange(e: Event) {
40
+ const newSize = Number((e.target as HTMLSelectElement).value);
41
+ onPageSizeChange?.(newSize);
42
+ }
43
+ </script>
44
+
45
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between {className}">
46
+ <div class="flex items-center gap-4 text-sm text-on-surface-muted">
47
+ {#if totalItems === 0}
48
+ <span>No results</span>
49
+ {:else}
50
+ <span>Showing {start}–{end} of {totalItems}</span>
51
+ {/if}
52
+
53
+ <div class="flex items-center gap-2">
54
+ <span class="text-on-surface-muted">Rows per page</span>
55
+ <div class="relative">
56
+ <select
57
+ value={pageSize}
58
+ onchange={handlePageSizeChange}
59
+ class="
60
+ appearance-none rounded-lg border border-outline bg-surface-raised py-1 pr-7 pl-2 text-sm
61
+ font-medium text-on-surface-muted shadow-sm focus:border-ring focus:ring-1 focus:ring-ring focus:outline-none
62
+ "
63
+ >
64
+ {#each pageSizeOptions as size (size)}
65
+ <option value={size}>{size}</option>
66
+ {/each}
67
+ </select>
68
+ <div
69
+ class="pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 text-on-surface-faint"
70
+ >
71
+ <ChevronDown size={14} />
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="flex items-center gap-2">
78
+ <Button
79
+ variant="secondary"
80
+ size="xs"
81
+ disabled={safePage <= 1}
82
+ onclick={() => goTo(safePage - 1)}>Prev</Button
83
+ >
84
+ <div class="min-w-[4rem] text-center text-xs text-on-surface-muted">
85
+ Page {safePage} of {totalPages}
86
+ </div>
87
+ <Button
88
+ variant="secondary"
89
+ size="xs"
90
+ disabled={safePage >= totalPages}
91
+ onclick={() => goTo(safePage + 1)}
92
+ >
93
+ Next
94
+ </Button>
95
+ </div>
96
+ </div>
@@ -0,0 +1,130 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ import type { Snippet } from "svelte";
6
+ import type { Placement } from "@floating-ui/dom";
7
+ import { tick, onDestroy } from "svelte";
8
+ import { computePosition, autoUpdate, flip, shift, offset } from "@floating-ui/dom";
9
+ import { scale } from "svelte/transition";
10
+ import { quintOut } from "svelte/easing";
11
+ import { portal } from "../actions/portal.js";
12
+
13
+ interface Props {
14
+ open?: boolean;
15
+ placement?: Placement;
16
+ trigger?: Snippet;
17
+ children?: Snippet;
18
+ class?: string;
19
+ }
20
+
21
+ let {
22
+ open = $bindable(false),
23
+ placement = "bottom",
24
+ trigger,
25
+ children,
26
+ class: className = "",
27
+ }: Props = $props();
28
+
29
+ let triggerEl = $state<HTMLElement | null>(null);
30
+ let contentEl = $state<HTMLElement | null>(null);
31
+ let cleanupFloating = $state<(() => void) | null>(null);
32
+
33
+ async function setupFloating() {
34
+ if (!triggerEl || !contentEl) return;
35
+
36
+ cleanupFloating = autoUpdate(triggerEl, contentEl, () => {
37
+ if (!triggerEl || !contentEl) return;
38
+
39
+ void computePosition(triggerEl, contentEl, {
40
+ placement,
41
+ middleware: [offset(8), flip(), shift({ padding: 8 })],
42
+ }).then(({ x, y }) => {
43
+ if (contentEl) {
44
+ Object.assign(contentEl.style, {
45
+ left: `${x}px`,
46
+ top: `${y}px`,
47
+ position: "absolute",
48
+ });
49
+ }
50
+ });
51
+ });
52
+ }
53
+
54
+ function toggle() {
55
+ open = !open;
56
+ if (open) {
57
+ void tick().then(() => {
58
+ void setupFloating();
59
+ });
60
+ } else {
61
+ cleanupFloating?.();
62
+ cleanupFloating = null;
63
+ }
64
+ }
65
+
66
+ function handleOutsideClick(e: MouseEvent) {
67
+ const target = e.target as HTMLElement;
68
+ if (triggerEl?.contains(target)) return;
69
+ if (contentEl?.contains(target)) return;
70
+ if (open) {
71
+ open = false;
72
+ cleanupFloating?.();
73
+ cleanupFloating = null;
74
+ }
75
+ }
76
+
77
+ function handleKeyDown(e: KeyboardEvent) {
78
+ if (e.key === "Escape" && open) {
79
+ open = false;
80
+ cleanupFloating?.();
81
+ cleanupFloating = null;
82
+ }
83
+ }
84
+
85
+ onDestroy(() => {
86
+ cleanupFloating?.();
87
+ });
88
+ </script>
89
+
90
+ <svelte:window onclick={handleOutsideClick} onkeydown={handleKeyDown} />
91
+
92
+ <div class="inline-block {className}">
93
+ <div
94
+ bind:this={triggerEl}
95
+ role="button"
96
+ tabindex="0"
97
+ onclick={(e) => {
98
+ e.stopPropagation();
99
+ toggle();
100
+ }}
101
+ onkeydown={(e) => {
102
+ if (e.key === "Enter" || e.key === " ") {
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ toggle();
106
+ }
107
+ }}
108
+ >
109
+ {#if trigger}
110
+ {@render trigger()}
111
+ {/if}
112
+ </div>
113
+
114
+ {#if open}
115
+ <div
116
+ use:portal
117
+ bind:this={contentEl}
118
+ transition:scale={{ duration: 150, start: 0.95, easing: quintOut }}
119
+ class="
120
+ z-[9999]
121
+ rounded-xl
122
+ border border-outline bg-surface-overlay p-4 shadow-xl shadow-on-surface/5
123
+ "
124
+ >
125
+ {#if children}
126
+ {@render children()}
127
+ {/if}
128
+ </div>
129
+ {/if}
130
+ </div>
@@ -0,0 +1,68 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ type ProgressVariant = "default" | "success" | "warning" | "danger";
6
+ type ProgressSize = "sm" | "md" | "lg";
7
+
8
+ interface Props {
9
+ value: number;
10
+ max?: number;
11
+ variant?: ProgressVariant;
12
+ size?: ProgressSize;
13
+ showLabel?: boolean;
14
+ label?: string;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ value,
20
+ max = 100,
21
+ variant = "default",
22
+ size = "md",
23
+ showLabel = false,
24
+ label,
25
+ class: className = "",
26
+ }: Props = $props();
27
+
28
+ const percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
29
+
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",
35
+ };
36
+
37
+ const sizeClasses: Record<ProgressSize, string> = {
38
+ sm: "h-1",
39
+ md: "h-2",
40
+ lg: "h-3",
41
+ };
42
+ </script>
43
+
44
+ <div class={className}>
45
+ {#if showLabel || label}
46
+ <div class="mb-1.5 flex items-center justify-between">
47
+ {#if label}
48
+ <span class="text-sm font-medium text-on-surface-muted">{label}</span>
49
+ {/if}
50
+ {#if showLabel}
51
+ <span class="text-sm text-on-surface-muted">{Math.round(percentage)}%</span>
52
+ {/if}
53
+ </div>
54
+ {/if}
55
+
56
+ <div class="w-full {sizeClasses[size]} overflow-hidden rounded-full bg-outline">
57
+ <div
58
+ class="{sizeClasses[size]} {variantClasses[
59
+ variant
60
+ ]} rounded-full transition-all duration-300"
61
+ style="width: {percentage}%"
62
+ role="progressbar"
63
+ aria-valuenow={value}
64
+ aria-valuemin={0}
65
+ aria-valuemax={max}
66
+ ></div>
67
+ </div>
68
+ </div>
@@ -0,0 +1,43 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ import type { Snippet } from "svelte";
6
+
7
+ type ProseSize = "sm" | "base" | "lg";
8
+
9
+ interface Props {
10
+ size?: ProseSize;
11
+ children: Snippet;
12
+ class?: string;
13
+ }
14
+
15
+ let { size = "base", children, class: className = "" }: Props = $props();
16
+
17
+ const sizeClasses: Record<ProseSize, string> = {
18
+ sm: "prose-sm",
19
+ base: "prose-base",
20
+ lg: "prose-lg",
21
+ };
22
+ </script>
23
+
24
+ <div
25
+ class="
26
+ prose {sizeClasses[size]}
27
+ max-w-none
28
+ prose-headings:text-on-surface
29
+ prose-p:text-on-surface-muted prose-a:text-primary prose-a:no-underline
30
+ hover:prose-a:underline
31
+ prose-blockquote:border-outline prose-blockquote:text-on-surface-muted prose-strong:text-on-surface prose-code:rounded prose-code:bg-surface-sunken
32
+ prose-code:px-1 prose-code:py-0.5 prose-code:text-on-surface
33
+ prose-pre:border prose-pre:border-outline
34
+ prose-pre:bg-surface-sunken
35
+ prose-li:text-on-surface-muted
36
+ prose-th:text-on-surface
37
+ prose-td:text-on-surface-muted
38
+ prose-hr:border-outline-faint
39
+ {className}
40
+ "
41
+ >
42
+ {@render children()}
43
+ </div>
@@ -0,0 +1,82 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ type RadioSize = "sm" | "md" | "lg";
6
+
7
+ interface Props {
8
+ value: string;
9
+ groupValue?: string;
10
+ name: string;
11
+ label: string;
12
+ description?: string;
13
+ disabled?: boolean;
14
+ size?: RadioSize;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ value,
20
+ groupValue = $bindable(""),
21
+ name,
22
+ label,
23
+ description,
24
+ disabled = false,
25
+ size = "md",
26
+ class: className = "",
27
+ }: Props = $props();
28
+
29
+ const sizeClasses: Record<RadioSize, { radio: string; dot: string; text: string }> = {
30
+ sm: { radio: "h-4 w-4", dot: "h-1.5 w-1.5", text: "text-sm" },
31
+ md: { radio: "h-5 w-5", dot: "h-2 w-2", text: "text-sm" },
32
+ lg: { radio: "h-6 w-6", dot: "h-2.5 w-2.5", text: "text-base" },
33
+ };
34
+
35
+ const isChecked = $derived(groupValue === value);
36
+ </script>
37
+
38
+ <label
39
+ class="inline-flex items-start gap-3 {disabled
40
+ ? 'cursor-not-allowed opacity-50'
41
+ : 'cursor-pointer'} {className}"
42
+ >
43
+ <div class="relative flex shrink-0 items-center justify-center">
44
+ <input
45
+ type="radio"
46
+ {name}
47
+ {value}
48
+ {disabled}
49
+ checked={isChecked}
50
+ onchange={() => (groupValue = value)}
51
+ class="
52
+ peer {sizeClasses[size].radio} duration-fast
53
+ cursor-pointer appearance-none rounded-full
54
+ border-2 border-outline-hover
55
+ bg-surface-raised transition-colors
56
+ checked:border-primary checked:bg-primary
57
+ hover:border-on-surface-faint
58
+ checked:hover:border-primary-hover checked:hover:bg-primary-hover
59
+ focus:ring-2 focus:ring-ring/20
60
+ focus:ring-offset-0 focus:outline-none
61
+ disabled:cursor-not-allowed disabled:hover:border-outline-hover
62
+ disabled:checked:hover:bg-primary
63
+ "
64
+ />
65
+ <div
66
+ class="duration-fast pointer-events-none absolute rounded-full bg-on-primary opacity-0 transition-opacity peer-checked:opacity-100 {sizeClasses[
67
+ size
68
+ ].dot}"
69
+ ></div>
70
+ </div>
71
+
72
+ {#if label || description}
73
+ <div class="flex flex-col gap-0.5">
74
+ {#if label}
75
+ <span class="{sizeClasses[size].text} font-medium text-on-surface">{label}</span>
76
+ {/if}
77
+ {#if description}
78
+ <span class="text-sm text-on-surface-muted">{description}</span>
79
+ {/if}
80
+ </div>
81
+ {/if}
82
+ </label>
@@ -0,0 +1,53 @@
1
+ <!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
2
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
3
+
4
+ <script lang="ts">
5
+ import type { Snippet } from "svelte";
6
+
7
+ type Orientation = "horizontal" | "vertical";
8
+
9
+ interface Props {
10
+ value?: string;
11
+ name: string;
12
+ label?: string;
13
+ error?: string;
14
+ hint?: string;
15
+ orientation?: Orientation;
16
+ children: Snippet;
17
+ class?: string;
18
+ }
19
+
20
+ let {
21
+ value = $bindable(""),
22
+ name: _name,
23
+ label,
24
+ error,
25
+ hint,
26
+ orientation = "vertical",
27
+ children,
28
+ class: className = "",
29
+ }: Props = $props();
30
+
31
+ const orientationClasses: Record<Orientation, string> = {
32
+ horizontal: "flex flex-row flex-wrap gap-x-6 gap-y-3",
33
+ vertical: "flex flex-col gap-3",
34
+ };
35
+ </script>
36
+
37
+ <fieldset class="space-y-3 {className}" role="radiogroup" aria-label={label}>
38
+ {#if label}
39
+ <legend class="text-sm font-medium text-on-surface-muted">{label}</legend>
40
+ {/if}
41
+
42
+ <div class={orientationClasses[orientation]}>
43
+ {@render children()}
44
+ </div>
45
+
46
+ {#if error}
47
+ <p class="animate-in slide-in-from-top-1 fade-in text-sm text-danger-600 duration-200">
48
+ {error}
49
+ </p>
50
+ {:else if hint}
51
+ <p class="text-sm text-on-surface-muted">{hint}</p>
52
+ {/if}
53
+ </fieldset>