@makolabs/ripple 3.0.8 → 3.0.10
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/dist/drawer/Drawer.svelte +33 -12
- package/dist/drawer/drawer-types.d.ts +13 -5
- package/dist/drawer/drawer.d.ts +45 -0
- package/dist/drawer/drawer.js +14 -5
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/layout/table/Table.svelte +2 -23
- package/dist/modal/ConfirmDialog.svelte +6 -9
- package/dist/modal/Modal.svelte +18 -14
- package/dist/modal/modal-types.d.ts +9 -5
- package/dist/modal/modal.d.ts +42 -0
- package/dist/modal/modal.js +18 -8
- package/dist/user-management/UserManagement.svelte +13 -1
- package/dist/user-management/UserModal.svelte +27 -28
- package/package.json +1 -1
- package/dist/modal/ModalFooter.svelte +0 -35
- package/dist/modal/ModalFooter.svelte.d.ts +0 -11
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
open = $bindable(false),
|
|
13
13
|
onclose: onClose = () => {},
|
|
14
14
|
title,
|
|
15
|
+
description,
|
|
15
16
|
position = 'right',
|
|
16
17
|
size = 'sm',
|
|
18
|
+
footerAlign,
|
|
17
19
|
children,
|
|
18
20
|
header,
|
|
19
21
|
footer,
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
headerClass = '',
|
|
24
26
|
bodyClass = '',
|
|
25
27
|
titleClass = '',
|
|
28
|
+
descriptionClass = '',
|
|
26
29
|
footerClass = '',
|
|
27
30
|
testId
|
|
28
31
|
}: DrawerProps = $props();
|
|
@@ -37,12 +40,14 @@
|
|
|
37
40
|
header: headerVClass,
|
|
38
41
|
body,
|
|
39
42
|
footer: footerVClass,
|
|
40
|
-
title: titleVClass
|
|
43
|
+
title: titleVClass,
|
|
44
|
+
description: descriptionVClass
|
|
41
45
|
} = $derived(
|
|
42
46
|
drawer({
|
|
43
47
|
open,
|
|
44
48
|
position,
|
|
45
|
-
size
|
|
49
|
+
size,
|
|
50
|
+
footerAlign
|
|
46
51
|
})
|
|
47
52
|
);
|
|
48
53
|
|
|
@@ -53,6 +58,7 @@
|
|
|
53
58
|
const headerClasses = $derived(cn(headerVClass(), headerClass));
|
|
54
59
|
const bodyClasses = $derived(cn(body(), bodyClass));
|
|
55
60
|
const titleClasses = $derived(cn(titleVClass(), titleClass));
|
|
61
|
+
const descriptionClasses = $derived(cn(descriptionVClass(), descriptionClass));
|
|
56
62
|
const footerClasses = $derived(cn(footerVClass(), 'mt-auto', footerClass));
|
|
57
63
|
|
|
58
64
|
function handleBackdropClick(e: MouseEvent) {
|
|
@@ -161,6 +167,8 @@
|
|
|
161
167
|
role="dialog"
|
|
162
168
|
aria-modal="true"
|
|
163
169
|
aria-labelledby={title ? 'drawer-title' : undefined}
|
|
170
|
+
aria-describedby={description ? 'drawer-description' : undefined}
|
|
171
|
+
aria-label={!title ? 'Drawer' : undefined}
|
|
164
172
|
>
|
|
165
173
|
{#key open}
|
|
166
174
|
<!-- Backdrop -->
|
|
@@ -196,19 +204,32 @@
|
|
|
196
204
|
</button>
|
|
197
205
|
|
|
198
206
|
<!-- Header -->
|
|
199
|
-
{#if
|
|
207
|
+
{#if header}
|
|
200
208
|
<div class={headerClasses}>
|
|
201
|
-
|
|
202
|
-
id="drawer-title"
|
|
203
|
-
class={titleClasses}
|
|
204
|
-
data-testid={buildTestId('drawer', 'title', testId)}
|
|
205
|
-
>
|
|
206
|
-
{title}
|
|
207
|
-
</h3>
|
|
209
|
+
{@render header()}
|
|
208
210
|
</div>
|
|
209
|
-
{:else if
|
|
211
|
+
{:else if title || description}
|
|
210
212
|
<div class={headerClasses}>
|
|
211
|
-
|
|
213
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
214
|
+
{#if title}
|
|
215
|
+
<h3
|
|
216
|
+
id="drawer-title"
|
|
217
|
+
class={titleClasses}
|
|
218
|
+
data-testid={buildTestId('drawer', 'title', testId)}
|
|
219
|
+
>
|
|
220
|
+
{title}
|
|
221
|
+
</h3>
|
|
222
|
+
{/if}
|
|
223
|
+
{#if description}
|
|
224
|
+
<p
|
|
225
|
+
id="drawer-description"
|
|
226
|
+
class={descriptionClasses}
|
|
227
|
+
data-testid={buildTestId('drawer', 'description', testId)}
|
|
228
|
+
>
|
|
229
|
+
{description}
|
|
230
|
+
</p>
|
|
231
|
+
{/if}
|
|
232
|
+
</div>
|
|
212
233
|
</div>
|
|
213
234
|
{/if}
|
|
214
235
|
|
|
@@ -12,10 +12,8 @@ import type { VariantSizes } from '../index.js';
|
|
|
12
12
|
* <Drawer bind:open title="Filters" position="right">
|
|
13
13
|
* <FilterPanel bind:filters />
|
|
14
14
|
* {#snippet footer()}
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
17
|
-
* <Button onclick={apply}>Apply</Button>
|
|
18
|
-
* </div>
|
|
15
|
+
* <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
16
|
+
* <Button onclick={apply}>Apply</Button>
|
|
19
17
|
* {/snippet}
|
|
20
18
|
* </Drawer>
|
|
21
19
|
* ```
|
|
@@ -27,6 +25,8 @@ export type DrawerProps = {
|
|
|
27
25
|
onclose?: () => void;
|
|
28
26
|
/** Header title text. Ignored if a `header` snippet is provided. */
|
|
29
27
|
title?: string;
|
|
28
|
+
/** Smaller muted line directly below the title. */
|
|
29
|
+
description?: string;
|
|
30
30
|
/** Side the drawer slides in from. @default 'right' */
|
|
31
31
|
position?: 'left' | 'right';
|
|
32
32
|
/** @default 'sm' */
|
|
@@ -48,7 +48,15 @@ export type DrawerProps = {
|
|
|
48
48
|
children?: Snippet;
|
|
49
49
|
/** Replace the default title bar. */
|
|
50
50
|
header?: Snippet;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Footer content — typically action buttons. The footer wrapper renders
|
|
53
|
+
* with a tinted background, hairline separator, padding, and flex layout
|
|
54
|
+
* built-in, so the snippet can just contain `<Button>`s directly.
|
|
55
|
+
*/
|
|
52
56
|
footer?: Snippet;
|
|
57
|
+
/** How to align footer items horizontally. @default 'end' */
|
|
58
|
+
footerAlign?: 'start' | 'center' | 'end' | 'between';
|
|
59
|
+
/** Classes on the description element. */
|
|
60
|
+
descriptionClass?: ClassValue;
|
|
53
61
|
testId?: string;
|
|
54
62
|
};
|
package/dist/drawer/drawer.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
footerAlign: {
|
|
3
|
+
start: {
|
|
4
|
+
footer: string;
|
|
5
|
+
};
|
|
6
|
+
center: {
|
|
7
|
+
footer: string;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
footer: string;
|
|
11
|
+
};
|
|
12
|
+
between: {
|
|
13
|
+
footer: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
open: {
|
|
3
17
|
true: {
|
|
4
18
|
base: string;
|
|
@@ -50,8 +64,23 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
50
64
|
body: string;
|
|
51
65
|
footer: string;
|
|
52
66
|
title: string;
|
|
67
|
+
description: string;
|
|
53
68
|
closeButton: string;
|
|
54
69
|
}, undefined, {
|
|
70
|
+
footerAlign: {
|
|
71
|
+
start: {
|
|
72
|
+
footer: string;
|
|
73
|
+
};
|
|
74
|
+
center: {
|
|
75
|
+
footer: string;
|
|
76
|
+
};
|
|
77
|
+
end: {
|
|
78
|
+
footer: string;
|
|
79
|
+
};
|
|
80
|
+
between: {
|
|
81
|
+
footer: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
55
84
|
open: {
|
|
56
85
|
true: {
|
|
57
86
|
base: string;
|
|
@@ -103,8 +132,23 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
103
132
|
body: string;
|
|
104
133
|
footer: string;
|
|
105
134
|
title: string;
|
|
135
|
+
description: string;
|
|
106
136
|
closeButton: string;
|
|
107
137
|
}, import("tailwind-variants").TVReturnType<{
|
|
138
|
+
footerAlign: {
|
|
139
|
+
start: {
|
|
140
|
+
footer: string;
|
|
141
|
+
};
|
|
142
|
+
center: {
|
|
143
|
+
footer: string;
|
|
144
|
+
};
|
|
145
|
+
end: {
|
|
146
|
+
footer: string;
|
|
147
|
+
};
|
|
148
|
+
between: {
|
|
149
|
+
footer: string;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
108
152
|
open: {
|
|
109
153
|
true: {
|
|
110
154
|
base: string;
|
|
@@ -156,5 +200,6 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
156
200
|
body: string;
|
|
157
201
|
footer: string;
|
|
158
202
|
title: string;
|
|
203
|
+
description: string;
|
|
159
204
|
closeButton: string;
|
|
160
205
|
}, undefined, unknown, unknown, undefined>>;
|
package/dist/drawer/drawer.js
CHANGED
|
@@ -6,13 +6,21 @@ export const drawer = tv({
|
|
|
6
6
|
backdrop: 'fixed inset-0 transition-opacity bg-black/50',
|
|
7
7
|
contentWrapper: 'absolute flex flex-col transform transition-transform max-w-[100vw]',
|
|
8
8
|
content: 'relative flex flex-col h-full w-full overflow-y-auto bg-white',
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// Header/footer tinted to match Modal/Card visual language.
|
|
10
|
+
header: 'flex items-center justify-between gap-3 px-5 py-3 bg-default-100/50 border-b border-default-200',
|
|
11
|
+
body: 'flex-1 overflow-y-auto p-5 text-sm text-default-700',
|
|
12
|
+
footer: 'flex flex-wrap items-center gap-3 px-5 py-3 bg-default-100/50 border-t border-default-200',
|
|
13
|
+
title: 'text-default-900 text-sm font-semibold leading-tight',
|
|
14
|
+
description: 'text-default-500 text-xs font-normal',
|
|
13
15
|
closeButton: 'text-default-400 hover:text-default-500 rounded-md cursor-pointer'
|
|
14
16
|
},
|
|
15
17
|
variants: {
|
|
18
|
+
footerAlign: {
|
|
19
|
+
start: { footer: 'justify-start' },
|
|
20
|
+
center: { footer: 'justify-center' },
|
|
21
|
+
end: { footer: 'justify-end' },
|
|
22
|
+
between: { footer: 'justify-between' }
|
|
23
|
+
},
|
|
16
24
|
open: {
|
|
17
25
|
true: {
|
|
18
26
|
base: 'visible',
|
|
@@ -76,6 +84,7 @@ export const drawer = tv({
|
|
|
76
84
|
defaultVariants: {
|
|
77
85
|
open: false,
|
|
78
86
|
position: 'right',
|
|
79
|
-
size: 'sm'
|
|
87
|
+
size: 'sm',
|
|
88
|
+
footerAlign: 'end'
|
|
80
89
|
}
|
|
81
90
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -65,7 +65,6 @@ export { tv, cn } from './helper/cls.js';
|
|
|
65
65
|
export { isRouteActive } from './helper/nav.svelte.js';
|
|
66
66
|
export { default as Button } from './button/Button.svelte';
|
|
67
67
|
export { default as Modal } from './modal/Modal.svelte';
|
|
68
|
-
export { default as ModalFooter } from './modal/ModalFooter.svelte';
|
|
69
68
|
export { default as ConfirmDialog } from './modal/ConfirmDialog.svelte';
|
|
70
69
|
export { default as Pipeline } from './pipeline/Pipeline.svelte';
|
|
71
70
|
export { pipelineVariants } from './pipeline/pipeline.js';
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,6 @@ export { isRouteActive } from './helper/nav.svelte.js';
|
|
|
25
25
|
export { default as Button } from './button/Button.svelte';
|
|
26
26
|
// Modal
|
|
27
27
|
export { default as Modal } from './modal/Modal.svelte';
|
|
28
|
-
export { default as ModalFooter } from './modal/ModalFooter.svelte';
|
|
29
28
|
export { default as ConfirmDialog } from './modal/ConfirmDialog.svelte';
|
|
30
29
|
// Pipeline
|
|
31
30
|
export { default as Pipeline } from './pipeline/Pipeline.svelte';
|
|
@@ -607,29 +607,8 @@
|
|
|
607
607
|
{/snippet}
|
|
608
608
|
|
|
609
609
|
{#if hasHeader}
|
|
610
|
-
<Card>
|
|
611
|
-
{
|
|
612
|
-
<!-- Header Section -->
|
|
613
|
-
<div class="border-default-200 mb-4 border-b pb-3">
|
|
614
|
-
<div class="flex items-center justify-between">
|
|
615
|
-
<div>
|
|
616
|
-
{#if title}
|
|
617
|
-
<h2 class="text-default-900 text-lg font-semibold">{title}</h2>
|
|
618
|
-
{/if}
|
|
619
|
-
{#if subtitle}
|
|
620
|
-
<p class="text-default-500 mt-1 text-xs">{subtitle}</p>
|
|
621
|
-
{/if}
|
|
622
|
-
</div>
|
|
623
|
-
{#if headerActions}
|
|
624
|
-
<div class="flex items-center">
|
|
625
|
-
{@render headerActions()}
|
|
626
|
-
</div>
|
|
627
|
-
{/if}
|
|
628
|
-
</div>
|
|
629
|
-
</div>
|
|
630
|
-
|
|
631
|
-
{@render tableContent()}
|
|
632
|
-
{/snippet}
|
|
610
|
+
<Card {title} {subtitle} headerEnd={headerActions} bodyClass="p-0">
|
|
611
|
+
{@render tableContent()}
|
|
633
612
|
</Card>
|
|
634
613
|
{:else}
|
|
635
614
|
{@render tableContent()}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Modal from './Modal.svelte';
|
|
3
|
-
import ModalFooter from './ModalFooter.svelte';
|
|
4
3
|
import Button from '../button/Button.svelte';
|
|
5
4
|
import type { ConfirmDialogProps } from './confirm-dialog-types.js';
|
|
6
5
|
import { Color } from '../variants.js';
|
|
@@ -53,13 +52,11 @@
|
|
|
53
52
|
</div>
|
|
54
53
|
|
|
55
54
|
{#snippet footer()}
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
</Button>
|
|
63
|
-
</ModalFooter>
|
|
55
|
+
<Button variant="outline" onclick={handleCancel} disabled={busy}>
|
|
56
|
+
{cancelLabel}
|
|
57
|
+
</Button>
|
|
58
|
+
<Button color={confirmColor} onclick={handleConfirm} loading={busy}>
|
|
59
|
+
{confirmLabel}
|
|
60
|
+
</Button>
|
|
64
61
|
{/snippet}
|
|
65
62
|
</Modal>
|
package/dist/modal/Modal.svelte
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
title,
|
|
15
15
|
description,
|
|
16
16
|
size,
|
|
17
|
+
footerAlign,
|
|
17
18
|
hideCloseButton = false,
|
|
18
19
|
class: className = '',
|
|
19
20
|
contentClass = '',
|
|
@@ -30,25 +31,17 @@
|
|
|
30
31
|
|
|
31
32
|
let modalElement: HTMLDivElement | undefined = $state();
|
|
32
33
|
|
|
33
|
-
const styles = $derived(modal({ size }));
|
|
34
|
+
const styles = $derived(modal({ size, footerAlign }));
|
|
34
35
|
|
|
35
36
|
const baseClass = $derived(cn(styles.base(), className));
|
|
36
37
|
const backdropClasses = $derived(cn(styles.backdrop(), backdropClass));
|
|
37
38
|
const containerClass = $derived(cn(styles.container(), contentClass));
|
|
38
39
|
const headerClasses = $derived(cn(styles.header(), headerClass));
|
|
39
40
|
const titleClasses = $derived(cn(styles.title(), titleClass));
|
|
40
|
-
const bodyClasses = $derived(
|
|
41
|
-
cn(
|
|
42
|
-
'flex-1 px-6 overflow-y-auto',
|
|
43
|
-
// Adjust top padding based on header presence
|
|
44
|
-
title || description ? 'py-4' : 'pt-6 pb-4',
|
|
45
|
-
bodyClass
|
|
46
|
-
)
|
|
47
|
-
);
|
|
41
|
+
const bodyClasses = $derived(cn(styles.body(), bodyClass));
|
|
48
42
|
const footerClasses = $derived(cn(styles.footer(), footerClass));
|
|
49
43
|
const closeClass = $derived(cn(styles.close(), ''));
|
|
50
|
-
|
|
51
|
-
const descriptionClass = $derived(cn(styles.description(), ''));
|
|
44
|
+
const descriptionClasses = $derived(cn(styles.description(), ''));
|
|
52
45
|
|
|
53
46
|
function handleBackdropClick() {
|
|
54
47
|
onclose();
|
|
@@ -131,15 +124,24 @@
|
|
|
131
124
|
{#snippet predefinedHeader()}
|
|
132
125
|
{#if title || description}
|
|
133
126
|
<header class={headerClasses}>
|
|
134
|
-
<div class="flex-1">
|
|
127
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
135
128
|
{#if title}
|
|
136
|
-
<
|
|
129
|
+
<h3
|
|
137
130
|
id="modal-title"
|
|
138
131
|
class={titleClasses}
|
|
139
132
|
data-testid={buildTestId('modal', 'title', testId)}
|
|
140
133
|
>
|
|
141
134
|
{title}
|
|
142
|
-
</
|
|
135
|
+
</h3>
|
|
136
|
+
{/if}
|
|
137
|
+
{#if description}
|
|
138
|
+
<p
|
|
139
|
+
id="modal-description"
|
|
140
|
+
class={descriptionClasses}
|
|
141
|
+
data-testid={buildTestId('modal', 'description', testId)}
|
|
142
|
+
>
|
|
143
|
+
{description}
|
|
144
|
+
</p>
|
|
143
145
|
{/if}
|
|
144
146
|
</div>
|
|
145
147
|
{#if !hideCloseButton}
|
|
@@ -190,6 +192,8 @@
|
|
|
190
192
|
role="dialog"
|
|
191
193
|
aria-modal="true"
|
|
192
194
|
aria-labelledby={title ? 'modal-title' : undefined}
|
|
195
|
+
aria-describedby={description ? 'modal-description' : undefined}
|
|
196
|
+
aria-label={!title ? 'Modal dialog' : undefined}
|
|
193
197
|
bind:this={modalElement}
|
|
194
198
|
>
|
|
195
199
|
<!-- Backdrop -->
|
|
@@ -12,10 +12,8 @@ import type { VariantSizes } from '../index.js';
|
|
|
12
12
|
* <Modal bind:open title="Edit profile">
|
|
13
13
|
* <ProfileForm />
|
|
14
14
|
* {#snippet footer()}
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
17
|
-
* <Button onclick={save}>Save</Button>
|
|
18
|
-
* </ModalFooter>
|
|
15
|
+
* <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
16
|
+
* <Button onclick={save}>Save</Button>
|
|
19
17
|
* {/snippet}
|
|
20
18
|
* </Modal>
|
|
21
19
|
* ```
|
|
@@ -65,8 +63,14 @@ export type ModalProps = {
|
|
|
65
63
|
footerClass?: ClassValue;
|
|
66
64
|
/** Body content. */
|
|
67
65
|
children?: Snippet;
|
|
68
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Footer content — typically action buttons. The footer wrapper renders
|
|
68
|
+
* with a tinted background, hairline separator, padding, and flex layout
|
|
69
|
+
* built-in, so the snippet can just contain `<Button>`s directly.
|
|
70
|
+
*/
|
|
69
71
|
footer?: Snippet;
|
|
72
|
+
/** How to align footer items horizontally. @default 'end' */
|
|
73
|
+
footerAlign?: 'start' | 'center' | 'end' | 'between';
|
|
70
74
|
/** Replace the default title bar entirely. */
|
|
71
75
|
header?: Snippet;
|
|
72
76
|
testId?: string;
|
package/dist/modal/modal.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
footerAlign: {
|
|
3
|
+
start: {
|
|
4
|
+
footer: string;
|
|
5
|
+
};
|
|
6
|
+
center: {
|
|
7
|
+
footer: string;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
footer: string;
|
|
11
|
+
};
|
|
12
|
+
between: {
|
|
13
|
+
footer: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
size: {
|
|
3
17
|
xs: {
|
|
4
18
|
container: string;
|
|
@@ -30,6 +44,20 @@ export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
|
30
44
|
description: string;
|
|
31
45
|
close: string;
|
|
32
46
|
}, undefined, {
|
|
47
|
+
footerAlign: {
|
|
48
|
+
start: {
|
|
49
|
+
footer: string;
|
|
50
|
+
};
|
|
51
|
+
center: {
|
|
52
|
+
footer: string;
|
|
53
|
+
};
|
|
54
|
+
end: {
|
|
55
|
+
footer: string;
|
|
56
|
+
};
|
|
57
|
+
between: {
|
|
58
|
+
footer: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
33
61
|
size: {
|
|
34
62
|
xs: {
|
|
35
63
|
container: string;
|
|
@@ -61,6 +89,20 @@ export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
|
61
89
|
description: string;
|
|
62
90
|
close: string;
|
|
63
91
|
}, import("tailwind-variants").TVReturnType<{
|
|
92
|
+
footerAlign: {
|
|
93
|
+
start: {
|
|
94
|
+
footer: string;
|
|
95
|
+
};
|
|
96
|
+
center: {
|
|
97
|
+
footer: string;
|
|
98
|
+
};
|
|
99
|
+
end: {
|
|
100
|
+
footer: string;
|
|
101
|
+
};
|
|
102
|
+
between: {
|
|
103
|
+
footer: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
64
106
|
size: {
|
|
65
107
|
xs: {
|
|
66
108
|
container: string;
|
package/dist/modal/modal.js
CHANGED
|
@@ -4,15 +4,24 @@ export const modal = tv({
|
|
|
4
4
|
slots: {
|
|
5
5
|
base: 'fixed inset-0 z-50 flex items-center justify-center p-2 sm:p-4',
|
|
6
6
|
backdrop: 'fixed inset-0 bg-black/50 backdrop-blur-sm',
|
|
7
|
-
container: 'relative w-full flex flex-col bg-white rounded-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
container: 'relative w-full flex flex-col bg-white rounded-lg shadow-2xl max-sm:max-w-none max-sm:max-h-[95vh]',
|
|
8
|
+
// Header: matches Card chrome — translucent bg-default-100/50 tint + hairline
|
|
9
|
+
// separator. Padding sits between Card md and lg: dialogs deserve a touch
|
|
10
|
+
// more presence than inline panels but stay compact relative to legacy modal.
|
|
11
|
+
header: 'px-5 py-3 bg-default-100/50 border-b border-default-200 flex items-center justify-between shrink-0',
|
|
12
|
+
body: 'flex-1 p-5 overflow-y-auto text-sm text-default-700',
|
|
13
|
+
footer: 'bg-default-100/50 border-t border-default-200 rounded-b-lg shrink-0 flex flex-wrap items-center gap-3 px-5 py-3',
|
|
14
|
+
title: 'text-sm font-semibold text-default-900 leading-tight',
|
|
15
|
+
description: 'text-xs text-default-500 font-normal',
|
|
16
|
+
close: 'p-1.5 -mr-1.5 ml-4 rounded-md text-default-400 cursor-pointer hover:text-default-600 hover:bg-default-200/70 transition-colors'
|
|
14
17
|
},
|
|
15
18
|
variants: {
|
|
19
|
+
footerAlign: {
|
|
20
|
+
start: { footer: 'justify-start' },
|
|
21
|
+
center: { footer: 'justify-center' },
|
|
22
|
+
end: { footer: 'justify-end' },
|
|
23
|
+
between: { footer: 'justify-between' }
|
|
24
|
+
},
|
|
16
25
|
size: {
|
|
17
26
|
[Size.XS]: {
|
|
18
27
|
container: 'max-w-xs max-h-[80vh]'
|
|
@@ -35,6 +44,7 @@ export const modal = tv({
|
|
|
35
44
|
}
|
|
36
45
|
},
|
|
37
46
|
defaultVariants: {
|
|
38
|
-
size: Size.MD
|
|
47
|
+
size: Size.MD,
|
|
48
|
+
footerAlign: 'end'
|
|
39
49
|
}
|
|
40
50
|
});
|
|
@@ -66,8 +66,20 @@
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// Refresh the query cache
|
|
69
|
+
// Refresh the query cache and re-fetch.
|
|
70
|
+
//
|
|
71
|
+
// SvelteKit `query()` results are cached per-arg-combo. After a mutating
|
|
72
|
+
// `command()`, the cache is stale — calling the query again returns the
|
|
73
|
+
// old data until `.refresh()` is invoked. We detect a query by the
|
|
74
|
+
// presence of a `.refresh` method and invalidate before re-fetching.
|
|
75
|
+
// Plain async-function adapters (no caching) skip this branch.
|
|
70
76
|
async function refreshUsersQuery() {
|
|
77
|
+
const fn = adapter.getUsers as typeof adapter.getUsers & {
|
|
78
|
+
refresh?: () => Promise<unknown>;
|
|
79
|
+
};
|
|
80
|
+
if (typeof fn.refresh === 'function') {
|
|
81
|
+
await fn.refresh();
|
|
82
|
+
}
|
|
71
83
|
await loadUsers();
|
|
72
84
|
}
|
|
73
85
|
|
|
@@ -290,6 +290,7 @@
|
|
|
290
290
|
{open}
|
|
291
291
|
onclose={handleClose}
|
|
292
292
|
title={getModalTitle()}
|
|
293
|
+
footerAlign="between"
|
|
293
294
|
contentClass="max-w-4xl"
|
|
294
295
|
class={cn(className)}
|
|
295
296
|
>
|
|
@@ -630,35 +631,33 @@
|
|
|
630
631
|
</div>
|
|
631
632
|
</form>
|
|
632
633
|
|
|
633
|
-
<!-- Form Actions -->
|
|
634
|
+
<!-- Form Actions: error on left, buttons on right via footerAlign="between" -->
|
|
634
635
|
{#snippet footer()}
|
|
635
|
-
|
|
636
|
-
{
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
<
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
</Button>
|
|
661
|
-
</div>
|
|
636
|
+
{#if formErrors.submit}
|
|
637
|
+
<p class="text-danger-500 text-sm">{formErrors.submit}</p>
|
|
638
|
+
{:else}
|
|
639
|
+
<div></div>
|
|
640
|
+
{/if}
|
|
641
|
+
<div class="flex gap-3">
|
|
642
|
+
<Button
|
|
643
|
+
variant="outline"
|
|
644
|
+
onclick={handleClose}
|
|
645
|
+
disabled={saving}
|
|
646
|
+
type="button"
|
|
647
|
+
data-testid="cancel-button"
|
|
648
|
+
>
|
|
649
|
+
Cancel
|
|
650
|
+
</Button>
|
|
651
|
+
<Button
|
|
652
|
+
type="button"
|
|
653
|
+
color="primary"
|
|
654
|
+
onclick={() => formElement?.requestSubmit()}
|
|
655
|
+
disabled={saving}
|
|
656
|
+
loading={saving}
|
|
657
|
+
data-testid="save-user-button"
|
|
658
|
+
>
|
|
659
|
+
{mode === 'create' ? 'Create User' : 'Save Changes'}
|
|
660
|
+
</Button>
|
|
662
661
|
</div>
|
|
663
662
|
{/snippet}
|
|
664
663
|
</Modal>
|
package/package.json
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { cn } from '../helper/cls.js';
|
|
3
|
-
import { buildTestId } from '../helper/testid.js';
|
|
4
|
-
import type { Snippet } from 'svelte';
|
|
5
|
-
import type { ClassValue } from 'tailwind-variants';
|
|
6
|
-
|
|
7
|
-
type Props = {
|
|
8
|
-
class?: ClassValue;
|
|
9
|
-
align?: 'start' | 'center' | 'end' | 'between';
|
|
10
|
-
children: Snippet;
|
|
11
|
-
testId?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let { class: className = '', align = 'end', children, testId }: Props = $props();
|
|
15
|
-
|
|
16
|
-
const alignClass = $derived(
|
|
17
|
-
{
|
|
18
|
-
start: 'justify-start',
|
|
19
|
-
center: 'justify-center',
|
|
20
|
-
end: 'justify-end',
|
|
21
|
-
between: 'justify-between'
|
|
22
|
-
}[align]
|
|
23
|
-
);
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<div
|
|
27
|
-
class={cn(
|
|
28
|
-
'border-default-200 flex flex-wrap items-center gap-3 border-t px-6 py-4',
|
|
29
|
-
alignClass,
|
|
30
|
-
className
|
|
31
|
-
)}
|
|
32
|
-
data-testid={buildTestId('modal-footer', undefined, testId)}
|
|
33
|
-
>
|
|
34
|
-
{@render children()}
|
|
35
|
-
</div>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { ClassValue } from 'tailwind-variants';
|
|
3
|
-
type Props = {
|
|
4
|
-
class?: ClassValue;
|
|
5
|
-
align?: 'start' | 'center' | 'end' | 'between';
|
|
6
|
-
children: Snippet;
|
|
7
|
-
testId?: string;
|
|
8
|
-
};
|
|
9
|
-
declare const ModalFooter: import("svelte").Component<Props, {}, "">;
|
|
10
|
-
type ModalFooter = ReturnType<typeof ModalFooter>;
|
|
11
|
-
export default ModalFooter;
|