@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.
- package/LICENSE +215 -0
- package/README.md +20 -0
- package/package.json +128 -0
- package/src/app.d.ts +8 -0
- package/src/app.html +12 -0
- package/src/astro/CTABand.astro +32 -0
- package/src/astro/Hero.astro +58 -0
- package/src/astro/SiteFooter.astro +47 -0
- package/src/astro/SiteHeader.astro +46 -0
- package/src/lib/actions/portal.ts +16 -0
- package/src/lib/assets/runwisp-logo.svg +16 -0
- package/src/lib/components/Accordion.svelte +20 -0
- package/src/lib/components/AccordionItem.svelte +74 -0
- package/src/lib/components/Alert.svelte +107 -0
- package/src/lib/components/AlertDialog.svelte +63 -0
- package/src/lib/components/Avatar.svelte +88 -0
- package/src/lib/components/Badge.svelte +131 -0
- package/src/lib/components/Breadcrumb.svelte +58 -0
- package/src/lib/components/Button.svelte +69 -0
- package/src/lib/components/Card.svelte +70 -0
- package/src/lib/components/Checkbox.svelte +90 -0
- package/src/lib/components/CodeBlock.svelte +66 -0
- package/src/lib/components/CronInput.svelte +130 -0
- package/src/lib/components/DataGrid.svelte +368 -0
- package/src/lib/components/Drawer.svelte +156 -0
- package/src/lib/components/Dropdown.svelte +195 -0
- package/src/lib/components/DurationInput.svelte +170 -0
- package/src/lib/components/EmptyState.svelte +46 -0
- package/src/lib/components/ErrorState.svelte +73 -0
- package/src/lib/components/FeatureCard.svelte +35 -0
- package/src/lib/components/FeatureGrid.svelte +37 -0
- package/src/lib/components/FormField.svelte +53 -0
- package/src/lib/components/Heading.svelte +46 -0
- package/src/lib/components/Input.svelte +114 -0
- package/src/lib/components/Kbd.svelte +31 -0
- package/src/lib/components/Link.svelte +56 -0
- package/src/lib/components/LinkButton.svelte +55 -0
- package/src/lib/components/LogConsole.svelte +424 -0
- package/src/lib/components/Logo.svelte +34 -0
- package/src/lib/components/Modal.svelte +138 -0
- package/src/lib/components/PageContainer.svelte +38 -0
- package/src/lib/components/Pagination.svelte +96 -0
- package/src/lib/components/Popover.svelte +130 -0
- package/src/lib/components/Progress.svelte +68 -0
- package/src/lib/components/Prose.svelte +43 -0
- package/src/lib/components/Radio.svelte +82 -0
- package/src/lib/components/RadioGroup.svelte +53 -0
- package/src/lib/components/Select.svelte +454 -0
- package/src/lib/components/Separator.svelte +30 -0
- package/src/lib/components/Skeleton.svelte +75 -0
- package/src/lib/components/Sparkline.svelte +58 -0
- package/src/lib/components/Spinner.svelte +44 -0
- package/src/lib/components/StatusIndicator.svelte +203 -0
- package/src/lib/components/Table.svelte +19 -0
- package/src/lib/components/TableBody.svelte +17 -0
- package/src/lib/components/TableCell.svelte +34 -0
- package/src/lib/components/TableHead.svelte +19 -0
- package/src/lib/components/TableRow.svelte +22 -0
- package/src/lib/components/Tabs.svelte +148 -0
- package/src/lib/components/Text.svelte +57 -0
- package/src/lib/components/Textarea.svelte +81 -0
- package/src/lib/components/ThemeToggle.svelte +47 -0
- package/src/lib/components/TimezoneSelect.svelte +127 -0
- package/src/lib/components/ToastContainer.svelte +84 -0
- package/src/lib/components/Toggle.svelte +84 -0
- package/src/lib/components/Tooltip.svelte +56 -0
- package/src/lib/components/button-styles.ts +51 -0
- package/src/lib/components/dashboard/RunDetailPanel.svelte +252 -0
- package/src/lib/components/dashboard/RunsList.svelte +331 -0
- package/src/lib/components/dashboard/run-helpers.ts +12 -0
- package/src/lib/components/dashboard/status-config.ts +141 -0
- package/src/lib/components/dashboard/types.ts +35 -0
- package/src/lib/index.ts +122 -0
- package/src/lib/log-console/LogCache.svelte.ts +93 -0
- package/src/lib/log-console/LogFetcher.svelte.ts +179 -0
- package/src/lib/log-console/types.ts +24 -0
- package/src/lib/theme-tokens.css +235 -0
- package/src/lib/theme.css +276 -0
- package/src/lib/utils/error.ts +18 -0
- package/src/lib/utils/format.ts +76 -0
- package/src/lib/utils/id.ts +6 -0
- package/src/lib/utils/toast.svelte.ts +87 -0
- package/src/lib/utils/toast.ts +60 -0
- package/src/routes/+layout.svelte +10 -0
- package/src/routes/+page.svelte +5 -0
- package/src/routes/layout.css +3 -0
- package/src/stories/Alert.stories.svelte +64 -0
- package/src/stories/Avatar.stories.svelte +76 -0
- package/src/stories/Badge.stories.svelte +76 -0
- package/src/stories/Breadcrumb.stories.svelte +64 -0
- package/src/stories/Button.stories.svelte +101 -0
- package/src/stories/Card.stories.svelte +106 -0
- package/src/stories/Checkbox.stories.svelte +77 -0
- package/src/stories/DataGrid.stories.svelte +130 -0
- package/src/stories/Dropdown.stories.svelte +81 -0
- package/src/stories/EmptyState.stories.svelte +74 -0
- package/src/stories/Input.stories.svelte +88 -0
- package/src/stories/Logo.stories.svelte +82 -0
- package/src/stories/Modal.stories.svelte +101 -0
- package/src/stories/Progress.stories.svelte +65 -0
- package/src/stories/Select.stories.svelte +158 -0
- package/src/stories/Tabs.stories.svelte +103 -0
- package/src/stories/Textarea.stories.svelte +45 -0
- package/src/stories/Toggle.stories.svelte +74 -0
- package/src/stories/Tooltip.stories.svelte +77 -0
- package/src/stories/Welcome.mdx +241 -0
- package/src/stories/assets/runwisp_c.svg +16 -0
- package/src/svelte-shims.d.ts +18 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/* Copyright (c) PoppyCake, s.r.o. SPDX-License-Identifier: Apache-2.0 */
|
|
2
|
+
|
|
3
|
+
@import "tailwindcss";
|
|
4
|
+
@plugin "@tailwindcss/forms";
|
|
5
|
+
@plugin "@tailwindcss/typography";
|
|
6
|
+
|
|
7
|
+
/* RunWisp Design System — Tailwind v4 entrypoint.
|
|
8
|
+
*
|
|
9
|
+
* Raw OKLch palette + semantic tokens live in ./theme-tokens.css (plain CSS),
|
|
10
|
+
* so apps/docs (Starlight, no Tailwind pipeline) can consume the same source.
|
|
11
|
+
* This file republishes those tokens under Tailwind's --color-* namespace so
|
|
12
|
+
* utilities like `bg-wisp-500`, `text-surface`, etc. generate.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
@import "./theme-tokens.css";
|
|
16
|
+
|
|
17
|
+
@import "@fontsource/tasa-orbiter/400.css";
|
|
18
|
+
@import "@fontsource/tasa-orbiter/500.css";
|
|
19
|
+
@import "@fontsource/tasa-orbiter/600.css";
|
|
20
|
+
@import "@fontsource/tasa-orbiter/700.css";
|
|
21
|
+
|
|
22
|
+
@theme {
|
|
23
|
+
/* Palette aliases — Tailwind utilities (bg-wisp-500, text-mist-900, …)
|
|
24
|
+
* resolve through to --rw-color-* declared in theme-tokens.css. */
|
|
25
|
+
--color-wisp-50: var(--rw-color-wisp-50);
|
|
26
|
+
--color-wisp-100: var(--rw-color-wisp-100);
|
|
27
|
+
--color-wisp-200: var(--rw-color-wisp-200);
|
|
28
|
+
--color-wisp-300: var(--rw-color-wisp-300);
|
|
29
|
+
--color-wisp-400: var(--rw-color-wisp-400);
|
|
30
|
+
--color-wisp-500: var(--rw-color-wisp-500);
|
|
31
|
+
--color-wisp-600: var(--rw-color-wisp-600);
|
|
32
|
+
--color-wisp-700: var(--rw-color-wisp-700);
|
|
33
|
+
--color-wisp-800: var(--rw-color-wisp-800);
|
|
34
|
+
--color-wisp-900: var(--rw-color-wisp-900);
|
|
35
|
+
--color-wisp-950: var(--rw-color-wisp-950);
|
|
36
|
+
|
|
37
|
+
--color-aurora-50: var(--rw-color-aurora-50);
|
|
38
|
+
--color-aurora-100: var(--rw-color-aurora-100);
|
|
39
|
+
--color-aurora-200: var(--rw-color-aurora-200);
|
|
40
|
+
--color-aurora-300: var(--rw-color-aurora-300);
|
|
41
|
+
--color-aurora-400: var(--rw-color-aurora-400);
|
|
42
|
+
--color-aurora-500: var(--rw-color-aurora-500);
|
|
43
|
+
--color-aurora-600: var(--rw-color-aurora-600);
|
|
44
|
+
--color-aurora-700: var(--rw-color-aurora-700);
|
|
45
|
+
--color-aurora-800: var(--rw-color-aurora-800);
|
|
46
|
+
--color-aurora-900: var(--rw-color-aurora-900);
|
|
47
|
+
|
|
48
|
+
--color-mist-50: var(--rw-color-mist-50);
|
|
49
|
+
--color-mist-100: var(--rw-color-mist-100);
|
|
50
|
+
--color-mist-200: var(--rw-color-mist-200);
|
|
51
|
+
--color-mist-300: var(--rw-color-mist-300);
|
|
52
|
+
--color-mist-400: var(--rw-color-mist-400);
|
|
53
|
+
--color-mist-500: var(--rw-color-mist-500);
|
|
54
|
+
--color-mist-600: var(--rw-color-mist-600);
|
|
55
|
+
--color-mist-700: var(--rw-color-mist-700);
|
|
56
|
+
--color-mist-800: var(--rw-color-mist-800);
|
|
57
|
+
--color-mist-900: var(--rw-color-mist-900);
|
|
58
|
+
--color-mist-950: var(--rw-color-mist-950);
|
|
59
|
+
|
|
60
|
+
--color-success-50: var(--rw-color-success-50);
|
|
61
|
+
--color-success-100: var(--rw-color-success-100);
|
|
62
|
+
--color-success-200: var(--rw-color-success-200);
|
|
63
|
+
--color-success-300: var(--rw-color-success-300);
|
|
64
|
+
--color-success-400: var(--rw-color-success-400);
|
|
65
|
+
--color-success-500: var(--rw-color-success-500);
|
|
66
|
+
--color-success-600: var(--rw-color-success-600);
|
|
67
|
+
--color-success-700: var(--rw-color-success-700);
|
|
68
|
+
|
|
69
|
+
--color-danger-50: var(--rw-color-danger-50);
|
|
70
|
+
--color-danger-100: var(--rw-color-danger-100);
|
|
71
|
+
--color-danger-200: var(--rw-color-danger-200);
|
|
72
|
+
--color-danger-300: var(--rw-color-danger-300);
|
|
73
|
+
--color-danger-400: var(--rw-color-danger-400);
|
|
74
|
+
--color-danger-500: var(--rw-color-danger-500);
|
|
75
|
+
--color-danger-600: var(--rw-color-danger-600);
|
|
76
|
+
--color-danger-700: var(--rw-color-danger-700);
|
|
77
|
+
|
|
78
|
+
--color-warning-50: var(--rw-color-warning-50);
|
|
79
|
+
--color-warning-100: var(--rw-color-warning-100);
|
|
80
|
+
--color-warning-200: var(--rw-color-warning-200);
|
|
81
|
+
--color-warning-300: var(--rw-color-warning-300);
|
|
82
|
+
--color-warning-400: var(--rw-color-warning-400);
|
|
83
|
+
--color-warning-500: var(--rw-color-warning-500);
|
|
84
|
+
--color-warning-600: var(--rw-color-warning-600);
|
|
85
|
+
--color-warning-700: var(--rw-color-warning-700);
|
|
86
|
+
|
|
87
|
+
/* Typography */
|
|
88
|
+
--font-sans: "TASA Orbiter", system-ui, -apple-system, sans-serif;
|
|
89
|
+
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
|
90
|
+
|
|
91
|
+
/* Font sizes */
|
|
92
|
+
--text-2xs: 0.625rem;
|
|
93
|
+
--text-xs: 0.75rem;
|
|
94
|
+
--text-sm: 0.875rem;
|
|
95
|
+
--text-base: 1rem;
|
|
96
|
+
--text-lg: 1.125rem;
|
|
97
|
+
--text-xl: 1.25rem;
|
|
98
|
+
--text-2xl: 1.5rem;
|
|
99
|
+
--text-3xl: 1.875rem;
|
|
100
|
+
--text-4xl: 2.25rem;
|
|
101
|
+
|
|
102
|
+
/* Spacing scale */
|
|
103
|
+
--spacing-px: 1px;
|
|
104
|
+
--spacing-0: 0;
|
|
105
|
+
--spacing-0\.5: 0.125rem;
|
|
106
|
+
--spacing-1: 0.25rem;
|
|
107
|
+
--spacing-1\.5: 0.375rem;
|
|
108
|
+
--spacing-2: 0.5rem;
|
|
109
|
+
--spacing-2\.5: 0.625rem;
|
|
110
|
+
--spacing-3: 0.75rem;
|
|
111
|
+
--spacing-3\.5: 0.875rem;
|
|
112
|
+
--spacing-4: 1rem;
|
|
113
|
+
--spacing-5: 1.25rem;
|
|
114
|
+
--spacing-6: 1.5rem;
|
|
115
|
+
--spacing-7: 1.75rem;
|
|
116
|
+
--spacing-8: 2rem;
|
|
117
|
+
--spacing-9: 2.25rem;
|
|
118
|
+
--spacing-10: 2.5rem;
|
|
119
|
+
--spacing-12: 3rem;
|
|
120
|
+
--spacing-14: 3.5rem;
|
|
121
|
+
--spacing-16: 4rem;
|
|
122
|
+
--spacing-20: 5rem;
|
|
123
|
+
--spacing-24: 6rem;
|
|
124
|
+
|
|
125
|
+
/* Border radius */
|
|
126
|
+
--radius-none: 0;
|
|
127
|
+
--radius-sm: 0.25rem;
|
|
128
|
+
--radius-md: 0.375rem;
|
|
129
|
+
--radius-lg: 0.5rem;
|
|
130
|
+
--radius-xl: 0.75rem;
|
|
131
|
+
--radius-2xl: 1rem;
|
|
132
|
+
--radius-3xl: 1.5rem;
|
|
133
|
+
--radius-full: 9999px;
|
|
134
|
+
|
|
135
|
+
/* Shadows — soft, ethereal */
|
|
136
|
+
--shadow-xs: 0 1px 2px 0 oklch(0.18 0.02 280 / 0.05);
|
|
137
|
+
--shadow-sm:
|
|
138
|
+
0 1px 3px 0 oklch(0.18 0.02 280 / 0.08), 0 1px 2px -1px oklch(0.18 0.02 280 / 0.08);
|
|
139
|
+
--shadow-md:
|
|
140
|
+
0 4px 6px -1px oklch(0.18 0.02 280 / 0.08), 0 2px 4px -2px oklch(0.18 0.02 280 / 0.08);
|
|
141
|
+
--shadow-lg:
|
|
142
|
+
0 10px 15px -3px oklch(0.18 0.02 280 / 0.08), 0 4px 6px -4px oklch(0.18 0.02 280 / 0.08);
|
|
143
|
+
--shadow-xl:
|
|
144
|
+
0 20px 25px -5px oklch(0.18 0.02 280 / 0.1), 0 8px 10px -6px oklch(0.18 0.02 280 / 0.1);
|
|
145
|
+
|
|
146
|
+
/* Glow effects for wisp theme */
|
|
147
|
+
--shadow-glow-sm: 0 0 10px oklch(0.58 0.18 270 / 0.15);
|
|
148
|
+
--shadow-glow-md: 0 0 20px oklch(0.58 0.18 270 / 0.2);
|
|
149
|
+
--shadow-glow-lg: 0 0 30px oklch(0.58 0.18 270 / 0.25);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Semantic Design Tokens — Tailwind utility aliases.
|
|
153
|
+
* Components use `bg-surface`, `text-primary`, etc.; the values live in
|
|
154
|
+
* theme-tokens.css under the --rw-* namespace and flip on light/dark. */
|
|
155
|
+
|
|
156
|
+
@theme {
|
|
157
|
+
/* Surfaces */
|
|
158
|
+
--color-surface: var(--rw-surface);
|
|
159
|
+
--color-surface-raised: var(--rw-surface-raised);
|
|
160
|
+
--color-surface-overlay: var(--rw-surface-overlay);
|
|
161
|
+
--color-surface-sunken: var(--rw-surface-sunken);
|
|
162
|
+
/* On-surface text */
|
|
163
|
+
--color-on-surface: var(--rw-on-surface);
|
|
164
|
+
--color-on-surface-muted: var(--rw-on-surface-muted);
|
|
165
|
+
--color-on-surface-faint: var(--rw-on-surface-faint);
|
|
166
|
+
/* Borders */
|
|
167
|
+
--color-outline: var(--rw-outline);
|
|
168
|
+
--color-outline-hover: var(--rw-outline-hover);
|
|
169
|
+
--color-outline-faint: var(--rw-outline-faint);
|
|
170
|
+
--color-ring: var(--rw-ring);
|
|
171
|
+
--color-backdrop: var(--rw-backdrop);
|
|
172
|
+
/* Primary */
|
|
173
|
+
--color-primary: var(--rw-primary);
|
|
174
|
+
--color-primary-hover: var(--rw-primary-hover);
|
|
175
|
+
--color-primary-active: var(--rw-primary-active);
|
|
176
|
+
--color-on-primary: var(--rw-on-primary);
|
|
177
|
+
--color-primary-soft: var(--rw-primary-soft);
|
|
178
|
+
--color-primary-soft-text: var(--rw-primary-soft-text);
|
|
179
|
+
/* Danger */
|
|
180
|
+
--color-danger-surface: var(--rw-danger);
|
|
181
|
+
--color-danger-hover: var(--rw-danger-hover);
|
|
182
|
+
--color-danger-active: var(--rw-danger-active);
|
|
183
|
+
--color-on-danger: var(--rw-on-danger);
|
|
184
|
+
--color-danger-soft: var(--rw-danger-soft);
|
|
185
|
+
--color-danger-soft-text: var(--rw-danger-soft-text);
|
|
186
|
+
/* Success */
|
|
187
|
+
--color-success-surface: var(--rw-success);
|
|
188
|
+
--color-success-hover: var(--rw-success-hover);
|
|
189
|
+
--color-on-success: var(--rw-on-success);
|
|
190
|
+
--color-success-soft: var(--rw-success-soft);
|
|
191
|
+
--color-success-soft-text: var(--rw-success-soft-text);
|
|
192
|
+
/* Warning */
|
|
193
|
+
--color-warning-surface: var(--rw-warning);
|
|
194
|
+
--color-warning-hover: var(--rw-warning-hover);
|
|
195
|
+
--color-on-warning: var(--rw-on-warning);
|
|
196
|
+
--color-warning-soft: var(--rw-warning-soft);
|
|
197
|
+
--color-warning-soft-text: var(--rw-warning-soft-text);
|
|
198
|
+
/* Info */
|
|
199
|
+
--color-info: var(--rw-info);
|
|
200
|
+
--color-info-hover: var(--rw-info-hover);
|
|
201
|
+
--color-on-info: var(--rw-on-info);
|
|
202
|
+
--color-info-soft: var(--rw-info-soft);
|
|
203
|
+
--color-info-soft-text: var(--rw-info-soft-text);
|
|
204
|
+
|
|
205
|
+
/* Transition durations */
|
|
206
|
+
--duration-fast: 100ms;
|
|
207
|
+
--duration-normal: 200ms;
|
|
208
|
+
--duration-slow: 300ms;
|
|
209
|
+
|
|
210
|
+
/* Easings */
|
|
211
|
+
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
|
|
212
|
+
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
213
|
+
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Base styles */
|
|
217
|
+
*,
|
|
218
|
+
*::before,
|
|
219
|
+
*::after {
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
html {
|
|
224
|
+
font-family: var(--font-sans);
|
|
225
|
+
font-size: 16px;
|
|
226
|
+
line-height: 1.5;
|
|
227
|
+
-webkit-font-smoothing: antialiased;
|
|
228
|
+
-moz-osx-font-smoothing: grayscale;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
body {
|
|
232
|
+
margin: 0;
|
|
233
|
+
padding: 0;
|
|
234
|
+
background-color: var(--rw-surface);
|
|
235
|
+
color: var(--rw-on-surface);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Utility classes for the design system */
|
|
239
|
+
.rw-text-gradient {
|
|
240
|
+
background: linear-gradient(135deg, var(--rw-color-wisp-600), var(--rw-color-aurora-500));
|
|
241
|
+
-webkit-background-clip: text;
|
|
242
|
+
-webkit-text-fill-color: transparent;
|
|
243
|
+
background-clip: text;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.rw-border-subtle {
|
|
247
|
+
border-color: var(--rw-color-mist-200);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.rw-bg-glass {
|
|
251
|
+
background: var(--rw-glass);
|
|
252
|
+
backdrop-filter: var(--rw-glass-blur);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Legacy aliases — prefer .rw-bg-glass which adapts to light/dark */
|
|
256
|
+
.rw-bg-glass-dark {
|
|
257
|
+
background: var(--rw-glass);
|
|
258
|
+
backdrop-filter: var(--rw-glass-blur);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Utilities */
|
|
262
|
+
.spinner {
|
|
263
|
+
animation: spin 1s linear infinite;
|
|
264
|
+
}
|
|
265
|
+
@keyframes spin {
|
|
266
|
+
to {
|
|
267
|
+
transform: rotate(360deg);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.btn-scale {
|
|
272
|
+
transition: all 0.15s ease-in-out;
|
|
273
|
+
}
|
|
274
|
+
.btn-scale:active {
|
|
275
|
+
transform: scale(0.97);
|
|
276
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts a human-readable message from an unknown caught value.
|
|
6
|
+
*/
|
|
7
|
+
export function extractErrorMessage(
|
|
8
|
+
err: unknown,
|
|
9
|
+
fallback = "An unexpected error occurred",
|
|
10
|
+
): string {
|
|
11
|
+
if (err instanceof Error) {
|
|
12
|
+
return err.message || fallback;
|
|
13
|
+
}
|
|
14
|
+
if (typeof err === "string") {
|
|
15
|
+
return err || fallback;
|
|
16
|
+
}
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { formatDistanceToNow } from "date-fns";
|
|
5
|
+
|
|
6
|
+
export function formatBytes(bytes: number): string {
|
|
7
|
+
const units: [string, ...string[]] = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
8
|
+
let value = bytes;
|
|
9
|
+
let unitIndex = 0;
|
|
10
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
11
|
+
value /= 1024;
|
|
12
|
+
unitIndex++;
|
|
13
|
+
}
|
|
14
|
+
const unit = units[unitIndex] ?? "B";
|
|
15
|
+
const digits = value < 10 && unitIndex > 0 ? 1 : 0;
|
|
16
|
+
return `${new Intl.NumberFormat("en", { maximumFractionDigits: digits }).format(value)} ${unit}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatRelativeTime(dateStr: string | Date): string {
|
|
20
|
+
return formatDistanceToNow(new Date(dateStr), { addSuffix: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatRelativeTimeWithAbsolute(dateStr: string | Date): string {
|
|
24
|
+
const date = new Date(dateStr);
|
|
25
|
+
const relative = formatDistanceToNow(date, { addSuffix: true }).replace("about ", "");
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const diffMs = Math.abs(now.getTime() - date.getTime());
|
|
28
|
+
const diffDays = diffMs / (1000 * 60 * 60 * 24);
|
|
29
|
+
|
|
30
|
+
let absolute: string;
|
|
31
|
+
if (diffDays < 1) {
|
|
32
|
+
absolute = date.toLocaleTimeString(undefined, {
|
|
33
|
+
hour: "numeric",
|
|
34
|
+
minute: "2-digit",
|
|
35
|
+
hour12: false,
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
absolute = date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `${relative} (${absolute})`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatDateTime(dateStr: string): string {
|
|
45
|
+
return new Date(dateStr).toLocaleString(undefined, {
|
|
46
|
+
year: "numeric",
|
|
47
|
+
month: "short",
|
|
48
|
+
day: "numeric",
|
|
49
|
+
hour: "numeric",
|
|
50
|
+
minute: "2-digit",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatDuration(ms: number): string {
|
|
55
|
+
if (ms < 1000) return String(ms) + "ms";
|
|
56
|
+
const s = Math.floor(ms / 1000);
|
|
57
|
+
if (s < 60) return String(s) + "s";
|
|
58
|
+
const m = Math.floor(s / 60);
|
|
59
|
+
const rem = s % 60;
|
|
60
|
+
if (m < 60) return rem > 0 ? String(m) + "m " + String(rem) + "s" : String(m) + "m";
|
|
61
|
+
const h = Math.floor(m / 60);
|
|
62
|
+
const remM = m % 60;
|
|
63
|
+
return remM > 0 ? String(h) + "h " + String(remM) + "m" : String(h) + "h";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatFullDateTime(date: Date | string): string {
|
|
67
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
68
|
+
year: "numeric",
|
|
69
|
+
month: "short",
|
|
70
|
+
day: "2-digit",
|
|
71
|
+
hour: "2-digit",
|
|
72
|
+
minute: "2-digit",
|
|
73
|
+
second: "2-digit",
|
|
74
|
+
hour12: false,
|
|
75
|
+
}).format(new Date(date));
|
|
76
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { generateUlid } from "@runwisp/common";
|
|
5
|
+
|
|
6
|
+
export type ToastType = "success" | "error" | "warning" | "info";
|
|
7
|
+
|
|
8
|
+
export interface Toast {
|
|
9
|
+
id: string;
|
|
10
|
+
type: ToastType;
|
|
11
|
+
message: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_DURATION = 5000;
|
|
16
|
+
|
|
17
|
+
class ToastStore {
|
|
18
|
+
items = $state<Toast[]>([]);
|
|
19
|
+
private timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
20
|
+
|
|
21
|
+
add(type: ToastType, message: string, duration = DEFAULT_DURATION): string {
|
|
22
|
+
const existing = this.items.find((t) => t.type === type && t.message === message);
|
|
23
|
+
if (existing) {
|
|
24
|
+
this.refreshTimer(existing.id, duration);
|
|
25
|
+
return existing.id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const id = generateUlid();
|
|
29
|
+
this.items = [...this.items, { id, type, message, duration }];
|
|
30
|
+
if (duration > 0) {
|
|
31
|
+
this.scheduleRemoval(id, duration);
|
|
32
|
+
}
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private scheduleRemoval(id: string, duration: number) {
|
|
37
|
+
this.timers.set(
|
|
38
|
+
id,
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
this.remove(id);
|
|
41
|
+
}, duration),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private refreshTimer(id: string, duration: number) {
|
|
46
|
+
const existing = this.timers.get(id);
|
|
47
|
+
if (existing) clearTimeout(existing);
|
|
48
|
+
if (duration > 0) {
|
|
49
|
+
this.scheduleRemoval(id, duration);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
remove(id: string) {
|
|
54
|
+
const timer = this.timers.get(id);
|
|
55
|
+
if (timer) {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
this.timers.delete(id);
|
|
58
|
+
}
|
|
59
|
+
this.items = this.items.filter((t) => t.id !== id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
success(message: string, duration?: number): string {
|
|
63
|
+
return this.add("success", message, duration);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
error(message: string, duration?: number): string {
|
|
67
|
+
return this.add("error", message, duration);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
warning(message: string, duration?: number): string {
|
|
71
|
+
return this.add("warning", message, duration);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
info(message: string, duration?: number): string {
|
|
75
|
+
return this.add("info", message, duration);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clear() {
|
|
79
|
+
for (const timer of this.timers.values()) {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
}
|
|
82
|
+
this.timers.clear();
|
|
83
|
+
this.items = [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const toast = new ToastStore();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { generateUlid } from "@runwisp/common";
|
|
5
|
+
|
|
6
|
+
export type ToastType = "success" | "error" | "warning" | "info";
|
|
7
|
+
|
|
8
|
+
export interface Toast {
|
|
9
|
+
id: string;
|
|
10
|
+
type: ToastType;
|
|
11
|
+
message: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_DURATION = 5000;
|
|
16
|
+
|
|
17
|
+
class ToastStore {
|
|
18
|
+
items = $state<Toast[]>([]);
|
|
19
|
+
|
|
20
|
+
add(type: ToastType, message: string, duration = DEFAULT_DURATION): string {
|
|
21
|
+
const id = generateUlid();
|
|
22
|
+
const t: Toast = { id, type, message, duration };
|
|
23
|
+
|
|
24
|
+
this.items = [...this.items, t];
|
|
25
|
+
|
|
26
|
+
if (duration > 0) {
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
this.remove(id);
|
|
29
|
+
}, duration);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
remove(id: string) {
|
|
36
|
+
this.items = this.items.filter((t) => t.id !== id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
success(message: string, duration?: number): string {
|
|
40
|
+
return this.add("success", message, duration);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
error(message: string, duration?: number): string {
|
|
44
|
+
return this.add("error", message, duration);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
warning(message: string, duration?: number): string {
|
|
48
|
+
return this.add("warning", message, duration);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
info(message: string, duration?: number): string {
|
|
52
|
+
return this.add("info", message, duration);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
clear() {
|
|
56
|
+
this.items = [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const toast = new ToastStore();
|
|
@@ -0,0 +1,64 @@
|
|
|
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 Alert from "$lib/components/Alert.svelte";
|
|
7
|
+
import Button from "$lib/components/Button.svelte";
|
|
8
|
+
|
|
9
|
+
const { Story } = defineMeta({
|
|
10
|
+
title: "Core/Alert",
|
|
11
|
+
component: Alert,
|
|
12
|
+
tags: ["autodocs"],
|
|
13
|
+
argTypes: {
|
|
14
|
+
variant: {
|
|
15
|
+
control: "select",
|
|
16
|
+
options: ["info", "success", "warning", "danger"],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<Story name="Info" args={{ variant: "info", title: "Scheduled maintenance" }}>
|
|
23
|
+
The system will undergo maintenance on January 20th from 2:00 AM to 4:00 AM UTC.
|
|
24
|
+
</Story>
|
|
25
|
+
|
|
26
|
+
<Story name="Success" args={{ variant: "success", title: "Task completed successfully" }}>
|
|
27
|
+
The backup task has finished processing 1,234 records.
|
|
28
|
+
</Story>
|
|
29
|
+
|
|
30
|
+
<Story name="Warning" args={{ variant: "warning", title: "Approaching rate limit" }}>
|
|
31
|
+
You have used 80% of your monthly task executions.
|
|
32
|
+
</Story>
|
|
33
|
+
|
|
34
|
+
<Story name="Danger" args={{ variant: "danger", title: "Task execution failed" }}>
|
|
35
|
+
The sync-data task failed after 3 retry attempts. Check logs for details.
|
|
36
|
+
</Story>
|
|
37
|
+
|
|
38
|
+
<Story
|
|
39
|
+
name="Dismissible"
|
|
40
|
+
args={{ variant: "info", title: "New feature available", dismissible: true }}
|
|
41
|
+
>
|
|
42
|
+
You can now schedule tasks using natural language expressions.
|
|
43
|
+
</Story>
|
|
44
|
+
|
|
45
|
+
<Story name="With Actions" asChild>
|
|
46
|
+
<Alert variant="danger" title="Task failed">
|
|
47
|
+
The scheduled task "backup-database" has failed.
|
|
48
|
+
{#snippet actions()}
|
|
49
|
+
<div class="flex gap-2">
|
|
50
|
+
<Button variant="danger" size="sm">View Logs</Button>
|
|
51
|
+
<Button variant="ghost" size="sm">Retry Task</Button>
|
|
52
|
+
</div>
|
|
53
|
+
{/snippet}
|
|
54
|
+
</Alert>
|
|
55
|
+
</Story>
|
|
56
|
+
|
|
57
|
+
<Story name="All Variants" asChild>
|
|
58
|
+
<div class="max-w-lg space-y-4">
|
|
59
|
+
<Alert variant="info" title="Information">This is an informational message.</Alert>
|
|
60
|
+
<Alert variant="success" title="Success">Operation completed successfully.</Alert>
|
|
61
|
+
<Alert variant="warning" title="Warning">Please review before proceeding.</Alert>
|
|
62
|
+
<Alert variant="danger" title="Error">An error has occurred.</Alert>
|
|
63
|
+
</div>
|
|
64
|
+
</Story>
|
|
@@ -0,0 +1,76 @@
|
|
|
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 Avatar from "$lib/components/Avatar.svelte";
|
|
7
|
+
|
|
8
|
+
const { Story } = defineMeta({
|
|
9
|
+
title: "Core/Avatar",
|
|
10
|
+
component: Avatar,
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: {
|
|
14
|
+
control: "select",
|
|
15
|
+
options: ["xs", "sm", "md", "lg", "xl"],
|
|
16
|
+
},
|
|
17
|
+
status: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: [undefined, "online", "offline", "busy", "away"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<Story name="Default" args={{ name: "John Doe" }} />
|
|
26
|
+
|
|
27
|
+
<Story
|
|
28
|
+
name="With Image"
|
|
29
|
+
args={{
|
|
30
|
+
name: "Jane Smith",
|
|
31
|
+
src: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop",
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<Story name="Sizes" asChild>
|
|
36
|
+
<div class="flex items-center gap-4">
|
|
37
|
+
<Avatar name="John Doe" size="xs" />
|
|
38
|
+
<Avatar name="John Doe" size="sm" />
|
|
39
|
+
<Avatar name="John Doe" size="md" />
|
|
40
|
+
<Avatar name="John Doe" size="lg" />
|
|
41
|
+
<Avatar name="John Doe" size="xl" />
|
|
42
|
+
</div>
|
|
43
|
+
</Story>
|
|
44
|
+
|
|
45
|
+
<Story name="With Status" asChild>
|
|
46
|
+
<div class="flex items-center gap-4">
|
|
47
|
+
<Avatar name="Online User" status="online" />
|
|
48
|
+
<Avatar name="Offline User" status="offline" />
|
|
49
|
+
<Avatar name="Busy User" status="busy" />
|
|
50
|
+
<Avatar name="Away User" status="away" />
|
|
51
|
+
</div>
|
|
52
|
+
</Story>
|
|
53
|
+
|
|
54
|
+
<Story name="Different Names" asChild>
|
|
55
|
+
<div class="flex items-center gap-4">
|
|
56
|
+
<Avatar name="Alice" />
|
|
57
|
+
<Avatar name="Bob Chen" />
|
|
58
|
+
<Avatar name="Charlie Davis" />
|
|
59
|
+
<Avatar name="Diana Evans" />
|
|
60
|
+
<Avatar name="Eve Franklin" />
|
|
61
|
+
</div>
|
|
62
|
+
</Story>
|
|
63
|
+
|
|
64
|
+
<Story name="Team Members" asChild>
|
|
65
|
+
<div class="flex items-center -space-x-3">
|
|
66
|
+
<Avatar name="Alice Johnson" size="md" />
|
|
67
|
+
<Avatar name="Bob Smith" size="md" />
|
|
68
|
+
<Avatar name="Charlie Brown" size="md" />
|
|
69
|
+
<Avatar name="Diana Ross" size="md" />
|
|
70
|
+
<div
|
|
71
|
+
class="flex h-10 w-10 items-center justify-center rounded-full bg-mist-200 text-sm font-medium text-mist-600 ring-2 ring-white"
|
|
72
|
+
>
|
|
73
|
+
+5
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</Story>
|