@x33025/sveltely 0.0.47 → 0.0.49
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.
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { CircleAlertIcon } from '@lucide/svelte';
|
|
2
3
|
import type { Component } from 'svelte';
|
|
3
4
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
5
|
import Spinner from './Spinner.svelte';
|
|
6
|
+
import Tooltip from './Tooltip.svelte';
|
|
5
7
|
|
|
6
8
|
type Props = {
|
|
7
9
|
icon?: Component<{ class?: string }>;
|
|
8
10
|
label: string;
|
|
9
11
|
action: () => void | Promise<void>;
|
|
12
|
+
error?: unknown | null;
|
|
13
|
+
style?: 'iconAndLabel' | 'iconOnly';
|
|
10
14
|
} & Omit<HTMLButtonAttributes, 'children' | 'onclick'>;
|
|
11
15
|
|
|
12
16
|
let {
|
|
13
17
|
icon,
|
|
14
18
|
label,
|
|
15
19
|
action,
|
|
20
|
+
error = $bindable<unknown | null>(null),
|
|
21
|
+
style: styleMode = 'iconAndLabel',
|
|
16
22
|
disabled = false,
|
|
17
23
|
class: className = '',
|
|
18
24
|
type = 'button',
|
|
@@ -23,28 +29,46 @@
|
|
|
23
29
|
|
|
24
30
|
const handleClick = async () => {
|
|
25
31
|
if (disabled || pending) return;
|
|
32
|
+
error = null;
|
|
26
33
|
pending = true;
|
|
27
34
|
try {
|
|
28
35
|
await action();
|
|
36
|
+
} catch (caught) {
|
|
37
|
+
error = caught;
|
|
29
38
|
} finally {
|
|
30
39
|
pending = false;
|
|
31
40
|
}
|
|
32
41
|
};
|
|
42
|
+
|
|
43
|
+
const errorLabel = $derived.by(() => {
|
|
44
|
+
if (!error) return null;
|
|
45
|
+
if (typeof error === 'string' && error.trim().length > 0) return error;
|
|
46
|
+
if (error instanceof Error && error.message.trim().length > 0) return error.message;
|
|
47
|
+
return 'Something went wrong';
|
|
48
|
+
});
|
|
33
49
|
</script>
|
|
34
50
|
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
<Tooltip label={errorLabel ?? ''} disabled={!errorLabel}>
|
|
52
|
+
<button
|
|
53
|
+
{type}
|
|
54
|
+
class="inline-flex items-center gap-2 disabled:cursor-not-allowed disabled:opacity-50 {className}"
|
|
55
|
+
disabled={disabled || pending}
|
|
56
|
+
aria-busy={pending}
|
|
57
|
+
aria-label={styleMode === 'iconOnly' ? label : undefined}
|
|
58
|
+
data-error={error ? 'true' : 'false'}
|
|
59
|
+
{...props}
|
|
60
|
+
onclick={handleClick}
|
|
61
|
+
>
|
|
62
|
+
{#if pending}
|
|
63
|
+
<Spinner class="async-button-icon size-4" />
|
|
64
|
+
{:else if error}
|
|
65
|
+
<CircleAlertIcon class="async-button-icon size-4 text-red-600" />
|
|
66
|
+
{:else if icon}
|
|
67
|
+
{@const Icon = icon}
|
|
68
|
+
<Icon class="async-button-icon size-4" />
|
|
69
|
+
{/if}
|
|
70
|
+
{#if styleMode === 'iconAndLabel'}
|
|
71
|
+
<span class="async-button-text">{label}</span>
|
|
72
|
+
{/if}
|
|
73
|
+
</button>
|
|
74
|
+
</Tooltip>
|
|
@@ -6,7 +6,9 @@ type Props = {
|
|
|
6
6
|
}>;
|
|
7
7
|
label: string;
|
|
8
8
|
action: () => void | Promise<void>;
|
|
9
|
+
error?: unknown | null;
|
|
10
|
+
style?: 'iconAndLabel' | 'iconOnly';
|
|
9
11
|
} & Omit<HTMLButtonAttributes, 'children' | 'onclick'>;
|
|
10
|
-
declare const AsyncButton: Component<Props, {}, "">;
|
|
12
|
+
declare const AsyncButton: Component<Props, {}, "error">;
|
|
11
13
|
type AsyncButton = ReturnType<typeof AsyncButton>;
|
|
12
14
|
export default AsyncButton;
|
package/dist/style/index.css
CHANGED
package/dist/style.css
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
8
8
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
|
9
9
|
"Courier New", monospace;
|
|
10
|
+
--color-red-500: oklch(63.7% 0.237 25.331);
|
|
11
|
+
--color-red-600: oklch(57.7% 0.245 27.325);
|
|
10
12
|
--color-red-700: oklch(50.5% 0.213 27.518);
|
|
11
13
|
--color-gray-200: oklch(92.8% 0.006 264.531);
|
|
12
14
|
--color-gray-700: oklch(37.3% 0.034 259.733);
|
|
@@ -367,6 +369,9 @@
|
|
|
367
369
|
.border-zinc-200 {
|
|
368
370
|
border-color: var(--color-zinc-200);
|
|
369
371
|
}
|
|
372
|
+
.border-zinc-300 {
|
|
373
|
+
border-color: var(--color-zinc-300);
|
|
374
|
+
}
|
|
370
375
|
.bg-white {
|
|
371
376
|
background-color: var(--color-white);
|
|
372
377
|
}
|
|
@@ -429,6 +434,9 @@
|
|
|
429
434
|
.text-gray-700 {
|
|
430
435
|
color: var(--color-gray-700);
|
|
431
436
|
}
|
|
437
|
+
.text-red-600 {
|
|
438
|
+
color: var(--color-red-600);
|
|
439
|
+
}
|
|
432
440
|
.text-red-700 {
|
|
433
441
|
color: var(--color-red-700);
|
|
434
442
|
}
|
|
@@ -540,6 +548,16 @@
|
|
|
540
548
|
opacity: 50%;
|
|
541
549
|
}
|
|
542
550
|
}
|
|
551
|
+
.error\:border-red-500 {
|
|
552
|
+
&[data-error='true'] {
|
|
553
|
+
border-color: var(--color-red-500);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
.error\:text-red-600 {
|
|
557
|
+
&[data-error='true'] {
|
|
558
|
+
color: var(--color-red-600);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
543
561
|
}
|
|
544
562
|
@layer base {
|
|
545
563
|
html, body {
|