@makolabs/ripple 3.0.9 → 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.
@@ -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 title}
207
+ {#if header}
200
208
  <div class={headerClasses}>
201
- <h3
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 header}
211
+ {:else if title || description}
210
212
  <div class={headerClasses}>
211
- {@render header()}
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
- * <div class="flex justify-end gap-2 p-4">
16
- * <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
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
- /** Footer content (usually action buttons). */
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
  };
@@ -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>>;
@@ -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
- header: 'flex items-center justify-between px-4 py-3 border-b border-default-200',
10
- body: 'flex-1 overflow-y-auto p-4',
11
- footer: 'flex justify-end border-t border-default-200 p-4',
12
- title: 'text-default-900 leading-6 text-base font-semibold',
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';
@@ -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
- <ModalFooter align="end">
57
- <Button variant="outline" onclick={handleCancel} disabled={busy}>
58
- {cancelLabel}
59
- </Button>
60
- <Button color={confirmColor} onclick={handleConfirm} loading={busy}>
61
- {confirmLabel}
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>
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
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
- <h2
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
- </h2>
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
- * <ModalFooter align="end">
16
- * <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
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
- /** Footer content — usually `<ModalFooter>` with action buttons. */
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;
@@ -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;
@@ -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-xl shadow-2xl max-sm:max-w-none max-sm:max-h-[95vh]',
8
- header: 'px-4 py-3 sm:px-6 sm:py-4 border-b border-default-200 flex items-center justify-between shrink-0',
9
- body: 'flex-1 px-4 py-3 sm:px-6 sm:py-4 overflow-y-auto',
10
- footer: 'bg-default-50/80 rounded-b-xl shrink-0',
11
- title: 'text-lg font-semibold text-default-900',
12
- description: 'text-sm text-default-600 mt-1',
13
- close: 'p-2 -mr-2 ml-4 rounded-lg text-default-400 cursor-pointer hover:text-default-600 hover:bg-default-100 transition-colors'
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 to get fresh data
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
- <div class="flex items-center justify-between gap-4">
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>
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "3.0.9",
3
+ "version": "3.0.10",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -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;