@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,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>