svelte-comp 1.3.3 → 1.3.6
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.md +21 -21
- package/README.md +101 -100
- package/dist/App.svelte +507 -507
- package/dist/Container.svelte +59 -59
- package/dist/app.css +234 -235
- package/dist/app.d.ts +10 -0
- package/dist/lib/Accordion.svelte +155 -155
- package/dist/lib/Badge.svelte +44 -44
- package/dist/lib/Button.svelte +185 -170
- package/dist/lib/Calendar.svelte +384 -384
- package/dist/lib/Card.svelte +103 -103
- package/dist/lib/Carousel.svelte +293 -293
- package/dist/lib/Carousel.svelte.d.ts +1 -1
- package/dist/lib/CheckBox.svelte +210 -210
- package/dist/lib/CodeView.svelte +308 -307
- package/dist/lib/ColorPicker.svelte +159 -159
- package/dist/lib/ContextMenu.svelte +328 -322
- package/dist/lib/DatePicker.svelte +246 -246
- package/dist/lib/Dialog.svelte +233 -233
- package/dist/lib/Field.svelte +299 -299
- package/dist/lib/FilePicker.svelte +295 -240
- package/dist/lib/FilePicker.svelte.d.ts +6 -1
- package/dist/lib/Form.svelte +438 -438
- package/dist/lib/Hamburger.svelte +217 -217
- package/dist/lib/InstallPWA.svelte +94 -94
- package/dist/lib/Menu.svelte +623 -623
- package/dist/lib/NoticeBase.svelte +140 -140
- package/dist/lib/PaginatedCard.svelte +73 -73
- package/dist/lib/Pagination.svelte +119 -119
- package/dist/lib/PrimaryColorSelect.svelte +111 -111
- package/dist/lib/ProgressBar.svelte +141 -141
- package/dist/lib/ProgressCircle.svelte +190 -190
- package/dist/lib/Radio.svelte +189 -189
- package/dist/lib/SearchInput.svelte +104 -104
- package/dist/lib/Select.svelte +524 -524
- package/dist/lib/Slider.svelte +253 -253
- package/dist/lib/Splitter.svelte +159 -150
- package/dist/lib/Switch.svelte +168 -167
- package/dist/lib/Table.svelte +299 -299
- package/dist/lib/Tabs.svelte +213 -213
- package/dist/lib/ThemeToggle.svelte +128 -127
- package/dist/lib/TimePicker.svelte +312 -312
- package/dist/lib/TimePickerNew.svelte +634 -0
- package/dist/lib/TimePickerNew.svelte.d.ts +49 -0
- package/dist/lib/Toast.svelte +123 -123
- package/dist/lib/Tooltip.svelte +110 -110
- package/dist/lib/Topbar.svelte +107 -107
- package/dist/lib/__tests__/Accordion.test.d.ts +1 -0
- package/dist/lib/__tests__/Accordion.test.js +171 -0
- package/dist/lib/__tests__/Badge.test.d.ts +1 -0
- package/dist/lib/__tests__/Badge.test.js +41 -0
- package/dist/lib/__tests__/Button.test.d.ts +1 -0
- package/dist/lib/__tests__/Button.test.js +269 -0
- package/dist/lib/__tests__/Calendar.test.d.ts +1 -0
- package/dist/lib/__tests__/Calendar.test.js +171 -0
- package/dist/lib/__tests__/Card.test.d.ts +1 -0
- package/dist/lib/__tests__/Card.test.js +148 -0
- package/dist/lib/__tests__/Carousel.test.d.ts +1 -0
- package/dist/lib/__tests__/Carousel.test.js +439 -0
- package/dist/lib/__tests__/CheckBox.test.d.ts +1 -0
- package/dist/lib/__tests__/CheckBox.test.js +152 -0
- package/dist/lib/__tests__/CodeView.test.d.ts +1 -0
- package/dist/lib/__tests__/CodeView.test.js +157 -0
- package/dist/lib/__tests__/ColorPicker.test.d.ts +1 -0
- package/dist/lib/__tests__/ColorPicker.test.js +93 -0
- package/dist/lib/__tests__/ContextMenu.test.d.ts +1 -0
- package/dist/lib/__tests__/ContextMenu.test.js +67 -0
- package/dist/lib/__tests__/DatePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/DatePicker.test.js +108 -0
- package/dist/lib/__tests__/Dialog.test.d.ts +1 -0
- package/dist/lib/__tests__/Dialog.test.js +183 -0
- package/dist/lib/__tests__/Field.test.d.ts +1 -0
- package/dist/lib/__tests__/Field.test.js +190 -0
- package/dist/lib/__tests__/FilePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/FilePicker.test.js +179 -0
- package/dist/lib/__tests__/Form.integration.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.integration.test.js +158 -0
- package/dist/lib/__tests__/Form.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.test.js +463 -0
- package/dist/lib/__tests__/Hamburger.test.d.ts +1 -0
- package/dist/lib/__tests__/Hamburger.test.js +161 -0
- package/dist/lib/__tests__/InstallPWA.test.d.ts +1 -0
- package/dist/lib/__tests__/InstallPWA.test.js +15 -0
- package/dist/lib/__tests__/Menu.test.d.ts +1 -0
- package/dist/lib/__tests__/Menu.test.js +285 -0
- package/dist/lib/__tests__/NoticeBase.test.d.ts +1 -0
- package/dist/lib/__tests__/NoticeBase.test.js +60 -0
- package/dist/lib/__tests__/PaginatedCard.test.d.ts +1 -0
- package/dist/lib/__tests__/PaginatedCard.test.js +89 -0
- package/dist/lib/__tests__/Pagination.test.d.ts +1 -0
- package/dist/lib/__tests__/Pagination.test.js +168 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.d.ts +1 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.js +92 -0
- package/dist/lib/__tests__/ProgressBar.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressBar.test.js +69 -0
- package/dist/lib/__tests__/ProgressCircle.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressCircle.test.js +71 -0
- package/dist/lib/__tests__/Radio.test.d.ts +1 -0
- package/dist/lib/__tests__/Radio.test.js +127 -0
- package/dist/lib/__tests__/SearchInput.test.d.ts +1 -0
- package/dist/lib/__tests__/SearchInput.test.js +80 -0
- package/dist/lib/__tests__/Select.test.d.ts +1 -0
- package/dist/lib/__tests__/Select.test.js +408 -0
- package/dist/lib/__tests__/Slider.test.d.ts +1 -0
- package/dist/lib/__tests__/Slider.test.js +213 -0
- package/dist/lib/__tests__/Splitter.test.d.ts +1 -0
- package/dist/lib/__tests__/Splitter.test.js +87 -0
- package/dist/lib/__tests__/Switch.test.d.ts +1 -0
- package/dist/lib/__tests__/Switch.test.js +97 -0
- package/dist/lib/__tests__/Table.test.d.ts +1 -0
- package/dist/lib/__tests__/Table.test.js +349 -0
- package/dist/lib/__tests__/Tabs.test.d.ts +1 -0
- package/dist/lib/__tests__/Tabs.test.js +262 -0
- package/dist/lib/__tests__/ThemeToggle.test.d.ts +1 -0
- package/dist/lib/__tests__/ThemeToggle.test.js +84 -0
- package/dist/lib/__tests__/TimePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePicker.test.js +146 -0
- package/dist/lib/__tests__/TimePickerNew.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePickerNew.test.js +322 -0
- package/dist/lib/__tests__/Toast.test.d.ts +1 -0
- package/dist/lib/__tests__/Toast.test.js +135 -0
- package/dist/lib/__tests__/Tooltip.test.d.ts +1 -0
- package/dist/lib/__tests__/Tooltip.test.js +171 -0
- package/dist/lib/__tests__/Topbar.test.d.ts +1 -0
- package/dist/lib/__tests__/Topbar.test.js +25 -0
- package/dist/lib/__tests__/setupLangContext.d.ts +1 -0
- package/dist/lib/__tests__/setupLangContext.js +65 -0
- package/dist/lib/__tests__/storage.test.d.ts +1 -0
- package/dist/lib/__tests__/storage.test.js +124 -0
- package/dist/lib/__tests__/utils.test.d.ts +1 -0
- package/dist/lib/__tests__/utils.test.js +11 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/lang.d.ts +4 -0
- package/dist/lib/lang.js +4 -0
- package/dist/styles.css +234 -232
- package/dist/utils/index.js +15 -4
- package/package.json +52 -52
package/dist/lib/Dialog.svelte
CHANGED
|
@@ -1,233 +1,233 @@
|
|
|
1
|
-
<!-- src/lib/Dialog.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* @component Dialog
|
|
5
|
-
* @description Modal dialog for confirmations or alerts.
|
|
6
|
-
*
|
|
7
|
-
* @prop open {boolean} - Controls dialog visibility
|
|
8
|
-
* @default false
|
|
9
|
-
*
|
|
10
|
-
* @prop title {string} - Dialog title used for labeling
|
|
11
|
-
* @default ""
|
|
12
|
-
*
|
|
13
|
-
* @prop message {string} - Simple message content
|
|
14
|
-
* @default ""
|
|
15
|
-
*
|
|
16
|
-
* @prop onConfirm {() => void} - Fired when the confirm action is triggered
|
|
17
|
-
* @default () => {}
|
|
18
|
-
*
|
|
19
|
-
* @prop onCancel {() => void} - Fired when the cancel action is triggered
|
|
20
|
-
* @default () => {}
|
|
21
|
-
*
|
|
22
|
-
* @prop onClose {() => void} - Fired after confirm or cancel to centralize cleanup
|
|
23
|
-
* @default () => {}
|
|
24
|
-
*
|
|
25
|
-
* @prop modal {boolean} - Enables modal mode with overlay and focus trap
|
|
26
|
-
* @default true
|
|
27
|
-
*
|
|
28
|
-
* @prop class {string} - Extra classes applied to the dialog container
|
|
29
|
-
* @default ""
|
|
30
|
-
*
|
|
31
|
-
* @prop sz {SizeKey} - Size preset for padding and text
|
|
32
|
-
* @options xs|sm|md|lg|xl
|
|
33
|
-
* @default md
|
|
34
|
-
*
|
|
35
|
-
* @prop children {Snippet} - Custom dialog body content
|
|
36
|
-
*
|
|
37
|
-
* @note In modal mode the first interactive element is focused automatically and focus is trapped inside the dialog; `Escape` triggers cancel.
|
|
38
|
-
* @note `onClose` runs after `onConfirm`/`onCancel`, so you can centralize cleanup.
|
|
39
|
-
* @note Non-modal mode (`modal=false`) renders a floating panel without overlay or focus trap.
|
|
40
|
-
* @note Buttons are labeled "OK" and "Cancel" and aren't customizable via props; provide `children` for full custom UI.
|
|
41
|
-
* @note Set a meaningful `title` for accessibility; it's used as the dialog's `aria-label`.
|
|
42
|
-
* @note `sz` adjusts both dialog padding and text sizes to match the rest of the system tokens.
|
|
43
|
-
*/
|
|
44
|
-
import type { Snippet } from "svelte";
|
|
45
|
-
import Button from "./Button.svelte";
|
|
46
|
-
import { type SizeKey, TEXT } from "./types";
|
|
47
|
-
import { cx, focusFirstInteractive, trapFocus } from "../utils";
|
|
48
|
-
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
49
|
-
|
|
50
|
-
type Props = {
|
|
51
|
-
open?: boolean;
|
|
52
|
-
title?: string;
|
|
53
|
-
message?: string;
|
|
54
|
-
onConfirm?: () => void;
|
|
55
|
-
onCancel?: () => void;
|
|
56
|
-
onClose?: () => void;
|
|
57
|
-
modal?: boolean;
|
|
58
|
-
class?: string;
|
|
59
|
-
sz?: SizeKey;
|
|
60
|
-
children?: Snippet<[SizeKey]>;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
let {
|
|
64
|
-
open = false,
|
|
65
|
-
title = "",
|
|
66
|
-
message = "",
|
|
67
|
-
onConfirm = () => {},
|
|
68
|
-
onCancel = () => {},
|
|
69
|
-
onClose = () => {},
|
|
70
|
-
modal = true,
|
|
71
|
-
class: externalClass = "",
|
|
72
|
-
sz = "md",
|
|
73
|
-
children,
|
|
74
|
-
}: Props = $props();
|
|
75
|
-
|
|
76
|
-
const langCtx = getLangContext();
|
|
77
|
-
const langKey = $derived(getLangKey(langCtx));
|
|
78
|
-
const L = $derived(getComponentText("dialog", langKey));
|
|
79
|
-
|
|
80
|
-
let panelEl = $state<HTMLDivElement | null>(null);
|
|
81
|
-
let releaseFocus: (() => void) | null = null;
|
|
82
|
-
|
|
83
|
-
function handleConfirm() {
|
|
84
|
-
onConfirm();
|
|
85
|
-
onClose();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function handleCancel() {
|
|
89
|
-
onCancel();
|
|
90
|
-
onClose();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
94
|
-
if (e.key === "Escape") {
|
|
95
|
-
e.preventDefault();
|
|
96
|
-
handleCancel();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const panelBase =
|
|
101
|
-
"fusion-dialog bg-[var(--color-bg-surface)] rounded-[var(--radius-lg)] shadow-
|
|
102
|
-
|
|
103
|
-
const paddingBySize: Record<SizeKey, string> = {
|
|
104
|
-
xs: "p-[var(--spacing-sm)]",
|
|
105
|
-
sm: "p-[var(--spacing-md)]",
|
|
106
|
-
md: "p-[var(--spacing-lg)]",
|
|
107
|
-
lg: "p-[var(--spacing-xl)]",
|
|
108
|
-
xl: "p-[var(--spacing-xl)]",
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const titleTextBySize: Record<SizeKey, string> = {
|
|
112
|
-
xs: TEXT.md,
|
|
113
|
-
sm: TEXT.md,
|
|
114
|
-
md: TEXT.xl,
|
|
115
|
-
lg: TEXT.xl,
|
|
116
|
-
xl: "text-2xl",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const panelClass = $derived(cx(panelBase, externalClass));
|
|
120
|
-
const paddingCls = $derived(paddingBySize[sz]);
|
|
121
|
-
const footerPaddingCls = $derived(cx(paddingCls, "pt-0"));
|
|
122
|
-
const titleTextCls = $derived(titleTextBySize[sz]);
|
|
123
|
-
const bodyTextCls = $derived(TEXT[sz]);
|
|
124
|
-
|
|
125
|
-
$effect(() => {
|
|
126
|
-
releaseFocus?.();
|
|
127
|
-
if (open && panelEl) {
|
|
128
|
-
queueMicrotask(() => {
|
|
129
|
-
focusFirstInteractive(panelEl!);
|
|
130
|
-
if (modal) {
|
|
131
|
-
releaseFocus = trapFocus(panelEl!);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
} else {
|
|
135
|
-
releaseFocus = null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return () => {
|
|
139
|
-
releaseFocus?.();
|
|
140
|
-
releaseFocus = null;
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
</script>
|
|
144
|
-
|
|
145
|
-
{#snippet titleBlock()}
|
|
146
|
-
<h3
|
|
147
|
-
class={cx(
|
|
148
|
-
"mb-2 font-[var(--font-weight-semibold)] color-[var(--color-text-default)]",
|
|
149
|
-
titleTextCls
|
|
150
|
-
)}
|
|
151
|
-
>
|
|
152
|
-
{title}
|
|
153
|
-
</h3>
|
|
154
|
-
<p
|
|
155
|
-
class={cx(
|
|
156
|
-
"leading-[var(--line-height-normal)] color-[var(--color-text-muted)]",
|
|
157
|
-
bodyTextCls
|
|
158
|
-
)}
|
|
159
|
-
>
|
|
160
|
-
{message}
|
|
161
|
-
</p>
|
|
162
|
-
{/snippet}
|
|
163
|
-
|
|
164
|
-
{#if open}
|
|
165
|
-
{#if modal}
|
|
166
|
-
<div
|
|
167
|
-
class="fixed inset-0 z-[var(--z-modal)] bg-oklch(0_0_0/var(--opacity-overlay)) flex items-center justify-center p-
|
|
168
|
-
role="presentation"
|
|
169
|
-
tabindex="-1"
|
|
170
|
-
onkeydown={handleKeydown}
|
|
171
|
-
>
|
|
172
|
-
<div
|
|
173
|
-
class={panelClass}
|
|
174
|
-
role="dialog"
|
|
175
|
-
aria-modal="true"
|
|
176
|
-
aria-label={title}
|
|
177
|
-
bind:this={panelEl}
|
|
178
|
-
>
|
|
179
|
-
<div class={paddingCls}>
|
|
180
|
-
{@render titleBlock()}
|
|
181
|
-
|
|
182
|
-
{#if children}
|
|
183
|
-
<div class="mt-3">
|
|
184
|
-
{@render children?.(sz)}
|
|
185
|
-
</div>
|
|
186
|
-
{/if}
|
|
187
|
-
</div>
|
|
188
|
-
|
|
189
|
-
<div class={cx("flex gap-2 justify-end", footerPaddingCls)}>
|
|
190
|
-
<Button {sz} variant="primary" onClick={handleConfirm}>{L.ok}</Button>
|
|
191
|
-
<Button {sz} variant="secondary" onClick={handleCancel}>{L.cancel}</Button
|
|
192
|
-
>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
{:else}
|
|
197
|
-
<div
|
|
198
|
-
class="fixed top-
|
|
199
|
-
role="dialog"
|
|
200
|
-
aria-modal="false"
|
|
201
|
-
aria-label={title}
|
|
202
|
-
tabindex="-1"
|
|
203
|
-
onkeydown={handleKeydown}
|
|
204
|
-
>
|
|
205
|
-
<div class={panelClass} bind:this={panelEl}>
|
|
206
|
-
<div class={paddingCls}>
|
|
207
|
-
{@render titleBlock()}
|
|
208
|
-
|
|
209
|
-
{#if children}
|
|
210
|
-
<div class={cx("mt-3", bodyTextCls)}>
|
|
211
|
-
{@render children?.(sz)}
|
|
212
|
-
</div>
|
|
213
|
-
{/if}
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div class={cx("flex gap-2 justify-end", footerPaddingCls)}>
|
|
217
|
-
<Button {sz} variant="primary" onClick={handleConfirm}>{L.ok}</Button>
|
|
218
|
-
<Button {sz} variant="secondary" onClick={handleCancel}>{L.cancel}</Button
|
|
219
|
-
>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
{/if}
|
|
224
|
-
{/if}
|
|
225
|
-
<style>
|
|
226
|
-
:global(.fusion-dialog h3) {
|
|
227
|
-
color: var(--color-text-default);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
:global(.fusion-dialog p) {
|
|
231
|
-
color: var(--color-text-muted);
|
|
232
|
-
}
|
|
233
|
-
</style>
|
|
1
|
+
<!-- src/lib/Dialog.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* @component Dialog
|
|
5
|
+
* @description Modal dialog for confirmations or alerts.
|
|
6
|
+
*
|
|
7
|
+
* @prop open {boolean} - Controls dialog visibility
|
|
8
|
+
* @default false
|
|
9
|
+
*
|
|
10
|
+
* @prop title {string} - Dialog title used for labeling
|
|
11
|
+
* @default ""
|
|
12
|
+
*
|
|
13
|
+
* @prop message {string} - Simple message content
|
|
14
|
+
* @default ""
|
|
15
|
+
*
|
|
16
|
+
* @prop onConfirm {() => void} - Fired when the confirm action is triggered
|
|
17
|
+
* @default () => {}
|
|
18
|
+
*
|
|
19
|
+
* @prop onCancel {() => void} - Fired when the cancel action is triggered
|
|
20
|
+
* @default () => {}
|
|
21
|
+
*
|
|
22
|
+
* @prop onClose {() => void} - Fired after confirm or cancel to centralize cleanup
|
|
23
|
+
* @default () => {}
|
|
24
|
+
*
|
|
25
|
+
* @prop modal {boolean} - Enables modal mode with overlay and focus trap
|
|
26
|
+
* @default true
|
|
27
|
+
*
|
|
28
|
+
* @prop class {string} - Extra classes applied to the dialog container
|
|
29
|
+
* @default ""
|
|
30
|
+
*
|
|
31
|
+
* @prop sz {SizeKey} - Size preset for padding and text
|
|
32
|
+
* @options xs|sm|md|lg|xl
|
|
33
|
+
* @default md
|
|
34
|
+
*
|
|
35
|
+
* @prop children {Snippet} - Custom dialog body content
|
|
36
|
+
*
|
|
37
|
+
* @note In modal mode the first interactive element is focused automatically and focus is trapped inside the dialog; `Escape` triggers cancel.
|
|
38
|
+
* @note `onClose` runs after `onConfirm`/`onCancel`, so you can centralize cleanup.
|
|
39
|
+
* @note Non-modal mode (`modal=false`) renders a floating panel without overlay or focus trap.
|
|
40
|
+
* @note Buttons are labeled "OK" and "Cancel" and aren't customizable via props; provide `children` for full custom UI.
|
|
41
|
+
* @note Set a meaningful `title` for accessibility; it's used as the dialog's `aria-label`.
|
|
42
|
+
* @note `sz` adjusts both dialog padding and text sizes to match the rest of the system tokens.
|
|
43
|
+
*/
|
|
44
|
+
import type { Snippet } from "svelte";
|
|
45
|
+
import Button from "./Button.svelte";
|
|
46
|
+
import { type SizeKey, TEXT } from "./types";
|
|
47
|
+
import { cx, focusFirstInteractive, trapFocus } from "../utils";
|
|
48
|
+
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
49
|
+
|
|
50
|
+
type Props = {
|
|
51
|
+
open?: boolean;
|
|
52
|
+
title?: string;
|
|
53
|
+
message?: string;
|
|
54
|
+
onConfirm?: () => void;
|
|
55
|
+
onCancel?: () => void;
|
|
56
|
+
onClose?: () => void;
|
|
57
|
+
modal?: boolean;
|
|
58
|
+
class?: string;
|
|
59
|
+
sz?: SizeKey;
|
|
60
|
+
children?: Snippet<[SizeKey]>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let {
|
|
64
|
+
open = false,
|
|
65
|
+
title = "",
|
|
66
|
+
message = "",
|
|
67
|
+
onConfirm = () => {},
|
|
68
|
+
onCancel = () => {},
|
|
69
|
+
onClose = () => {},
|
|
70
|
+
modal = true,
|
|
71
|
+
class: externalClass = "",
|
|
72
|
+
sz = "md",
|
|
73
|
+
children,
|
|
74
|
+
}: Props = $props();
|
|
75
|
+
|
|
76
|
+
const langCtx = getLangContext();
|
|
77
|
+
const langKey = $derived(getLangKey(langCtx));
|
|
78
|
+
const L = $derived(getComponentText("dialog", langKey));
|
|
79
|
+
|
|
80
|
+
let panelEl = $state<HTMLDivElement | null>(null);
|
|
81
|
+
let releaseFocus: (() => void) | null = null;
|
|
82
|
+
|
|
83
|
+
function handleConfirm() {
|
|
84
|
+
onConfirm();
|
|
85
|
+
onClose();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleCancel() {
|
|
89
|
+
onCancel();
|
|
90
|
+
onClose();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
94
|
+
if (e.key === "Escape") {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
handleCancel();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const panelBase =
|
|
101
|
+
"fusion-dialog bg-[var(--color-bg-surface)] rounded-[var(--radius-lg)] shadow-[0_8px_24px_var(--shadow-color)] min-w-0 max-w-[min(100%,28rem)] max-h-[calc(100svh-var(--spacing-lg)*2)] overflow-auto w-full border border-[var(--border-color-default)]";
|
|
102
|
+
|
|
103
|
+
const paddingBySize: Record<SizeKey, string> = {
|
|
104
|
+
xs: "p-[var(--spacing-sm)]",
|
|
105
|
+
sm: "p-[var(--spacing-md)]",
|
|
106
|
+
md: "p-[var(--spacing-lg)]",
|
|
107
|
+
lg: "p-[var(--spacing-xl)]",
|
|
108
|
+
xl: "p-[var(--spacing-xl)]",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const titleTextBySize: Record<SizeKey, string> = {
|
|
112
|
+
xs: TEXT.md,
|
|
113
|
+
sm: TEXT.md,
|
|
114
|
+
md: TEXT.xl,
|
|
115
|
+
lg: TEXT.xl,
|
|
116
|
+
xl: "text-2xl",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const panelClass = $derived(cx(panelBase, externalClass));
|
|
120
|
+
const paddingCls = $derived(paddingBySize[sz]);
|
|
121
|
+
const footerPaddingCls = $derived(cx(paddingCls, "pt-0"));
|
|
122
|
+
const titleTextCls = $derived(titleTextBySize[sz]);
|
|
123
|
+
const bodyTextCls = $derived(TEXT[sz]);
|
|
124
|
+
|
|
125
|
+
$effect(() => {
|
|
126
|
+
releaseFocus?.();
|
|
127
|
+
if (open && panelEl) {
|
|
128
|
+
queueMicrotask(() => {
|
|
129
|
+
focusFirstInteractive(panelEl!);
|
|
130
|
+
if (modal) {
|
|
131
|
+
releaseFocus = trapFocus(panelEl!);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
releaseFocus = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
releaseFocus?.();
|
|
140
|
+
releaseFocus = null;
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
{#snippet titleBlock()}
|
|
146
|
+
<h3
|
|
147
|
+
class={cx(
|
|
148
|
+
"mb-2 font-[var(--font-weight-semibold)] color-[var(--color-text-default)]",
|
|
149
|
+
titleTextCls
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
{title}
|
|
153
|
+
</h3>
|
|
154
|
+
<p
|
|
155
|
+
class={cx(
|
|
156
|
+
"leading-[var(--line-height-normal)] color-[var(--color-text-muted)]",
|
|
157
|
+
bodyTextCls
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{message}
|
|
161
|
+
</p>
|
|
162
|
+
{/snippet}
|
|
163
|
+
|
|
164
|
+
{#if open}
|
|
165
|
+
{#if modal}
|
|
166
|
+
<div
|
|
167
|
+
class="fixed inset-0 z-[var(--z-modal)] bg-[oklch(0_0_0/var(--opacity-overlay))] flex items-center justify-center p-[var(--spacing-md)]"
|
|
168
|
+
role="presentation"
|
|
169
|
+
tabindex="-1"
|
|
170
|
+
onkeydown={handleKeydown}
|
|
171
|
+
>
|
|
172
|
+
<div
|
|
173
|
+
class={panelClass}
|
|
174
|
+
role="dialog"
|
|
175
|
+
aria-modal="true"
|
|
176
|
+
aria-label={title}
|
|
177
|
+
bind:this={panelEl}
|
|
178
|
+
>
|
|
179
|
+
<div class={paddingCls}>
|
|
180
|
+
{@render titleBlock()}
|
|
181
|
+
|
|
182
|
+
{#if children}
|
|
183
|
+
<div class="mt-3">
|
|
184
|
+
{@render children?.(sz)}
|
|
185
|
+
</div>
|
|
186
|
+
{/if}
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class={cx("flex gap-2 justify-end", footerPaddingCls)}>
|
|
190
|
+
<Button {sz} variant="primary" onClick={handleConfirm}>{L.ok}</Button>
|
|
191
|
+
<Button {sz} variant="secondary" onClick={handleCancel}>{L.cancel}</Button
|
|
192
|
+
>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{:else}
|
|
197
|
+
<div
|
|
198
|
+
class="fixed top-[var(--spacing-md)] right-[var(--spacing-md)] z-[var(--z-modal)] max-w-[calc(100vw-var(--spacing-md)*2)]"
|
|
199
|
+
role="dialog"
|
|
200
|
+
aria-modal="false"
|
|
201
|
+
aria-label={title}
|
|
202
|
+
tabindex="-1"
|
|
203
|
+
onkeydown={handleKeydown}
|
|
204
|
+
>
|
|
205
|
+
<div class={panelClass} bind:this={panelEl}>
|
|
206
|
+
<div class={paddingCls}>
|
|
207
|
+
{@render titleBlock()}
|
|
208
|
+
|
|
209
|
+
{#if children}
|
|
210
|
+
<div class={cx("mt-3", bodyTextCls)}>
|
|
211
|
+
{@render children?.(sz)}
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class={cx("flex gap-2 justify-end", footerPaddingCls)}>
|
|
217
|
+
<Button {sz} variant="primary" onClick={handleConfirm}>{L.ok}</Button>
|
|
218
|
+
<Button {sz} variant="secondary" onClick={handleCancel}>{L.cancel}</Button
|
|
219
|
+
>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
{/if}
|
|
224
|
+
{/if}
|
|
225
|
+
<style>
|
|
226
|
+
:global(.fusion-dialog h3) {
|
|
227
|
+
color: var(--color-text-default);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
:global(.fusion-dialog p) {
|
|
231
|
+
color: var(--color-text-muted);
|
|
232
|
+
}
|
|
233
|
+
</style>
|