@ainsleydev/sveltekit-helper 0.4.1 → 0.5.0
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.
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet, TransitionConfig } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export type TransitionFn = (node: Element, params: Record<string, unknown>) => TransitionConfig;
|
|
5
|
+
|
|
6
|
+
export type ModalProps = {
|
|
7
|
+
title?: string;
|
|
8
|
+
isOpen?: boolean;
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
class?: string;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
transition?: TransitionFn;
|
|
13
|
+
transitionParams?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import { X } from '@lucide/svelte';
|
|
19
|
+
import { fade } from 'svelte/transition';
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
title = '',
|
|
23
|
+
isOpen = $bindable(false),
|
|
24
|
+
children,
|
|
25
|
+
class: className = '',
|
|
26
|
+
onClose,
|
|
27
|
+
transition: transitionFn = fade,
|
|
28
|
+
transitionParams = { duration: 100 }
|
|
29
|
+
}: ModalProps = $props();
|
|
30
|
+
|
|
31
|
+
let modalContent = $state<HTMLDivElement>();
|
|
32
|
+
|
|
33
|
+
const handleBackdropClick = (event: MouseEvent) => {
|
|
34
|
+
if (modalContent && !modalContent.contains(event.target as Node)) {
|
|
35
|
+
onClose?.();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (!isOpen) return;
|
|
41
|
+
|
|
42
|
+
const handleKeydown = (e: KeyboardEvent) => {
|
|
43
|
+
if (e.key === 'Escape') {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
onClose?.();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
document.addEventListener('keydown', handleKeydown);
|
|
50
|
+
return () => document.removeEventListener('keydown', handleKeydown);
|
|
51
|
+
});
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<!--
|
|
55
|
+
@component
|
|
56
|
+
|
|
57
|
+
Modal dialog component with backdrop click and Escape key close behaviour.
|
|
58
|
+
Uses the native `<dialog>` element for accessibility.
|
|
59
|
+
|
|
60
|
+
@example Basic usage with title
|
|
61
|
+
```svelte
|
|
62
|
+
<Modal bind:isOpen title="Confirm action" onClose={() => (isOpen = false)}>
|
|
63
|
+
<p>Are you sure you want to proceed?</p>
|
|
64
|
+
</Modal>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
@example Without title
|
|
68
|
+
```svelte
|
|
69
|
+
<Modal bind:isOpen onClose={() => (isOpen = false)}>
|
|
70
|
+
<form>...</form>
|
|
71
|
+
</Modal>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
@example Slide in from the left
|
|
75
|
+
```svelte
|
|
76
|
+
<script>
|
|
77
|
+
import { fly } from 'svelte/transition';
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<Modal
|
|
81
|
+
bind:isOpen
|
|
82
|
+
title="Slide modal"
|
|
83
|
+
onClose={() => (isOpen = false)}
|
|
84
|
+
transition={fly}
|
|
85
|
+
transitionParams={{ x: -300, duration: 200 }}
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
@example Scale up from centre
|
|
90
|
+
```svelte
|
|
91
|
+
<script>
|
|
92
|
+
import { scale } from 'svelte/transition';
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<Modal
|
|
96
|
+
bind:isOpen
|
|
97
|
+
onClose={() => (isOpen = false)}
|
|
98
|
+
transition={scale}
|
|
99
|
+
transitionParams={{ start: 0.9, duration: 150 }}
|
|
100
|
+
>
|
|
101
|
+
<p>Scaled content</p>
|
|
102
|
+
</Modal>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
CSS Custom Properties:
|
|
106
|
+
- `--modal-overlay-bg`: Backdrop colour (default: rgba(0, 0, 0, 0.6))
|
|
107
|
+
- `--modal-padding-top`: Top offset from viewport (default: var(--header-height))
|
|
108
|
+
- `--modal-content-max-width`: Max width of the content panel (default: 600px)
|
|
109
|
+
- `--modal-content-bg`: Content background (default: var(--token-surface-default))
|
|
110
|
+
- `--modal-content-border`: Content border (default: 1px solid var(--token-border-grey))
|
|
111
|
+
- `--modal-content-border-radius`: Content border radius (default: 12px)
|
|
112
|
+
- `--modal-content-padding`: Content padding (default: 1.5rem / 2rem on tablet)
|
|
113
|
+
- `--modal-header-border`: Header bottom border (default: 1px solid var(--token-border-grey))
|
|
114
|
+
- `--modal-close-colour`: Close icon colour (default: var(--token-icon-grey))
|
|
115
|
+
-->
|
|
116
|
+
{#if isOpen}
|
|
117
|
+
<dialog
|
|
118
|
+
open
|
|
119
|
+
class="modal {className}"
|
|
120
|
+
aria-modal="true"
|
|
121
|
+
aria-label={title || undefined}
|
|
122
|
+
onclick={handleBackdropClick}
|
|
123
|
+
transition:transitionFn={transitionParams}
|
|
124
|
+
>
|
|
125
|
+
<div class="modal__content" bind:this={modalContent}>
|
|
126
|
+
{#if title}
|
|
127
|
+
<header class="modal__header">
|
|
128
|
+
<h4 class="modal__title">{title}</h4>
|
|
129
|
+
<button
|
|
130
|
+
class="modal__close"
|
|
131
|
+
onclick={() => onClose?.()}
|
|
132
|
+
aria-label={title ? `Close ${title}` : 'Close modal'}
|
|
133
|
+
>
|
|
134
|
+
<X color="var(--modal-close-colour, var(--token-icon-grey))" />
|
|
135
|
+
</button>
|
|
136
|
+
</header>
|
|
137
|
+
{/if}
|
|
138
|
+
<div class="modal__body">
|
|
139
|
+
{#if children}
|
|
140
|
+
{@render children()}
|
|
141
|
+
{/if}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</dialog>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
<style>.modal {
|
|
148
|
+
position: fixed;
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: flex-start;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
top: 0;
|
|
153
|
+
left: 0;
|
|
154
|
+
padding: var(--modal-padding-top, var(--header-height)) 0 0;
|
|
155
|
+
height: 100vh;
|
|
156
|
+
width: 100vw;
|
|
157
|
+
max-width: none;
|
|
158
|
+
max-height: none;
|
|
159
|
+
background-color: var(--modal-overlay-bg, rgba(0, 0, 0, 0.6));
|
|
160
|
+
z-index: 9999999;
|
|
161
|
+
outline: none;
|
|
162
|
+
border: none;
|
|
163
|
+
}
|
|
164
|
+
.modal__header {
|
|
165
|
+
display: flex;
|
|
166
|
+
justify-content: space-between;
|
|
167
|
+
align-items: flex-start;
|
|
168
|
+
width: 100%;
|
|
169
|
+
border-bottom: var(--modal-header-border, 1px solid var(--token-border-grey));
|
|
170
|
+
margin-bottom: 1rem;
|
|
171
|
+
padding-bottom: 1rem;
|
|
172
|
+
}
|
|
173
|
+
.modal__title {
|
|
174
|
+
margin: 0;
|
|
175
|
+
}
|
|
176
|
+
.modal__close {
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
background: none;
|
|
179
|
+
border: none;
|
|
180
|
+
padding: 0;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
}
|
|
185
|
+
.modal__content {
|
|
186
|
+
position: relative;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
align-items: flex-start;
|
|
190
|
+
width: min(100% - 1.6rem, var(--modal-content-max-width, 600px));
|
|
191
|
+
background: var(--modal-content-bg, var(--token-surface-default));
|
|
192
|
+
padding: var(--modal-content-padding, 1.5rem);
|
|
193
|
+
border: var(--modal-content-border, 1px solid var(--token-border-grey));
|
|
194
|
+
border-radius: var(--modal-content-border-radius, 12px);
|
|
195
|
+
}
|
|
196
|
+
@media screen and (min-width: 768px) {
|
|
197
|
+
.modal__content {
|
|
198
|
+
padding: var(--modal-content-padding, 2rem);
|
|
199
|
+
}
|
|
200
|
+
}</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Snippet, TransitionConfig } from 'svelte';
|
|
2
|
+
export type TransitionFn = (node: Element, params: Record<string, unknown>) => TransitionConfig;
|
|
3
|
+
export type ModalProps = {
|
|
4
|
+
title?: string;
|
|
5
|
+
isOpen?: boolean;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
class?: string;
|
|
8
|
+
onClose?: () => void;
|
|
9
|
+
transition?: TransitionFn;
|
|
10
|
+
transitionParams?: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Modal dialog component with backdrop click and Escape key close behaviour.
|
|
14
|
+
* Uses the native `<dialog>` element for accessibility.
|
|
15
|
+
*
|
|
16
|
+
* @example Basic usage with title
|
|
17
|
+
* ```svelte
|
|
18
|
+
* <Modal bind:isOpen title="Confirm action" onClose={() => (isOpen = false)}>
|
|
19
|
+
* <p>Are you sure you want to proceed?</p>
|
|
20
|
+
* </Modal>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Without title
|
|
24
|
+
* ```svelte
|
|
25
|
+
* <Modal bind:isOpen onClose={() => (isOpen = false)}>
|
|
26
|
+
* <form>...</form>
|
|
27
|
+
* </Modal>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Slide in from the left
|
|
31
|
+
* ```svelte
|
|
32
|
+
* <script>
|
|
33
|
+
* import { fly } from 'svelte/transition';
|
|
34
|
+
* </script>
|
|
35
|
+
*
|
|
36
|
+
* <Modal
|
|
37
|
+
* bind:isOpen
|
|
38
|
+
* title="Slide modal"
|
|
39
|
+
* onClose={() => (isOpen = false)}
|
|
40
|
+
* transition={fly}
|
|
41
|
+
* transitionParams={{ x: -300, duration: 200 }}
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Scale up from centre
|
|
46
|
+
* ```svelte
|
|
47
|
+
* <script>
|
|
48
|
+
* import { scale } from 'svelte/transition';
|
|
49
|
+
* </script>
|
|
50
|
+
*
|
|
51
|
+
* <Modal
|
|
52
|
+
* bind:isOpen
|
|
53
|
+
* onClose={() => (isOpen = false)}
|
|
54
|
+
* transition={scale}
|
|
55
|
+
* transitionParams={{ start: 0.9, duration: 150 }}
|
|
56
|
+
* >
|
|
57
|
+
* <p>Scaled content</p>
|
|
58
|
+
* </Modal>
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* CSS Custom Properties:
|
|
62
|
+
* - `--modal-overlay-bg`: Backdrop colour (default: rgba(0, 0, 0, 0.6))
|
|
63
|
+
* - `--modal-padding-top`: Top offset from viewport (default: var(--header-height))
|
|
64
|
+
* - `--modal-content-max-width`: Max width of the content panel (default: 600px)
|
|
65
|
+
* - `--modal-content-bg`: Content background (default: var(--token-surface-default))
|
|
66
|
+
* - `--modal-content-border`: Content border (default: 1px solid var(--token-border-grey))
|
|
67
|
+
* - `--modal-content-border-radius`: Content border radius (default: 12px)
|
|
68
|
+
* - `--modal-content-padding`: Content padding (default: 1.5rem / 2rem on tablet)
|
|
69
|
+
* - `--modal-header-border`: Header bottom border (default: 1px solid var(--token-border-grey))
|
|
70
|
+
* - `--modal-close-colour`: Close icon colour (default: var(--token-icon-grey))
|
|
71
|
+
*/
|
|
72
|
+
declare const Modal: import("svelte").Component<ModalProps, {}, "isOpen">;
|
|
73
|
+
type Modal = ReturnType<typeof Modal>;
|
|
74
|
+
export default Modal;
|
|
75
|
+
//# sourceMappingURL=Modal.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Modal.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,gBAAgB,CAAC;AAEhG,MAAM,MAAM,UAAU,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC;AAsEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,QAAA,MAAM,KAAK,sDAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
1
2
|
export { default as Sidebar } from './Sidebar.svelte';
|
|
2
3
|
export { default as Hamburger } from './Hamburger.svelte';
|
|
3
4
|
export { Alert, Notice } from './notifications';
|
|
5
|
+
export type { ModalProps, TransitionFn } from './Modal.svelte';
|
|
4
6
|
export type { AlertProps, AlertType, NoticeProps, NoticeType } from './notifications';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/components/index.js
CHANGED