@lyeve-labs/ui-kit 0.8.2

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/components/Accordion.svelte +48 -0
  4. package/dist/components/Accordion.svelte.d.ts +10 -0
  5. package/dist/components/AccordionItem.svelte +55 -0
  6. package/dist/components/AccordionItem.svelte.d.ts +10 -0
  7. package/dist/components/Alert.svelte +60 -0
  8. package/dist/components/Alert.svelte.d.ts +13 -0
  9. package/dist/components/Autocomplete.svelte +198 -0
  10. package/dist/components/Autocomplete.svelte.d.ts +23 -0
  11. package/dist/components/Avatar.svelte +65 -0
  12. package/dist/components/Avatar.svelte.d.ts +9 -0
  13. package/dist/components/AvatarGroup.svelte +45 -0
  14. package/dist/components/AvatarGroup.svelte.d.ts +14 -0
  15. package/dist/components/Badge.svelte +48 -0
  16. package/dist/components/Badge.svelte.d.ts +13 -0
  17. package/dist/components/Banner.svelte +73 -0
  18. package/dist/components/Banner.svelte.d.ts +13 -0
  19. package/dist/components/Breadcrumb.svelte +47 -0
  20. package/dist/components/Breadcrumb.svelte.d.ts +10 -0
  21. package/dist/components/Button.svelte +71 -0
  22. package/dist/components/Button.svelte.d.ts +18 -0
  23. package/dist/components/ButtonGroup.svelte +33 -0
  24. package/dist/components/ButtonGroup.svelte.d.ts +9 -0
  25. package/dist/components/Card.svelte +72 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/Checkbox.svelte +80 -0
  28. package/dist/components/Checkbox.svelte.d.ts +14 -0
  29. package/dist/components/DatePicker.svelte +274 -0
  30. package/dist/components/DatePicker.svelte.d.ts +17 -0
  31. package/dist/components/Divider.svelte +23 -0
  32. package/dist/components/Divider.svelte.d.ts +8 -0
  33. package/dist/components/Drawer.svelte +120 -0
  34. package/dist/components/Drawer.svelte.d.ts +16 -0
  35. package/dist/components/Dropdown.svelte +88 -0
  36. package/dist/components/Dropdown.svelte.d.ts +23 -0
  37. package/dist/components/EmptyState.svelte +51 -0
  38. package/dist/components/EmptyState.svelte.d.ts +13 -0
  39. package/dist/components/FileInput.svelte +102 -0
  40. package/dist/components/FileInput.svelte.d.ts +15 -0
  41. package/dist/components/Indicator.svelte +38 -0
  42. package/dist/components/Indicator.svelte.d.ts +11 -0
  43. package/dist/components/Input.svelte +60 -0
  44. package/dist/components/Input.svelte.d.ts +17 -0
  45. package/dist/components/Kbd.svelte +18 -0
  46. package/dist/components/Kbd.svelte.d.ts +8 -0
  47. package/dist/components/Label.svelte +30 -0
  48. package/dist/components/Label.svelte.d.ts +11 -0
  49. package/dist/components/Modal.svelte +105 -0
  50. package/dist/components/Modal.svelte.d.ts +14 -0
  51. package/dist/components/MultiSelect.svelte +212 -0
  52. package/dist/components/MultiSelect.svelte.d.ts +22 -0
  53. package/dist/components/NumberInput.svelte +98 -0
  54. package/dist/components/NumberInput.svelte.d.ts +15 -0
  55. package/dist/components/PageHeader.svelte +22 -0
  56. package/dist/components/PageHeader.svelte.d.ts +10 -0
  57. package/dist/components/Pagination.svelte +112 -0
  58. package/dist/components/Pagination.svelte.d.ts +10 -0
  59. package/dist/components/Progress.svelte +70 -0
  60. package/dist/components/Progress.svelte.d.ts +15 -0
  61. package/dist/components/Radio.svelte +65 -0
  62. package/dist/components/Radio.svelte.d.ts +14 -0
  63. package/dist/components/RadioGroup.svelte +96 -0
  64. package/dist/components/RadioGroup.svelte.d.ts +22 -0
  65. package/dist/components/SearchInput.svelte +81 -0
  66. package/dist/components/SearchInput.svelte.d.ts +10 -0
  67. package/dist/components/Select.svelte +69 -0
  68. package/dist/components/Select.svelte.d.ts +18 -0
  69. package/dist/components/Skeleton.svelte +20 -0
  70. package/dist/components/Skeleton.svelte.d.ts +9 -0
  71. package/dist/components/Spinner.svelte +19 -0
  72. package/dist/components/Spinner.svelte.d.ts +7 -0
  73. package/dist/components/Stat.svelte +45 -0
  74. package/dist/components/Stat.svelte.d.ts +14 -0
  75. package/dist/components/StepIndicator.svelte +142 -0
  76. package/dist/components/StepIndicator.svelte.d.ts +13 -0
  77. package/dist/components/Table.svelte +36 -0
  78. package/dist/components/Table.svelte.d.ts +11 -0
  79. package/dist/components/Tabs.svelte +39 -0
  80. package/dist/components/Tabs.svelte.d.ts +13 -0
  81. package/dist/components/Tag.svelte +56 -0
  82. package/dist/components/Tag.svelte.d.ts +11 -0
  83. package/dist/components/Textarea.svelte +75 -0
  84. package/dist/components/Textarea.svelte.d.ts +26 -0
  85. package/dist/components/ThemeToggle.svelte +33 -0
  86. package/dist/components/ThemeToggle.svelte.d.ts +6 -0
  87. package/dist/components/Toaster.svelte +54 -0
  88. package/dist/components/Toaster.svelte.d.ts +18 -0
  89. package/dist/components/Toggle.svelte +74 -0
  90. package/dist/components/Toggle.svelte.d.ts +11 -0
  91. package/dist/components/Tooltip.svelte +44 -0
  92. package/dist/components/Tooltip.svelte.d.ts +11 -0
  93. package/dist/components/dialog/ConfirmDialog.svelte +95 -0
  94. package/dist/components/dialog/ConfirmDialog.svelte.d.ts +8 -0
  95. package/dist/components/dialog/Dialog.svelte +225 -0
  96. package/dist/components/dialog/Dialog.svelte.d.ts +9 -0
  97. package/dist/components/dialog/DialogContainer.svelte +17 -0
  98. package/dist/components/dialog/DialogContainer.svelte.d.ts +18 -0
  99. package/dist/components/dialog/dialog-manager.svelte.d.ts +54 -0
  100. package/dist/components/dialog/dialog-manager.svelte.js +164 -0
  101. package/dist/components/dialog/types.d.ts +33 -0
  102. package/dist/components/dialog/types.js +10 -0
  103. package/dist/index.d.ts +63 -0
  104. package/dist/index.js +72 -0
  105. package/dist/stores/toast.svelte.d.ts +18 -0
  106. package/dist/stores/toast.svelte.js +28 -0
  107. package/dist/styles/theme.css +148 -0
  108. package/dist/utils/cn.d.ts +13 -0
  109. package/dist/utils/cn.js +24 -0
  110. package/dist/utils/theme.d.ts +26 -0
  111. package/dist/utils/theme.js +55 -0
  112. package/package.json +81 -0
  113. package/src/lib/styles/theme.css +148 -0
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { toast, type ToastTone } from '../stores/toast.svelte';
3
+
4
+ const styles: Record<ToastTone, { bar: string; icon: string; mark: string }> = {
5
+ info: { bar: 'bg-brand', icon: 'text-brand', mark: 'ℹ' },
6
+ success: { bar: 'bg-success', icon: 'text-success', mark: '✓' },
7
+ warn: { bar: 'bg-warn', icon: 'text-warn', mark: '!' },
8
+ danger: { bar: 'bg-danger', icon: 'text-danger', mark: '×' },
9
+ };
10
+ </script>
11
+
12
+ <div
13
+ class="pointer-events-none fixed bottom-4 right-4 z-[60] flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2"
14
+ >
15
+ {#each toast.items as t (t.id)}
16
+ <div
17
+ class="pointer-events-auto flex items-start gap-3 overflow-hidden rounded-lg border border-line
18
+ bg-surface pl-0 pr-3 py-3 shadow-xl animate-[toast-in_140ms_ease-out]"
19
+ role="status"
20
+ >
21
+ <span class="w-1 self-stretch shrink-0 {styles[t.tone].bar}"></span>
22
+ <span
23
+ class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-current text-xs font-bold {styles[
24
+ t.tone
25
+ ].icon}"
26
+ aria-hidden="true"
27
+ >
28
+ {styles[t.tone].mark}
29
+ </span>
30
+ <p class="flex-1 text-sm text-fg leading-snug">{t.message}</p>
31
+ <button
32
+ type="button"
33
+ class="text-faint hover:text-fg transition-colors text-lg leading-none -mt-0.5"
34
+ aria-label="Dismiss"
35
+ onclick={() => toast.dismiss(t.id)}
36
+ >
37
+ ×
38
+ </button>
39
+ </div>
40
+ {/each}
41
+ </div>
42
+
43
+ <style>
44
+ @keyframes toast-in {
45
+ from {
46
+ opacity: 0;
47
+ transform: translateX(12px);
48
+ }
49
+ to {
50
+ opacity: 1;
51
+ transform: translateX(0);
52
+ }
53
+ }
54
+ </style>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Toaster: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Toaster = InstanceType<typeof Toaster>;
18
+ export default Toaster;
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ let {
3
+ checked = $bindable(false),
4
+ label,
5
+ hint,
6
+ disabled = false,
7
+ size = 'md',
8
+ onchange,
9
+ }: {
10
+ checked?: boolean;
11
+ label?: string;
12
+ hint?: string;
13
+ disabled?: boolean;
14
+ size?: 'sm' | 'md';
15
+ onchange?: (checked: boolean) => void;
16
+ } = $props();
17
+
18
+ const track: Record<'sm' | 'md', string> = {
19
+ sm: 'w-8 h-4',
20
+ md: 'w-10 h-5',
21
+ };
22
+
23
+ const thumb: Record<'sm' | 'md', string> = {
24
+ sm: 'w-3 h-3 top-0.5 left-0.5',
25
+ md: 'w-4 h-4 top-0.5 left-0.5',
26
+ };
27
+
28
+ const thumbOn: Record<'sm' | 'md', string> = {
29
+ sm: 'translate-x-4',
30
+ md: 'translate-x-5',
31
+ };
32
+
33
+ function handleClick() {
34
+ if (disabled) return;
35
+ checked = !checked;
36
+ onchange?.(checked);
37
+ }
38
+ </script>
39
+
40
+ <label
41
+ class="inline-flex items-start gap-2.5 cursor-pointer select-none {disabled
42
+ ? 'opacity-50 cursor-not-allowed'
43
+ : ''}"
44
+ >
45
+ <button
46
+ type="button"
47
+ role="switch"
48
+ aria-checked={checked}
49
+ aria-label={label ?? 'Toggle'}
50
+ {disabled}
51
+ onclick={handleClick}
52
+ class="relative shrink-0 rounded-full transition-colors focus:outline-none
53
+ focus-visible:ring-2 focus-visible:ring-brand/50 focus-visible:ring-offset-1
54
+ focus-visible:ring-offset-ink mt-0.5
55
+ {track[size]}
56
+ {checked ? 'bg-brand' : 'bg-surface-2'}"
57
+ >
58
+ <span
59
+ class="absolute rounded-full bg-fg shadow transition-transform
60
+ {thumb[size]}
61
+ {checked ? thumbOn[size] : 'translate-x-0'}"
62
+ ></span>
63
+ </button>
64
+ {#if label || hint}
65
+ <span class="flex flex-col gap-0.5">
66
+ {#if label}
67
+ <span class="text-sm text-fg">{label}</span>
68
+ {/if}
69
+ {#if hint}
70
+ <span class="text-xs text-faint">{hint}</span>
71
+ {/if}
72
+ </span>
73
+ {/if}
74
+ </label>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ checked?: boolean;
3
+ label?: string;
4
+ hint?: string;
5
+ disabled?: boolean;
6
+ size?: 'sm' | 'md';
7
+ onchange?: (checked: boolean) => void;
8
+ };
9
+ declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "checked">;
10
+ type Toggle = ReturnType<typeof Toggle>;
11
+ export default Toggle;
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ type Position = 'top' | 'bottom' | 'left' | 'right';
5
+
6
+ interface Props {
7
+ text: string;
8
+ position?: Position;
9
+ class?: string;
10
+ children: Snippet;
11
+ }
12
+
13
+ let { text, position = 'top', class: cls = '', children }: Props = $props();
14
+
15
+ let visible = $state(false);
16
+
17
+ const pos: Record<Position, string> = {
18
+ top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
19
+ bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
20
+ left: 'right-full top-1/2 -translate-y-1/2 mr-2',
21
+ right: 'left-full top-1/2 -translate-y-1/2 ml-2',
22
+ };
23
+ </script>
24
+
25
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
26
+ <span
27
+ class="relative inline-flex {cls}"
28
+ onmouseenter={() => (visible = true)}
29
+ onmouseleave={() => (visible = false)}
30
+ onfocus={() => (visible = true)}
31
+ onblur={() => (visible = false)}
32
+ >
33
+ {@render children()}
34
+ {#if visible}
35
+ <span
36
+ role="tooltip"
37
+ class="pointer-events-none absolute {pos[position]} z-50
38
+ whitespace-nowrap rounded-lg border border-line bg-surface-2
39
+ px-2.5 py-1.5 text-xs text-fg shadow-xl"
40
+ >
41
+ {text}
42
+ </span>
43
+ {/if}
44
+ </span>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Position = 'top' | 'bottom' | 'left' | 'right';
3
+ interface Props {
4
+ text: string;
5
+ position?: Position;
6
+ class?: string;
7
+ children: Snippet;
8
+ }
9
+ declare const Tooltip: import("svelte").Component<Props, {}, "">;
10
+ type Tooltip = ReturnType<typeof Tooltip>;
11
+ export default Tooltip;
@@ -0,0 +1,95 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ConfirmDialog - confirm/cancel pattern with async loading state.
4
+ *
5
+ * Renders a warning icon + message + Cancel/Confirm buttons.
6
+ * The confirm button shows a loading spinner during async actions.
7
+ *
8
+ * Usage (from DialogContainer):
9
+ * <Dialog {entry}>
10
+ * <ConfirmDialog {entry} />
11
+ * </Dialog>
12
+ *
13
+ * Programmatic usage:
14
+ * import { confirm } from '@lyeve/ui-kit';
15
+ * const ok = await confirm('Delete this item?', 'This action cannot be undone.');
16
+ * if (ok) { await deleteItem(); }
17
+ */
18
+ import { TriangleAlert } from '@lucide/svelte';
19
+ import { closeDialog, dismissDialog } from './dialog-manager.svelte';
20
+ import Button from '../Button.svelte';
21
+ import type { DialogEntry } from './types.js';
22
+
23
+ let {
24
+ entry,
25
+ /** If provided, called on confirm. Return false/throw to prevent close. */
26
+ onConfirm,
27
+ }: {
28
+ entry: DialogEntry<boolean>;
29
+ onConfirm?: () => Promise<boolean | void> | boolean | void;
30
+ } = $props();
31
+
32
+ let loading = $state(false);
33
+ let title = $derived(
34
+ typeof entry.meta?.confirmTitle === 'string' ? entry.meta.confirmTitle : 'Confirm',
35
+ );
36
+ let message = $derived(
37
+ typeof entry.meta?.confirmMessage === 'string' ? entry.meta.confirmMessage : '',
38
+ );
39
+ let cancelLabel = $derived(
40
+ typeof entry.meta?.cancelLabel === 'string' ? entry.meta.cancelLabel : 'Cancel',
41
+ );
42
+ let confirmLabel = $derived(
43
+ typeof entry.meta?.confirmLabel === 'string' ? entry.meta.confirmLabel : 'Confirm',
44
+ );
45
+
46
+ async function handleConfirm() {
47
+ if (loading) return;
48
+
49
+ try {
50
+ loading = true;
51
+ if (onConfirm) {
52
+ const result = await onConfirm();
53
+ if (result === false) {
54
+ loading = false;
55
+ return;
56
+ }
57
+ }
58
+ loading = false;
59
+ closeDialog(true, entry.id);
60
+ } catch {
61
+ loading = false;
62
+ }
63
+ }
64
+
65
+ function handleCancel() {
66
+ dismissDialog(entry.id);
67
+ }
68
+ </script>
69
+
70
+ <div class="flex flex-col gap-4">
71
+ <!-- Warning icon + message -->
72
+ <div class="flex items-start gap-3">
73
+ <div
74
+ class="flex items-center justify-center w-10 h-10 rounded-full bg-warn/10 text-warn shrink-0"
75
+ >
76
+ <TriangleAlert class="w-5 h-5" />
77
+ </div>
78
+ <div class="flex-1 min-w-0">
79
+ <p class="text-sm text-fg font-medium">{title}</p>
80
+ {#if message}
81
+ <p class="text-sm text-muted mt-1">{message}</p>
82
+ {/if}
83
+ </div>
84
+ </div>
85
+
86
+ <!-- Actions -->
87
+ <div class="flex items-center justify-end gap-3 pt-2">
88
+ <Button variant="secondary" size="sm" onclick={handleCancel}>
89
+ {cancelLabel}
90
+ </Button>
91
+ <Button variant="danger" size="sm" onclick={handleConfirm} {loading}>
92
+ {confirmLabel}
93
+ </Button>
94
+ </div>
95
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { DialogEntry } from './types.js';
2
+ type $$ComponentProps = {
3
+ entry: DialogEntry<boolean>;
4
+ onConfirm?: () => Promise<boolean | void> | boolean | void;
5
+ };
6
+ declare const ConfirmDialog: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
8
+ export default ConfirmDialog;
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Dialog - modal frame component.
4
+ *
5
+ * Handles: focus trap, ESC to close, click-outside to close,
6
+ * scale+fade animation, stacked z-indexing, portal to body.
7
+ *
8
+ * Rendered by DialogContainer for each entry in the dialog stack.
9
+ * Not meant to be used directly - use openDialog() from dialog-manager.
10
+ */
11
+ import { X } from '@lucide/svelte';
12
+ import { onMount, tick } from 'svelte';
13
+ import type { Snippet } from 'svelte';
14
+ import type { DialogEntry } from './types.js';
15
+ import { sizeClass } from './types.js';
16
+ import {
17
+ closeDialog,
18
+ dismissDialog,
19
+ _lockBodyScroll,
20
+ _unlockBodyScroll,
21
+ } from './dialog-manager.svelte';
22
+ import ConfirmDialog from './ConfirmDialog.svelte';
23
+
24
+ let {
25
+ entry,
26
+ children,
27
+ }: {
28
+ entry: DialogEntry<any>;
29
+ children?: Snippet;
30
+ } = $props();
31
+
32
+ // ──────────────────────────────────────────────────────
33
+ // Animation state
34
+ // ──────────────────────────────────────────────────────
35
+
36
+ let visible = $state(false);
37
+ let exiting = $state(false);
38
+
39
+ // Stacked offset: each deeper dialog shrinks and shifts back
40
+ let offset = $derived(entry.depth);
41
+ let zIndex = $derived(50 + offset);
42
+
43
+ // ──────────────────────────────────────────────────────
44
+ // DOM refs
45
+ // ──────────────────────────────────────────────────────
46
+
47
+ let dialogEl = $state<HTMLDivElement>();
48
+ let previousFocus = $state<HTMLElement | null>(null);
49
+ let focusableSelector =
50
+ 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
51
+
52
+ // ──────────────────────────────────────────────────────
53
+ // Lifecycle
54
+ // ──────────────────────────────────────────────────────
55
+
56
+ onMount(() => {
57
+ // Save previously focused element for restore on unmount
58
+ const el = document.activeElement;
59
+ previousFocus = el instanceof HTMLElement ? el : null;
60
+
61
+ // Trigger enter animation on next frame
62
+ requestAnimationFrame(() => {
63
+ visible = true;
64
+ });
65
+
66
+ // Focus trap: focus first focusable element, or the dialog itself
67
+ tick().then(() => {
68
+ if (dialogEl) {
69
+ const focusable = dialogEl.querySelectorAll(focusableSelector);
70
+ if (focusable.length > 0) {
71
+ (focusable[0] as HTMLElement).focus();
72
+ } else {
73
+ dialogEl.focus();
74
+ }
75
+ }
76
+ });
77
+
78
+ // Lock body scroll (counter-based: only unlock when no dialogs remain)
79
+ _lockBodyScroll();
80
+ const unlock = () => _unlockBodyScroll();
81
+
82
+ return () => {
83
+ unlock();
84
+ // Restore focus
85
+ previousFocus?.focus?.();
86
+ };
87
+ });
88
+
89
+ // ──────────────────────────────────────────────────────
90
+ // Handlers
91
+ // ──────────────────────────────────────────────────────
92
+
93
+ function handleBackdropClick(e: MouseEvent) {
94
+ // Only close if clicking the backdrop itself (not the dialog panel)
95
+ if (e.target === e.currentTarget && !entry.options.persistent) {
96
+ handleDismiss();
97
+ }
98
+ }
99
+
100
+ function handleKeydown(e: KeyboardEvent) {
101
+ if (e.key === 'Escape') {
102
+ e.stopPropagation();
103
+ if (!entry.options.persistent) {
104
+ handleDismiss();
105
+ }
106
+ }
107
+
108
+ // Focus trap
109
+ if (e.key === 'Tab' && dialogEl) {
110
+ const focusable = dialogEl.querySelectorAll(focusableSelector);
111
+ if (focusable.length === 0) {
112
+ e.preventDefault();
113
+ return;
114
+ }
115
+
116
+ const first = focusable[0] as HTMLElement;
117
+ const last = focusable[focusable.length - 1] as HTMLElement;
118
+
119
+ if (e.shiftKey) {
120
+ if (document.activeElement === first) {
121
+ e.preventDefault();
122
+ last.focus();
123
+ }
124
+ } else {
125
+ if (document.activeElement === last) {
126
+ e.preventDefault();
127
+ first.focus();
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ async function handleDismiss(): Promise<void> {
134
+ exiting = true;
135
+ // Wait for exit animation
136
+ await new Promise((r) => setTimeout(r, 200));
137
+ dismissDialog(entry.id);
138
+ }
139
+
140
+ async function handleClose(value?: unknown): Promise<void> {
141
+ exiting = true;
142
+ await new Promise((r) => setTimeout(r, 200));
143
+ closeDialog(value, entry.id);
144
+ }
145
+ </script>
146
+
147
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
148
+ <div class="fixed inset-0 z-[{zIndex}] flex items-center justify-center" role="presentation">
149
+ <!-- Backdrop -->
150
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
151
+ <div
152
+ class="absolute inset-0 bg-black/60 backdrop-blur-sm transition-opacity duration-200"
153
+ class:opacity-0={!visible || exiting}
154
+ class:opacity-100={visible && !exiting}
155
+ onclick={handleBackdropClick}
156
+ onkeydown={(e: KeyboardEvent) => {
157
+ if (e.key === 'Escape' && !entry.options.persistent) handleDismiss();
158
+ }}
159
+ role="presentation"
160
+ ></div>
161
+
162
+ <!-- Dialog panel -->
163
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
164
+ <div
165
+ bind:this={dialogEl}
166
+ class="relative w-full {sizeClass(entry.options.size ?? 'md')} mx-4
167
+ bg-surface border border-line rounded-xl shadow-2xl
168
+ transition-all duration-200 ease-out
169
+ focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
170
+ class:opacity-0={!visible || exiting}
171
+ class:opacity-100={visible && !exiting}
172
+ style="transform: scale({visible && !exiting ? 1 - offset * 0.03 : 0.95}) translateY({offset *
173
+ 16}px);
174
+ transform-origin: center;"
175
+ role="dialog"
176
+ aria-modal="true"
177
+ aria-label={typeof entry.options.title === 'string' ? entry.options.title : 'Dialog'}
178
+ tabindex="-1"
179
+ onkeydown={handleKeydown}
180
+ >
181
+ <!-- Header -->
182
+ <div class="flex items-center justify-between px-6 pt-6 pb-2">
183
+ <div class="flex-1 min-w-0">
184
+ {#if entry.options.title && entry.meta?.confirmTitle === undefined}
185
+ {#if typeof entry.options.title === 'string'}
186
+ <h2 class="text-lg font-semibold text-fg truncate">
187
+ {entry.options.title}
188
+ </h2>
189
+ {:else}
190
+ {@render entry.options.title()}
191
+ {/if}
192
+ {/if}
193
+ </div>
194
+
195
+ {#if !entry.options.persistent}
196
+ <button
197
+ class="inline-flex items-center justify-center w-8 h-8 -mr-2 rounded-lg
198
+ text-muted hover:text-fg hover:bg-surface-2 transition-colors shrink-0"
199
+ onclick={() => handleClose()}
200
+ aria-label="Close"
201
+ >
202
+ <X class="w-4 h-4" />
203
+ </button>
204
+ {/if}
205
+ </div>
206
+
207
+ <!-- Body -->
208
+ <div class="px-6 py-2">
209
+ {#if entry.options.body}
210
+ {@render entry.options.body()}
211
+ {:else if entry.meta?.confirmTitle !== undefined}
212
+ <ConfirmDialog {entry} />
213
+ {:else if children}
214
+ {@render children()}
215
+ {/if}
216
+ </div>
217
+
218
+ <!-- Footer -->
219
+ {#if entry.options.footer}
220
+ <div class="flex items-center justify-end gap-3 px-6 pb-6 pt-2">
221
+ {@render entry.options.footer()}
222
+ </div>
223
+ {/if}
224
+ </div>
225
+ </div>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { DialogEntry } from './types.js';
3
+ type $$ComponentProps = {
4
+ entry: DialogEntry<any>;
5
+ children?: Snippet;
6
+ };
7
+ declare const Dialog: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type Dialog = ReturnType<typeof Dialog>;
9
+ export default Dialog;
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ /**
3
+ * DialogContainer - renders the dialog stack.
4
+ *
5
+ * Mount once near the app root (in +layout.svelte).
6
+ * Reads from the module-level $state store - no props needed.
7
+ *
8
+ * Dialogs stack bottom-to-top; newest renders on top.
9
+ * Each dialog gets its own focus trap, ESC handler, and backdrop.
10
+ */
11
+ import { getDialogStack } from './dialog-manager.svelte';
12
+ import Dialog from './Dialog.svelte';
13
+ </script>
14
+
15
+ {#each getDialogStack() as entry (entry.id)}
16
+ <Dialog {entry} />
17
+ {/each}
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const DialogContainer: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type DialogContainer = InstanceType<typeof DialogContainer>;
18
+ export default DialogContainer;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Dialog Manager - module-level Svelte 5 reactive state.
3
+ *
4
+ * Manages a stack of dialogs. Dialogs render in last-to-first order (newest on top).
5
+ * Each dialog returns a Promise that resolves/rejects when closed.
6
+ *
7
+ * Usage:
8
+ * import { openDialog } from '@lyeve/ui-kit';
9
+ * // Inside a .svelte.ts file or $effect:
10
+ * const result = await openDialog({ title: 'Delete?', body: mySnippet, size: 'sm' });
11
+ *
12
+ * For confirm/cancel pattern:
13
+ * import { confirm } from '@lyeve/ui-kit';
14
+ * const ok = await confirm('Delete this item?', 'This action cannot be undone.');
15
+ * if (ok) { /* ... *\/ }
16
+ */
17
+ import type { DialogOptions, DialogEntry } from './types.js';
18
+ export declare function _lockBodyScroll(): void;
19
+ export declare function _unlockBodyScroll(): void;
20
+ /** Readonly snapshot for components */
21
+ export declare function getDialogStack(): readonly DialogEntry<any>[];
22
+ /**
23
+ * Open a new dialog. Returns a promise that resolves when the dialog is closed
24
+ * (via closeDialog with a value) or rejects if dismissed without a value.
25
+ *
26
+ * The dialog is pushed onto the stack and becomes the topmost.
27
+ */
28
+ export declare function openDialog<T = void>(options: DialogOptions<T>): Promise<T>;
29
+ /**
30
+ * Close a dialog by id and resolve its promise with a value.
31
+ * If omitted, closes the topmost dialog.
32
+ */
33
+ export declare function closeDialog<T = void>(value: T, id?: string): void;
34
+ /**
35
+ * Dismiss (cancel) a dialog - rejects the promise.
36
+ * Called when user clicks backdrop or presses ESC on a non-persistent dialog.
37
+ */
38
+ export declare function dismissDialog(id?: string): void;
39
+ /**
40
+ * Close all open dialogs, rejecting their promises.
41
+ */
42
+ export declare function dismissAllDialogs(): void;
43
+ /**
44
+ * Convenience: confirm dialog.
45
+ * Returns `true` if user confirmed, `false` if cancelled.
46
+ *
47
+ * const ok = await confirm('Delete item?', 'This cannot be undone.');
48
+ */
49
+ export declare function confirm(title: string, message?: string): Promise<boolean>;
50
+ /**
51
+ * Set metadata on a dialog entry after it's opened.
52
+ * Used by convenience wrappers to pass extra data without polluting DialogOptions.
53
+ */
54
+ export declare function setDialogMeta(id: string, meta: Record<string, unknown>): void;