@spaethtech/svelte-ui 0.3.1-dev.9.88889c6 → 0.4.1-dev.11.2fa0c5d
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/.claude/skills/svelte-ui/SKILL.md +3 -1
- package/dist/components/ConfirmDialog.svelte +103 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +33 -0
- package/dist/components/Dialog.svelte +105 -86
- package/dist/components/Dialog.svelte.d.ts +22 -16
- package/dist/components/Query.svelte +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/components.md +18 -3
- package/docs/usage.md +37 -14
- package/package.json +1 -1
|
@@ -101,7 +101,9 @@ examples):
|
|
|
101
101
|
- **Date / time:** `DatePicker` `Calendar` `TimePicker` `TimeSpinner` `TimeRangeInput` `DateTimeInput`
|
|
102
102
|
- **Data:** `DataTable` `Query` — driven by the headless layer at **`@spaethtech/svelte-ui/data`**
|
|
103
103
|
(query-language parser/AST, `createGrid`, `DataGrid<T>`, `DataSet`).
|
|
104
|
-
- **Overlays:** `Dialog`
|
|
104
|
+
- **Overlays:** `Dialog` (pure modal shell — BYO content; `width`/`height` accept any CSS value)
|
|
105
|
+
· `ConfirmDialog` (title + message + Confirm/Cancel, built on Dialog) · `Popup` `Menu` ·
|
|
106
|
+
`tooltip` (a Svelte `use:` action) · `anchored` (positioning engine)
|
|
105
107
|
- **Navigation:** `TabStrip` `SideBarMenu`
|
|
106
108
|
- **Feedback:** `Alert` `Banner` `Badge` `Toaster` + `toast`
|
|
107
109
|
- **Layout:** `Card` `CardHeader` `CardBody` `CardFooter`
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Dialog from "./Dialog.svelte";
|
|
3
|
+
import Card from "./Card.svelte";
|
|
4
|
+
import CardHeader from "./CardHeader.svelte";
|
|
5
|
+
import CardFooter from "./CardFooter.svelte";
|
|
6
|
+
import Button from "./Button.svelte";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
import type { Size } from "../types/sizes.js";
|
|
9
|
+
import type { Responsive } from "../types/responsive.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ConfirmDialog — the common title + message + Confirm/Cancel modal, built on the `Dialog` shell.
|
|
13
|
+
* It composes a `Card` (`CardHeader` title, body, `CardFooter` actions) inside `Dialog`, so it stays
|
|
14
|
+
* consistent with every other surface. Pass `children` to replace the message body with custom
|
|
15
|
+
* content (the header + footer stay). For a fully custom modal, use `Dialog` directly.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
title?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
confirmText?: string;
|
|
22
|
+
cancelText?: string;
|
|
23
|
+
onConfirm?: () => void;
|
|
24
|
+
onCancel?: () => void;
|
|
25
|
+
variant?: "danger" | "normal";
|
|
26
|
+
/** Density tier — forwarded to the Card / CardHeader / CardFooter. */
|
|
27
|
+
size?: Responsive<Size>;
|
|
28
|
+
showConfirm?: boolean;
|
|
29
|
+
showCancel?: boolean;
|
|
30
|
+
/** Allow dismissing by backdrop / Escape (default true). */
|
|
31
|
+
dismissible?: boolean;
|
|
32
|
+
/** Forwarded to the Dialog shell — any CSS value (default: content-sized, capped at 32rem). */
|
|
33
|
+
width?: string;
|
|
34
|
+
height?: string;
|
|
35
|
+
/** Custom body between header and footer (replaces `message`). */
|
|
36
|
+
children?: Snippet;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
isOpen = $bindable(),
|
|
41
|
+
title = "Dialog",
|
|
42
|
+
message,
|
|
43
|
+
confirmText = "Confirm",
|
|
44
|
+
cancelText = "Cancel",
|
|
45
|
+
onConfirm,
|
|
46
|
+
onCancel,
|
|
47
|
+
variant = "normal",
|
|
48
|
+
size = "md",
|
|
49
|
+
showConfirm = true,
|
|
50
|
+
showCancel = true,
|
|
51
|
+
dismissible = true,
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
children,
|
|
55
|
+
}: Props = $props();
|
|
56
|
+
|
|
57
|
+
function handleConfirm() {
|
|
58
|
+
onConfirm?.();
|
|
59
|
+
isOpen = false;
|
|
60
|
+
}
|
|
61
|
+
function handleCancel() {
|
|
62
|
+
onCancel?.();
|
|
63
|
+
isOpen = false;
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<Dialog
|
|
68
|
+
bind:isOpen
|
|
69
|
+
{dismissible}
|
|
70
|
+
{width}
|
|
71
|
+
{height}
|
|
72
|
+
onClose={onCancel}
|
|
73
|
+
aria-label={title}
|
|
74
|
+
>
|
|
75
|
+
<Card {size} class="w-full shadow-xl">
|
|
76
|
+
<CardHeader {size}>
|
|
77
|
+
<h3 class="text-lg font-medium [color:var(--ui-color-text)]">{title}</h3>
|
|
78
|
+
</CardHeader>
|
|
79
|
+
|
|
80
|
+
{#if children}
|
|
81
|
+
{@render children()}
|
|
82
|
+
{:else if message}
|
|
83
|
+
<p class="text-sm [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
84
|
+
{message}
|
|
85
|
+
</p>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
{#if showConfirm || showCancel}
|
|
89
|
+
<CardFooter {size}>
|
|
90
|
+
{#if showCancel}
|
|
91
|
+
<Button text={cancelText} onclick={handleCancel} variant="secondary" />
|
|
92
|
+
{/if}
|
|
93
|
+
{#if showConfirm}
|
|
94
|
+
<Button
|
|
95
|
+
text={confirmText}
|
|
96
|
+
onclick={handleConfirm}
|
|
97
|
+
variant={variant === "danger" ? "danger" : "primary"}
|
|
98
|
+
/>
|
|
99
|
+
{/if}
|
|
100
|
+
</CardFooter>
|
|
101
|
+
{/if}
|
|
102
|
+
</Card>
|
|
103
|
+
</Dialog>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import type { Responsive } from "../types/responsive.js";
|
|
4
|
+
/**
|
|
5
|
+
* ConfirmDialog — the common title + message + Confirm/Cancel modal, built on the `Dialog` shell.
|
|
6
|
+
* It composes a `Card` (`CardHeader` title, body, `CardFooter` actions) inside `Dialog`, so it stays
|
|
7
|
+
* consistent with every other surface. Pass `children` to replace the message body with custom
|
|
8
|
+
* content (the header + footer stay). For a fully custom modal, use `Dialog` directly.
|
|
9
|
+
*/
|
|
10
|
+
interface Props {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
title?: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
confirmText?: string;
|
|
15
|
+
cancelText?: string;
|
|
16
|
+
onConfirm?: () => void;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
variant?: "danger" | "normal";
|
|
19
|
+
/** Density tier — forwarded to the Card / CardHeader / CardFooter. */
|
|
20
|
+
size?: Responsive<Size>;
|
|
21
|
+
showConfirm?: boolean;
|
|
22
|
+
showCancel?: boolean;
|
|
23
|
+
/** Allow dismissing by backdrop / Escape (default true). */
|
|
24
|
+
dismissible?: boolean;
|
|
25
|
+
/** Forwarded to the Dialog shell — any CSS value (default: content-sized, capped at 32rem). */
|
|
26
|
+
width?: string;
|
|
27
|
+
height?: string;
|
|
28
|
+
/** Custom body between header and footer (replaces `message`). */
|
|
29
|
+
children?: Snippet;
|
|
30
|
+
}
|
|
31
|
+
declare const ConfirmDialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
32
|
+
type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
|
|
33
|
+
export default ConfirmDialog;
|
|
@@ -1,57 +1,58 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import Button from "./Button.svelte";
|
|
3
|
-
import Card from "./Card.svelte";
|
|
4
|
-
import CardHeader from "./CardHeader.svelte";
|
|
5
|
-
import CardFooter from "./CardFooter.svelte";
|
|
6
2
|
import type { Snippet } from "svelte";
|
|
7
|
-
import type {
|
|
8
|
-
import type { Responsive } from "../types/responsive.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Dialog — a PURE modal shell (layout container). It owns only the modal mechanics: portals itself
|
|
7
|
+
* to <body>, locks background scroll, renders a dismissable backdrop, traps focus, and exposes the
|
|
8
|
+
* ARIA dialog role. It imposes NO content structure — put a `Card` (or anything) inside via
|
|
9
|
+
* `children`. For the common title + message + Confirm/Cancel pattern, use `ConfirmDialog`, which is
|
|
10
|
+
* built on this.
|
|
11
|
+
*
|
|
12
|
+
* `width` / `height` accept any CSS length: `"80%"`, `"80vw"`, `"60vh"`, `"400px"`, … (both default
|
|
13
|
+
* to content size, capped to the viewport minus a 1rem inset so the panel never runs off-screen).
|
|
14
|
+
*/
|
|
15
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
16
|
+
/** Whether the dialog is shown (bindable). */
|
|
11
17
|
isOpen: boolean;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onCancel?: () => void;
|
|
18
|
-
variant?: "danger" | "normal";
|
|
19
|
-
/** Density tier — forwarded to the underlying Card / CardHeader / CardFooter. */
|
|
20
|
-
size?: Responsive<Size>;
|
|
21
|
-
showConfirm?: boolean;
|
|
22
|
-
showCancel?: boolean;
|
|
23
|
-
/** Allow dismissing by clicking the backdrop OR pressing Escape (default true). Set `false` for a
|
|
24
|
-
* must-decide dialog (e.g. a destructive confirm) so only the buttons close it. */
|
|
18
|
+
/** Panel width — any CSS value (`80%`, `80vw`, `400px`). Default: auto, capped at `min(32rem, …)`. */
|
|
19
|
+
width?: string;
|
|
20
|
+
/** Panel height — any CSS value (`60%`, `60vh`, `300px`). Default: auto (content height). */
|
|
21
|
+
height?: string;
|
|
22
|
+
/** Allow dismissing via backdrop click / Escape (default true). Set false for a must-decide modal. */
|
|
25
23
|
dismissible?: boolean;
|
|
24
|
+
/** Called when dismissed via backdrop / Escape (isOpen is also set false). */
|
|
25
|
+
onClose?: () => void;
|
|
26
|
+
/** Extra classes on the panel wrapper. */
|
|
27
|
+
class?: string;
|
|
26
28
|
children?: Snippet;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
let {
|
|
30
32
|
isOpen = $bindable(),
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
confirmText = "Confirm",
|
|
34
|
-
cancelText = "Cancel",
|
|
35
|
-
onConfirm,
|
|
36
|
-
onCancel,
|
|
37
|
-
variant = "normal",
|
|
38
|
-
size = "md",
|
|
39
|
-
showConfirm = true,
|
|
40
|
-
showCancel = true,
|
|
33
|
+
width,
|
|
34
|
+
height,
|
|
41
35
|
dismissible = true,
|
|
36
|
+
onClose,
|
|
37
|
+
class: cls = "",
|
|
42
38
|
children,
|
|
39
|
+
...rest
|
|
43
40
|
}: Props = $props();
|
|
44
41
|
|
|
42
|
+
const FOCUSABLE =
|
|
43
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
44
|
+
|
|
45
|
+
let panelEl: HTMLElement | undefined = $state();
|
|
46
|
+
let previouslyFocused: HTMLElement | null = null;
|
|
47
|
+
|
|
45
48
|
// Move the overlay to <body> so it's a real top-level layer — never a sibling of the trigger (which
|
|
46
|
-
// would perturb surrounding layout
|
|
47
|
-
// ancestor's overflow/stacking context. This is what makes it an actual modal, not an inline box.
|
|
49
|
+
// would perturb surrounding layout) and never clipped by an ancestor's overflow/stacking context.
|
|
48
50
|
function portal(node: HTMLElement) {
|
|
49
51
|
document.body.appendChild(node);
|
|
50
52
|
return { destroy: () => node.remove() };
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
// Lock background scroll while open; pad by the scrollbar width so hiding it doesn't shift the page
|
|
54
|
-
// sideways. Restore exactly what was there on close/destroy.
|
|
55
|
+
// Lock background scroll while open; pad by the scrollbar width so hiding it doesn't shift the page.
|
|
55
56
|
$effect(() => {
|
|
56
57
|
if (!isOpen) return;
|
|
57
58
|
const sbw = window.innerWidth - document.documentElement.clientWidth;
|
|
@@ -65,68 +66,86 @@
|
|
|
65
66
|
};
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
// Focus management: on open, remember what was focused and move focus into the panel; on close,
|
|
70
|
+
// restore it. (Tab-wrapping is handled in onKeydown.)
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (!isOpen) return;
|
|
73
|
+
previouslyFocused = document.activeElement as HTMLElement | null;
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
const first = panelEl?.querySelector<HTMLElement>(FOCUSABLE);
|
|
76
|
+
(first ?? panelEl)?.focus();
|
|
77
|
+
});
|
|
78
|
+
return () => previouslyFocused?.focus?.();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const panelStyle = $derived(
|
|
82
|
+
[
|
|
83
|
+
width ? `width:${width}` : "width:auto",
|
|
84
|
+
`max-width:${width ? "calc(100vw - 2rem)" : "min(32rem, calc(100vw - 2rem))"}`,
|
|
85
|
+
height ? `height:${height}` : null,
|
|
86
|
+
"max-height:calc(100vh - 2rem)",
|
|
87
|
+
]
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join(";"),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
function close() {
|
|
93
|
+
if (!dismissible) return;
|
|
94
|
+
onClose?.();
|
|
74
95
|
isOpen = false;
|
|
75
96
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (dismissible) handleCancel();
|
|
79
|
-
}
|
|
80
|
-
function handleBackdropClick(event: MouseEvent) {
|
|
81
|
-
if (event.target === event.currentTarget) dismiss();
|
|
97
|
+
function onBackdrop(event: MouseEvent) {
|
|
98
|
+
if (event.target === event.currentTarget) close();
|
|
82
99
|
}
|
|
83
|
-
function
|
|
84
|
-
if (isOpen
|
|
100
|
+
function onKeydown(event: KeyboardEvent) {
|
|
101
|
+
if (!isOpen) return;
|
|
102
|
+
if (event.key === "Escape") {
|
|
103
|
+
close();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (event.key === "Tab" && panelEl) {
|
|
107
|
+
const items = [...panelEl.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(
|
|
108
|
+
(el) => el.offsetParent !== null,
|
|
109
|
+
);
|
|
110
|
+
if (items.length === 0) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
panelEl.focus();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const first = items[0];
|
|
116
|
+
const last = items[items.length - 1];
|
|
117
|
+
const active = document.activeElement;
|
|
118
|
+
if (event.shiftKey && (active === first || active === panelEl)) {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
last.focus();
|
|
121
|
+
} else if (!event.shiftKey && active === last) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
first.focus();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
85
126
|
}
|
|
86
127
|
</script>
|
|
87
128
|
|
|
88
|
-
<svelte:window on:keydown={
|
|
129
|
+
<svelte:window on:keydown={onKeydown} />
|
|
89
130
|
|
|
90
131
|
{#if isOpen}
|
|
132
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
91
133
|
<div
|
|
92
134
|
use:portal
|
|
93
|
-
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]"
|
|
94
|
-
onclick={
|
|
95
|
-
|
|
96
|
-
role="dialog"
|
|
97
|
-
aria-modal="true"
|
|
98
|
-
tabindex="-1"
|
|
135
|
+
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999] p-4"
|
|
136
|
+
onclick={onBackdrop}
|
|
137
|
+
role="presentation"
|
|
99
138
|
>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<p class="text-sm [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
112
|
-
{message}
|
|
113
|
-
</p>
|
|
114
|
-
{/if}
|
|
115
|
-
|
|
116
|
-
{#if showConfirm || showCancel}
|
|
117
|
-
<CardFooter {size}>
|
|
118
|
-
{#if showCancel}
|
|
119
|
-
<Button text={cancelText} onclick={handleCancel} variant="secondary" />
|
|
120
|
-
{/if}
|
|
121
|
-
{#if showConfirm}
|
|
122
|
-
<Button
|
|
123
|
-
text={confirmText}
|
|
124
|
-
onclick={handleConfirm}
|
|
125
|
-
variant={variant === "danger" ? "danger" : "primary"}
|
|
126
|
-
/>
|
|
127
|
-
{/if}
|
|
128
|
-
</CardFooter>
|
|
129
|
-
{/if}
|
|
130
|
-
</Card>
|
|
139
|
+
<div
|
|
140
|
+
bind:this={panelEl}
|
|
141
|
+
class="overflow-auto z-[10000] {cls}"
|
|
142
|
+
style={panelStyle}
|
|
143
|
+
role="dialog"
|
|
144
|
+
aria-modal="true"
|
|
145
|
+
tabindex="-1"
|
|
146
|
+
{...rest}
|
|
147
|
+
>
|
|
148
|
+
{@render children?.()}
|
|
149
|
+
</div>
|
|
131
150
|
</div>
|
|
132
151
|
{/if}
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* Dialog — a PURE modal shell (layout container). It owns only the modal mechanics: portals itself
|
|
5
|
+
* to <body>, locks background scroll, renders a dismissable backdrop, traps focus, and exposes the
|
|
6
|
+
* ARIA dialog role. It imposes NO content structure — put a `Card` (or anything) inside via
|
|
7
|
+
* `children`. For the common title + message + Confirm/Cancel pattern, use `ConfirmDialog`, which is
|
|
8
|
+
* built on this.
|
|
9
|
+
*
|
|
10
|
+
* `width` / `height` accept any CSS length: `"80%"`, `"80vw"`, `"60vh"`, `"400px"`, … (both default
|
|
11
|
+
* to content size, capped to the viewport minus a 1rem inset so the panel never runs off-screen).
|
|
12
|
+
*/
|
|
13
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
14
|
+
/** Whether the dialog is shown (bindable). */
|
|
5
15
|
isOpen: boolean;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
onCancel?: () => void;
|
|
12
|
-
variant?: "danger" | "normal";
|
|
13
|
-
/** Density tier — forwarded to the underlying Card / CardHeader / CardFooter. */
|
|
14
|
-
size?: Responsive<Size>;
|
|
15
|
-
showConfirm?: boolean;
|
|
16
|
-
showCancel?: boolean;
|
|
17
|
-
/** Allow dismissing by clicking the backdrop OR pressing Escape (default true). Set `false` for a
|
|
18
|
-
* must-decide dialog (e.g. a destructive confirm) so only the buttons close it. */
|
|
16
|
+
/** Panel width — any CSS value (`80%`, `80vw`, `400px`). Default: auto, capped at `min(32rem, …)`. */
|
|
17
|
+
width?: string;
|
|
18
|
+
/** Panel height — any CSS value (`60%`, `60vh`, `300px`). Default: auto (content height). */
|
|
19
|
+
height?: string;
|
|
20
|
+
/** Allow dismissing via backdrop click / Escape (default true). Set false for a must-decide modal. */
|
|
19
21
|
dismissible?: boolean;
|
|
22
|
+
/** Called when dismissed via backdrop / Escape (isOpen is also set false). */
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
/** Extra classes on the panel wrapper. */
|
|
25
|
+
class?: string;
|
|
20
26
|
children?: Snippet;
|
|
21
27
|
}
|
|
22
28
|
declare const Dialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import Input from "./Input.svelte";
|
|
15
15
|
import Button from "./Button.svelte";
|
|
16
16
|
import Popup from "./Popup.svelte";
|
|
17
|
-
import
|
|
17
|
+
import ConfirmDialog from "./ConfirmDialog.svelte";
|
|
18
18
|
import type { DataSet } from "../data/dataset.js";
|
|
19
19
|
import type { Size } from "../types/sizes.js";
|
|
20
20
|
import type { Responsive } from "../types/responsive.js";
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
</ul>
|
|
228
228
|
</Popup>
|
|
229
229
|
|
|
230
|
-
<
|
|
230
|
+
<ConfirmDialog bind:isOpen={helpOpen} title="Query syntax" showConfirm={false} showCancel={false}>
|
|
231
231
|
<div class="max-h-[70vh] space-y-4 overflow-y-auto text-sm [color:var(--ui-color-text)]">
|
|
232
232
|
<p>
|
|
233
233
|
A filter is one or more conditions, <code class="font-mono">$column operator value</code>.
|
|
@@ -281,4 +281,4 @@ $ip|inet |= [10.0.0.0|inet..10.255.255.255|inet]
|
|
|
281
281
|
values. Applies on <kbd class="rounded border px-1 {bc}">Enter</kbd>.
|
|
282
282
|
</p>
|
|
283
283
|
</div>
|
|
284
|
-
</
|
|
284
|
+
</ConfirmDialog>
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
|
10
10
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
11
11
|
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
|
|
12
12
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
13
|
+
export { default as ConfirmDialog } from "./components/ConfirmDialog.svelte";
|
|
13
14
|
export { default as Input } from "./components/Input.svelte";
|
|
14
15
|
export { default as NotesEditor } from "./components/NotesEditor.svelte";
|
|
15
16
|
export { default as Rating } from "./components/Rating.svelte";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
|
12
12
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
13
13
|
export { toast, getToasts, dismissToast, } from "./components/Toast/toast.svelte.js";
|
|
14
14
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
15
|
+
export { default as ConfirmDialog } from "./components/ConfirmDialog.svelte";
|
|
15
16
|
export { default as Input } from "./components/Input.svelte";
|
|
16
17
|
export { default as NotesEditor } from "./components/NotesEditor.svelte";
|
|
17
18
|
export { default as Rating } from "./components/Rating.svelte";
|
package/docs/components.md
CHANGED
|
@@ -142,12 +142,27 @@ dialog.
|
|
|
142
142
|
|
|
143
143
|
### Dialog
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
**Pure modal shell** — the mechanics only, no imposed content. Put a `Card` (or anything) inside via
|
|
146
|
+
`children`. For the common confirm pattern use `ConfirmDialog` (below).
|
|
146
147
|
|
|
147
148
|
- **Location**: `src/lib/components/Dialog.svelte`
|
|
149
|
+
- **Props**: `isOpen` (bindable), `width` (any CSS value — `'80%'`, `'80vw'`, `'400px'`; default
|
|
150
|
+
auto, capped at `min(32rem, …)`), `height` (any CSS value; default auto), `dismissible` (default
|
|
151
|
+
`true`), `onClose`, `class`, `children` (snippet). Extra attributes (e.g. `aria-label`) spread onto
|
|
152
|
+
the panel.
|
|
153
|
+
- **Features**: portals to `<body>`, background scroll-lock (scrollbar-width compensated), backdrop
|
|
154
|
+
+ Escape dismiss, **focus trap** (initial focus in, Tab wraps, focus restored on close),
|
|
155
|
+
`role="dialog"` + `aria-modal`.
|
|
156
|
+
|
|
157
|
+
### ConfirmDialog
|
|
158
|
+
|
|
159
|
+
The common title + message + Confirm/Cancel modal, composed from a `Card` on top of the `Dialog`
|
|
160
|
+
shell.
|
|
161
|
+
|
|
162
|
+
- **Location**: `src/lib/components/ConfirmDialog.svelte`
|
|
148
163
|
- **Props**: `isOpen` (bindable), `title`, `message`, `confirmText`, `cancelText`, `onConfirm`,
|
|
149
|
-
`onCancel`, `variant` (`'normal' | 'danger'`), `showConfirm`, `showCancel`, `
|
|
150
|
-
|
|
164
|
+
`onCancel`, `variant` (`'normal' | 'danger'`), `size`, `showConfirm`, `showCancel`, `dismissible`,
|
|
165
|
+
`width`/`height` (forwarded to the shell), `children` (snippet — replaces `message` as the body).
|
|
151
166
|
|
|
152
167
|
### Popup
|
|
153
168
|
|
package/docs/usage.md
CHANGED
|
@@ -212,41 +212,64 @@ into a component's `icon` snippet — the library sizes and colors it:
|
|
|
212
212
|
<Checkbox disabled />
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
-
###
|
|
215
|
+
### ConfirmDialog
|
|
216
|
+
|
|
217
|
+
The common title + message + Confirm/Cancel modal (composed from a `Card` on the `Dialog` shell).
|
|
216
218
|
|
|
217
219
|
```svelte
|
|
218
220
|
<script>
|
|
219
|
-
import {
|
|
221
|
+
import { ConfirmDialog, Button } from "@spaethtech/svelte-ui";
|
|
220
222
|
|
|
221
|
-
let
|
|
223
|
+
let showConfirm = $state(false);
|
|
222
224
|
let showDanger = $state(false);
|
|
223
225
|
</script>
|
|
224
226
|
|
|
225
227
|
<!-- Basic confirmation -->
|
|
226
|
-
<Button text="Show Dialog" onclick={() => (
|
|
227
|
-
<
|
|
228
|
-
bind:isOpen={
|
|
228
|
+
<Button text="Show Dialog" onclick={() => (showConfirm = true)} />
|
|
229
|
+
<ConfirmDialog
|
|
230
|
+
bind:isOpen={showConfirm}
|
|
229
231
|
title="Confirm Action"
|
|
230
232
|
message="Are you sure you want to proceed?"
|
|
231
233
|
onConfirm={() => console.log("Confirmed")}
|
|
232
234
|
/>
|
|
233
235
|
|
|
234
|
-
<!-- Danger
|
|
235
|
-
<
|
|
236
|
+
<!-- Danger + must-decide (no backdrop/Escape dismiss) -->
|
|
237
|
+
<ConfirmDialog
|
|
236
238
|
bind:isOpen={showDanger}
|
|
237
239
|
title="Delete Item"
|
|
238
240
|
message="This action cannot be undone!"
|
|
239
241
|
variant="danger"
|
|
240
242
|
confirmText="Delete"
|
|
243
|
+
dismissible={false}
|
|
241
244
|
onConfirm={() => console.log("Deleted")}
|
|
242
245
|
/>
|
|
246
|
+
```
|
|
243
247
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
### Dialog (shell)
|
|
249
|
+
|
|
250
|
+
A pure modal shell — you supply the content. Use it for anything beyond the confirm pattern; size it
|
|
251
|
+
with `width` / `height` (any CSS value).
|
|
252
|
+
|
|
253
|
+
```svelte
|
|
254
|
+
<script>
|
|
255
|
+
import { Dialog, Card, CardHeader, CardFooter, Button, Input } from "@spaethtech/svelte-ui";
|
|
256
|
+
|
|
257
|
+
let open = $state(false);
|
|
258
|
+
</script>
|
|
259
|
+
|
|
260
|
+
<Button text="Open" onclick={() => (open = true)} />
|
|
261
|
+
|
|
262
|
+
<Dialog bind:isOpen={open} width="80vw" height="60vh">
|
|
263
|
+
<Card class="w-full h-full flex flex-col">
|
|
264
|
+
<CardHeader><h3 class="text-lg font-medium">Custom modal</h3></CardHeader>
|
|
265
|
+
<div class="flex-1 overflow-auto space-y-2">
|
|
266
|
+
<p>You own the Card — control its separator, variant, sizing, and body.</p>
|
|
267
|
+
<Input placeholder="Enter value" />
|
|
268
|
+
</div>
|
|
269
|
+
<CardFooter separator={false}>
|
|
270
|
+
<Button text="Close" variant="secondary" onclick={() => (open = false)} />
|
|
271
|
+
</CardFooter>
|
|
272
|
+
</Card>
|
|
250
273
|
</Dialog>
|
|
251
274
|
```
|
|
252
275
|
|