@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,74 @@
|
|
|
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 { ChevronDown } from "@lucide/svelte";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title?: Snippet | string;
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
open?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
title,
|
|
18
|
+
children,
|
|
19
|
+
open = $bindable(false),
|
|
20
|
+
disabled = false,
|
|
21
|
+
class: className = "",
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
function toggle() {
|
|
25
|
+
if (!disabled) {
|
|
26
|
+
open = !open;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isSnippet = $derived(typeof title === "function");
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="group {className}">
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onclick={toggle}
|
|
37
|
+
{disabled}
|
|
38
|
+
class="
|
|
39
|
+
flex w-full items-center justify-between gap-3 py-4 text-left text-sm font-medium
|
|
40
|
+
text-on-surface transition-colors
|
|
41
|
+
hover:text-on-surface-muted
|
|
42
|
+
disabled:cursor-not-allowed disabled:opacity-50
|
|
43
|
+
"
|
|
44
|
+
aria-expanded={open}
|
|
45
|
+
>
|
|
46
|
+
<span class="flex-1">
|
|
47
|
+
{#if isSnippet && title}
|
|
48
|
+
{@render (title as Snippet)()}
|
|
49
|
+
{:else if title}
|
|
50
|
+
{title}
|
|
51
|
+
{/if}
|
|
52
|
+
</span>
|
|
53
|
+
<ChevronDown
|
|
54
|
+
size={16}
|
|
55
|
+
class="shrink-0 text-on-surface-faint transition-transform duration-200 {open
|
|
56
|
+
? 'rotate-180'
|
|
57
|
+
: ''}"
|
|
58
|
+
/>
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
class="grid transition-[grid-template-rows] duration-200 ease-out {open
|
|
63
|
+
? 'grid-rows-[1fr]'
|
|
64
|
+
: 'grid-rows-[0fr]'}"
|
|
65
|
+
>
|
|
66
|
+
<div class="overflow-hidden">
|
|
67
|
+
{#if children}
|
|
68
|
+
<div class="pb-4 text-sm text-on-surface-muted">
|
|
69
|
+
{@render children()}
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
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 { Info, CircleCheckBig, TriangleAlert, CircleX, X } from "@lucide/svelte";
|
|
7
|
+
|
|
8
|
+
type AlertVariant = "info" | "success" | "warning" | "danger";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
variant?: AlertVariant;
|
|
12
|
+
title?: string;
|
|
13
|
+
dismissible?: boolean;
|
|
14
|
+
onDismiss?: () => void;
|
|
15
|
+
icon?: Snippet;
|
|
16
|
+
actions?: Snippet;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
variant = "info",
|
|
23
|
+
title,
|
|
24
|
+
dismissible = false,
|
|
25
|
+
onDismiss,
|
|
26
|
+
icon,
|
|
27
|
+
actions,
|
|
28
|
+
children,
|
|
29
|
+
class: className = "",
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const variantConfig: Record<
|
|
33
|
+
AlertVariant,
|
|
34
|
+
{ bg: string; border: string; icon: typeof Info; iconColor: string }
|
|
35
|
+
> = {
|
|
36
|
+
info: {
|
|
37
|
+
bg: "bg-info-soft",
|
|
38
|
+
border: "border-aurora-200",
|
|
39
|
+
icon: Info,
|
|
40
|
+
iconColor: "text-info",
|
|
41
|
+
},
|
|
42
|
+
success: {
|
|
43
|
+
bg: "bg-success-soft",
|
|
44
|
+
border: "border-success-200",
|
|
45
|
+
icon: CircleCheckBig,
|
|
46
|
+
iconColor: "text-success-surface",
|
|
47
|
+
},
|
|
48
|
+
warning: {
|
|
49
|
+
bg: "bg-warning-soft",
|
|
50
|
+
border: "border-warning-200",
|
|
51
|
+
icon: TriangleAlert,
|
|
52
|
+
iconColor: "text-warning-surface",
|
|
53
|
+
},
|
|
54
|
+
danger: {
|
|
55
|
+
bg: "bg-danger-soft",
|
|
56
|
+
border: "border-danger-200",
|
|
57
|
+
icon: CircleX,
|
|
58
|
+
iconColor: "text-danger-surface",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const config = $derived(variantConfig[variant]);
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<div
|
|
66
|
+
class="
|
|
67
|
+
flex gap-3 rounded-xl border p-4
|
|
68
|
+
{config.bg} {config.border}
|
|
69
|
+
{className}
|
|
70
|
+
"
|
|
71
|
+
role="alert"
|
|
72
|
+
>
|
|
73
|
+
<div class="shrink-0">
|
|
74
|
+
{#if icon}
|
|
75
|
+
{@render icon()}
|
|
76
|
+
{:else}
|
|
77
|
+
{@const IconComponent = config.icon}
|
|
78
|
+
<IconComponent size={20} class={config.iconColor} />
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div class="min-w-0 flex-1">
|
|
83
|
+
{#if title}
|
|
84
|
+
<h4 class="mb-1 text-sm font-semibold text-on-surface">{title}</h4>
|
|
85
|
+
{/if}
|
|
86
|
+
{#if children}
|
|
87
|
+
<div class="text-sm text-on-surface-muted">
|
|
88
|
+
{@render children()}
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
{#if actions}
|
|
92
|
+
<div class="mt-3">
|
|
93
|
+
{@render actions()}
|
|
94
|
+
</div>
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{#if dismissible}
|
|
99
|
+
<button
|
|
100
|
+
onclick={onDismiss}
|
|
101
|
+
class="shrink-0 rounded-lg p-1 text-on-surface-faint transition-colors hover:bg-surface-raised/50 hover:text-on-surface-muted"
|
|
102
|
+
aria-label="Dismiss"
|
|
103
|
+
>
|
|
104
|
+
<X size={16} />
|
|
105
|
+
</button>
|
|
106
|
+
{/if}
|
|
107
|
+
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
|
|
2
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
3
|
+
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import Modal from "./Modal.svelte";
|
|
6
|
+
import Button from "./Button.svelte";
|
|
7
|
+
|
|
8
|
+
type AlertVariant = "danger" | "warning" | "info";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
open?: boolean;
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string | undefined;
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
cancelLabel?: string;
|
|
16
|
+
variant?: AlertVariant;
|
|
17
|
+
onConfirm?: () => void;
|
|
18
|
+
onCancel?: () => void;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
open = $bindable(false),
|
|
25
|
+
title,
|
|
26
|
+
description,
|
|
27
|
+
confirmLabel = "Confirm",
|
|
28
|
+
cancelLabel = "Cancel",
|
|
29
|
+
variant = "danger",
|
|
30
|
+
onConfirm,
|
|
31
|
+
onCancel,
|
|
32
|
+
loading = false,
|
|
33
|
+
class: className = "",
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
const confirmVariantMap: Record<AlertVariant, "danger" | "primary"> = {
|
|
37
|
+
danger: "danger",
|
|
38
|
+
warning: "primary",
|
|
39
|
+
info: "primary",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function handleCancel() {
|
|
43
|
+
open = false;
|
|
44
|
+
onCancel?.();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleConfirm() {
|
|
48
|
+
onConfirm?.();
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<Modal bind:open {title} {description} size="sm" closable onClose={handleCancel} class={className}>
|
|
53
|
+
{#snippet footer()}
|
|
54
|
+
<div class="flex items-center justify-end gap-3">
|
|
55
|
+
<Button variant="secondary" onclick={handleCancel} disabled={loading}>
|
|
56
|
+
{cancelLabel}
|
|
57
|
+
</Button>
|
|
58
|
+
<Button variant={confirmVariantMap[variant]} onclick={handleConfirm} {loading}>
|
|
59
|
+
{confirmLabel}
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
{/snippet}
|
|
63
|
+
</Modal>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: PoppyCake, s.r.o. -->
|
|
2
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
3
|
+
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
interface Props {
|
|
6
|
+
name?: string;
|
|
7
|
+
src?: string;
|
|
8
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
9
|
+
status?: "online" | "offline" | "busy" | "away";
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { name = "", src, size = "md", status, class: className = "" }: Props = $props();
|
|
14
|
+
|
|
15
|
+
const initials = $derived(
|
|
16
|
+
name
|
|
17
|
+
.split(" ")
|
|
18
|
+
.map((n) => n[0])
|
|
19
|
+
.slice(0, 2)
|
|
20
|
+
.join("")
|
|
21
|
+
.toUpperCase(),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const sizeClasses: Record<string, string> = {
|
|
25
|
+
xs: "h-6 w-6 text-xs",
|
|
26
|
+
sm: "h-8 w-8 text-xs",
|
|
27
|
+
md: "h-10 w-10 text-sm",
|
|
28
|
+
lg: "h-12 w-12 text-base",
|
|
29
|
+
xl: "h-16 w-16 text-lg",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const statusClasses: Record<string, string> = {
|
|
33
|
+
online: "bg-success-500",
|
|
34
|
+
offline: "bg-on-surface-faint",
|
|
35
|
+
busy: "bg-danger-500",
|
|
36
|
+
away: "bg-warning-500",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const statusSizes: Record<string, string> = {
|
|
40
|
+
xs: "h-1.5 w-1.5",
|
|
41
|
+
sm: "h-2 w-2",
|
|
42
|
+
md: "h-2.5 w-2.5",
|
|
43
|
+
lg: "h-3 w-3",
|
|
44
|
+
xl: "h-4 w-4",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Generate a consistent color based on name
|
|
48
|
+
const colors = [
|
|
49
|
+
"bg-wisp-500",
|
|
50
|
+
"bg-aurora-500",
|
|
51
|
+
"bg-success-500",
|
|
52
|
+
"bg-warning-500",
|
|
53
|
+
"bg-danger-500",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const colorIndex = $derived(
|
|
57
|
+
name.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) % colors.length,
|
|
58
|
+
);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div class="relative inline-flex {className}">
|
|
62
|
+
{#if src}
|
|
63
|
+
<img {src} alt={name} class="{sizeClasses[size]} rounded-full object-cover shadow-sm" />
|
|
64
|
+
{:else}
|
|
65
|
+
<div
|
|
66
|
+
class="
|
|
67
|
+
{sizeClasses[size]}
|
|
68
|
+
{colors[colorIndex]}
|
|
69
|
+
flex
|
|
70
|
+
items-center justify-center rounded-full
|
|
71
|
+
font-medium text-white shadow-sm
|
|
72
|
+
"
|
|
73
|
+
>
|
|
74
|
+
{initials || "?"}
|
|
75
|
+
</div>
|
|
76
|
+
{/if}
|
|
77
|
+
|
|
78
|
+
{#if status}
|
|
79
|
+
<span
|
|
80
|
+
class="
|
|
81
|
+
absolute right-0 bottom-0
|
|
82
|
+
{statusSizes[size]}
|
|
83
|
+
{statusClasses[status]}
|
|
84
|
+
rounded-full ring-2 ring-surface-raised
|
|
85
|
+
"
|
|
86
|
+
></span>
|
|
87
|
+
{/if}
|
|
88
|
+
</div>
|
|
@@ -0,0 +1,131 @@
|
|
|
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 BadgeVariant = "default" | "primary" | "success" | "warning" | "danger" | "info";
|
|
8
|
+
type BadgeSize = "sm" | "md" | "lg";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
variant?: BadgeVariant;
|
|
12
|
+
size?: BadgeSize;
|
|
13
|
+
dot?: boolean;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
outline?: boolean;
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
variant = "default",
|
|
22
|
+
size = "md",
|
|
23
|
+
dot = false,
|
|
24
|
+
loading = false,
|
|
25
|
+
outline = false,
|
|
26
|
+
children,
|
|
27
|
+
class: className = "",
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const baseClasses = `
|
|
31
|
+
inline-flex items-center gap-1.5
|
|
32
|
+
font-medium rounded-full
|
|
33
|
+
whitespace-nowrap
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const variantClasses: Record<BadgeVariant, { solid: string; outline: string }> = {
|
|
37
|
+
default: {
|
|
38
|
+
solid: "bg-mist-100 text-mist-700",
|
|
39
|
+
outline: "bg-transparent border border-mist-300 text-mist-700",
|
|
40
|
+
},
|
|
41
|
+
primary: {
|
|
42
|
+
solid: "bg-wisp-100 text-wisp-700",
|
|
43
|
+
outline: "bg-transparent border border-wisp-300 text-wisp-700",
|
|
44
|
+
},
|
|
45
|
+
success: {
|
|
46
|
+
solid: "bg-success-100 text-success-700",
|
|
47
|
+
outline: "bg-transparent border border-success-300 text-success-700",
|
|
48
|
+
},
|
|
49
|
+
warning: {
|
|
50
|
+
solid: "bg-warning-100 text-warning-700",
|
|
51
|
+
outline: "bg-transparent border border-warning-400 text-warning-700",
|
|
52
|
+
},
|
|
53
|
+
danger: {
|
|
54
|
+
solid: "bg-danger-100 text-danger-700",
|
|
55
|
+
outline: "bg-transparent border border-danger-300 text-danger-700",
|
|
56
|
+
},
|
|
57
|
+
info: {
|
|
58
|
+
solid: "bg-aurora-100 text-aurora-700",
|
|
59
|
+
outline: "bg-transparent border border-aurora-300 text-aurora-700",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const sizeClasses: Record<BadgeSize, string> = {
|
|
64
|
+
sm: "text-2xs px-1.5 py-0.5",
|
|
65
|
+
md: "text-xs px-2 py-0.5",
|
|
66
|
+
lg: "text-sm px-2.5 py-1",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const dotColors: Record<BadgeVariant, string> = {
|
|
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",
|
|
76
|
+
};
|
|
77
|
+
|
|
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",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const spinnerSizeClasses: Record<BadgeSize, string> = {
|
|
88
|
+
sm: "h-3 w-3",
|
|
89
|
+
md: "h-3.5 w-3.5",
|
|
90
|
+
lg: "h-4 w-4",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let safeVariant = $derived(
|
|
94
|
+
(Object.hasOwn(variantClasses, variant) ? variant : "default") as BadgeVariant,
|
|
95
|
+
);
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<span
|
|
99
|
+
class="{baseClasses} {outline
|
|
100
|
+
? variantClasses[safeVariant].outline
|
|
101
|
+
: variantClasses[safeVariant].solid} {sizeClasses[size]} {className}"
|
|
102
|
+
>
|
|
103
|
+
{#if dot}
|
|
104
|
+
{#if loading}
|
|
105
|
+
<svg
|
|
106
|
+
class="{spinnerSizeClasses[size]} animate-spin {indicatorTextColors[safeVariant]}"
|
|
107
|
+
viewBox="0 0 24 24"
|
|
108
|
+
fill="none"
|
|
109
|
+
>
|
|
110
|
+
<circle
|
|
111
|
+
class="opacity-25"
|
|
112
|
+
cx="12"
|
|
113
|
+
cy="12"
|
|
114
|
+
r="10"
|
|
115
|
+
stroke="currentColor"
|
|
116
|
+
stroke-width="4"
|
|
117
|
+
></circle>
|
|
118
|
+
<path
|
|
119
|
+
class="opacity-75"
|
|
120
|
+
fill="currentColor"
|
|
121
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
122
|
+
></path>
|
|
123
|
+
</svg>
|
|
124
|
+
{:else}
|
|
125
|
+
<span class="h-1.5 w-1.5 rounded-full {dotColors[safeVariant]}"></span>
|
|
126
|
+
{/if}
|
|
127
|
+
{/if}
|
|
128
|
+
{#if children}
|
|
129
|
+
{@render children()}
|
|
130
|
+
{/if}
|
|
131
|
+
</span>
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { ChevronRight, House } from "@lucide/svelte";
|
|
7
|
+
|
|
8
|
+
interface BreadcrumbItem {
|
|
9
|
+
label: string;
|
|
10
|
+
href?: string;
|
|
11
|
+
icon?: typeof House;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
items: BreadcrumbItem[];
|
|
16
|
+
separator?: Snippet;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { items, separator, class: className = "" }: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<nav aria-label="Breadcrumb" class={className}>
|
|
24
|
+
<ol class="flex items-center gap-1.5 text-sm">
|
|
25
|
+
{#each items as item, idx (idx)}
|
|
26
|
+
<li class="flex items-center gap-1.5">
|
|
27
|
+
{#if idx > 0}
|
|
28
|
+
{#if separator}
|
|
29
|
+
{@render separator()}
|
|
30
|
+
{:else}
|
|
31
|
+
<ChevronRight size={14} class="text-on-surface-faint" />
|
|
32
|
+
{/if}
|
|
33
|
+
{/if}
|
|
34
|
+
|
|
35
|
+
{#if item.href && idx < items.length - 1}
|
|
36
|
+
<a
|
|
37
|
+
href={item.href}
|
|
38
|
+
class="flex items-center gap-1.5 text-on-surface-muted transition-colors hover:text-on-surface"
|
|
39
|
+
>
|
|
40
|
+
{#if item.icon}
|
|
41
|
+
{@const Icon = item.icon}
|
|
42
|
+
<Icon size={14} />
|
|
43
|
+
{/if}
|
|
44
|
+
<span>{item.label}</span>
|
|
45
|
+
</a>
|
|
46
|
+
{:else}
|
|
47
|
+
<span class="flex items-center gap-1.5 font-medium text-on-surface">
|
|
48
|
+
{#if item.icon}
|
|
49
|
+
{@const Icon = item.icon}
|
|
50
|
+
<Icon size={14} />
|
|
51
|
+
{/if}
|
|
52
|
+
<span>{item.label}</span>
|
|
53
|
+
</span>
|
|
54
|
+
{/if}
|
|
55
|
+
</li>
|
|
56
|
+
{/each}
|
|
57
|
+
</ol>
|
|
58
|
+
</nav>
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { HTMLButtonAttributes } from "svelte/elements";
|
|
7
|
+
import {
|
|
8
|
+
BUTTON_BASE,
|
|
9
|
+
BUTTON_SIZES,
|
|
10
|
+
BUTTON_VARIANTS,
|
|
11
|
+
type ButtonSize,
|
|
12
|
+
type ButtonVariant,
|
|
13
|
+
} from "./button-styles.js";
|
|
14
|
+
|
|
15
|
+
interface Props extends HTMLButtonAttributes {
|
|
16
|
+
variant?: ButtonVariant;
|
|
17
|
+
size?: ButtonSize;
|
|
18
|
+
fullWidth?: boolean;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
icon?: Snippet;
|
|
21
|
+
iconRight?: Snippet;
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
variant = "primary",
|
|
27
|
+
size = "md",
|
|
28
|
+
fullWidth = false,
|
|
29
|
+
loading = false,
|
|
30
|
+
disabled = false,
|
|
31
|
+
icon,
|
|
32
|
+
iconRight,
|
|
33
|
+
children,
|
|
34
|
+
class: className = "",
|
|
35
|
+
...restProps
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<button
|
|
40
|
+
class="{BUTTON_BASE} {BUTTON_VARIANTS[variant]} {BUTTON_SIZES[size]} {fullWidth
|
|
41
|
+
? 'w-full'
|
|
42
|
+
: ''} {className}"
|
|
43
|
+
disabled={disabled || loading}
|
|
44
|
+
{...restProps}
|
|
45
|
+
>
|
|
46
|
+
{#if loading}
|
|
47
|
+
<svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
|
48
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
|
|
49
|
+
></circle>
|
|
50
|
+
<path
|
|
51
|
+
class="opacity-75"
|
|
52
|
+
fill="currentColor"
|
|
53
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
54
|
+
></path>
|
|
55
|
+
</svg>
|
|
56
|
+
{:else if icon}
|
|
57
|
+
<span class="shrink-0 transition-transform group-active:scale-95">
|
|
58
|
+
{@render icon()}
|
|
59
|
+
</span>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if children}
|
|
62
|
+
{@render children()}
|
|
63
|
+
{/if}
|
|
64
|
+
{#if iconRight && !loading}
|
|
65
|
+
<span class="shrink-0 transition-transform group-active:scale-95">
|
|
66
|
+
{@render iconRight()}
|
|
67
|
+
</span>
|
|
68
|
+
{/if}
|
|
69
|
+
</button>
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
interface Props {
|
|
8
|
+
padding?: "none" | "sm" | "md" | "lg";
|
|
9
|
+
hover?: boolean;
|
|
10
|
+
border?: boolean;
|
|
11
|
+
shadow?: "none" | "sm" | "md" | "lg";
|
|
12
|
+
header?: Snippet;
|
|
13
|
+
footer?: Snippet;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
padding = "md",
|
|
20
|
+
hover = false,
|
|
21
|
+
border = true,
|
|
22
|
+
shadow = "sm",
|
|
23
|
+
header,
|
|
24
|
+
footer,
|
|
25
|
+
children,
|
|
26
|
+
class: className = "",
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const paddingClasses: Record<string, string> = {
|
|
30
|
+
none: "",
|
|
31
|
+
sm: "p-3",
|
|
32
|
+
md: "p-4",
|
|
33
|
+
lg: "p-6",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const shadowClasses: Record<string, string> = {
|
|
37
|
+
none: "",
|
|
38
|
+
sm: "shadow-sm",
|
|
39
|
+
md: "shadow-md",
|
|
40
|
+
lg: "shadow-lg",
|
|
41
|
+
};
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
class="
|
|
46
|
+
overflow-hidden rounded-xl bg-surface-raised
|
|
47
|
+
{border ? 'border border-outline' : ''}
|
|
48
|
+
{shadowClasses[shadow]}
|
|
49
|
+
{hover ? 'duration-fast transition-shadow hover:border-outline-hover hover:shadow-md' : ''}
|
|
50
|
+
{className}
|
|
51
|
+
"
|
|
52
|
+
>
|
|
53
|
+
{#if header}
|
|
54
|
+
<div class="border-b border-outline-faint bg-surface-sunken/50 px-4 py-3">
|
|
55
|
+
{@render header()}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<div class={paddingClasses[padding]}>
|
|
60
|
+
{#if children}
|
|
61
|
+
{@render children()}
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{#if footer}
|
|
66
|
+
<div class="border-t border-outline-faint bg-surface-sunken/50 px-4 py-3">
|
|
67
|
+
{@render footer()}
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|