@ainsleydev/sveltekit-helper 0.4.1 → 0.5.1

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,217 @@
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
+ showClose?: boolean;
10
+ children?: Snippet;
11
+ class?: string;
12
+ onClose?: () => void;
13
+ transition?: TransitionFn;
14
+ transitionParams?: Record<string, unknown>;
15
+ };
16
+ </script>
17
+
18
+ <script lang="ts">
19
+ import { X } from '@lucide/svelte';
20
+ import { fade } from 'svelte/transition';
21
+
22
+ let {
23
+ title = '',
24
+ isOpen = $bindable(false),
25
+ showClose = true,
26
+ children,
27
+ class: className = '',
28
+ onClose,
29
+ transition: transitionFn = fade,
30
+ transitionParams = { duration: 100 }
31
+ }: ModalProps = $props();
32
+
33
+ let modalContent = $state<HTMLDivElement>();
34
+
35
+ const handleBackdropClick = (event: MouseEvent) => {
36
+ if (modalContent && !modalContent.contains(event.target as Node)) {
37
+ onClose?.();
38
+ }
39
+ };
40
+
41
+ $effect(() => {
42
+ if (!isOpen) return;
43
+
44
+ const handleKeydown = (e: KeyboardEvent) => {
45
+ if (e.key === 'Escape') {
46
+ e.preventDefault();
47
+ onClose?.();
48
+ }
49
+ };
50
+
51
+ document.addEventListener('keydown', handleKeydown);
52
+ return () => document.removeEventListener('keydown', handleKeydown);
53
+ });
54
+ </script>
55
+
56
+ <!--
57
+ @component
58
+
59
+ Modal dialog component with backdrop click and Escape key close behaviour.
60
+ Uses the native `<dialog>` element for accessibility.
61
+
62
+ @example Basic usage with title
63
+ ```svelte
64
+ <Modal bind:isOpen title="Confirm action" onClose={() => (isOpen = false)}>
65
+ <p>Are you sure you want to proceed?</p>
66
+ </Modal>
67
+ ```
68
+
69
+ @example Without title
70
+ ```svelte
71
+ <Modal bind:isOpen onClose={() => (isOpen = false)}>
72
+ <form>...</form>
73
+ </Modal>
74
+ ```
75
+
76
+ @example Slide in from the left
77
+ ```svelte
78
+ <script>
79
+ import { fly } from 'svelte/transition';
80
+ </script>
81
+
82
+ <Modal
83
+ bind:isOpen
84
+ title="Slide modal"
85
+ onClose={() => (isOpen = false)}
86
+ transition={fly}
87
+ transitionParams={{ x: -300, duration: 200 }}
88
+ />
89
+ ```
90
+
91
+ @example Scale up from centre
92
+ ```svelte
93
+ <script>
94
+ import { scale } from 'svelte/transition';
95
+ </script>
96
+
97
+ <Modal
98
+ bind:isOpen
99
+ onClose={() => (isOpen = false)}
100
+ transition={scale}
101
+ transitionParams={{ start: 0.9, duration: 150 }}
102
+ >
103
+ <p>Scaled content</p>
104
+ </Modal>
105
+ ```
106
+
107
+ CSS Custom Properties:
108
+ - `--modal-overlay-bg`: Backdrop colour (default: rgba(0, 0, 0, 0.6))
109
+ - `--modal-padding-top`: Top offset from viewport (default: var(--header-height))
110
+ - `--modal-content-max-width`: Max width of the content panel (default: 600px)
111
+ - `--modal-content-bg`: Content background (default: var(--token-surface-default))
112
+ - `--modal-content-border`: Content border (default: 1px solid var(--token-border-grey))
113
+ - `--modal-content-border-radius`: Content border radius (default: 12px)
114
+ - `--modal-content-padding`: Content padding (default: 1.5rem / 2rem on tablet)
115
+ - `--modal-header-border`: Header bottom border (default: 1px solid var(--token-border-grey))
116
+ - `--modal-close-colour`: Close icon colour (default: var(--token-icon-grey))
117
+
118
+ Props:
119
+ - `title` (optional): Modal heading text.
120
+ - `isOpen` (bindable): Controls modal visibility.
121
+ - `showClose` (default: true): Whether to render the close button.
122
+ - `children`: Slot content (Snippet).
123
+ - `class`: Custom CSS class.
124
+ - `onClose`: Callback when the modal should close.
125
+ - `transition`: Custom transition function (default: fade).
126
+ - `transitionParams`: Transition parameters.
127
+ -->
128
+ {#if isOpen}
129
+ <dialog
130
+ open
131
+ class="modal {className}"
132
+ aria-modal="true"
133
+ aria-label={title || undefined}
134
+ onclick={handleBackdropClick}
135
+ transition:transitionFn={transitionParams}
136
+ >
137
+ <div class="modal__content" bind:this={modalContent}>
138
+ {#if title || showClose}
139
+ <header class="modal__header">
140
+ {#if title}
141
+ <h4 class="modal__title">{title}</h4>
142
+ {/if}
143
+ {#if showClose}
144
+ <button
145
+ class="modal__close"
146
+ onclick={() => onClose?.()}
147
+ aria-label={title ? `Close ${title}` : 'Close modal'}
148
+ >
149
+ <X color="var(--modal-close-colour, var(--token-icon-grey))" />
150
+ </button>
151
+ {/if}
152
+ </header>
153
+ {/if}
154
+ <div class="modal__body">
155
+ {#if children}
156
+ {@render children()}
157
+ {/if}
158
+ </div>
159
+ </div>
160
+ </dialog>
161
+ {/if}
162
+
163
+ <style>.modal {
164
+ position: fixed;
165
+ display: flex;
166
+ align-items: flex-start;
167
+ justify-content: center;
168
+ top: 0;
169
+ left: 0;
170
+ padding: var(--modal-padding-top, var(--header-height)) 0 0;
171
+ height: 100vh;
172
+ width: 100vw;
173
+ max-width: none;
174
+ max-height: none;
175
+ background-color: var(--modal-overlay-bg, rgba(0, 0, 0, 0.6));
176
+ z-index: 9999999;
177
+ outline: none;
178
+ border: none;
179
+ }
180
+ .modal__header {
181
+ display: flex;
182
+ justify-content: space-between;
183
+ align-items: flex-start;
184
+ width: 100%;
185
+ border-bottom: var(--modal-header-border, 1px solid var(--token-border-grey));
186
+ margin-bottom: 1rem;
187
+ padding-bottom: 1rem;
188
+ }
189
+ .modal__title {
190
+ margin: 0;
191
+ }
192
+ .modal__close {
193
+ cursor: pointer;
194
+ background: none;
195
+ border: none;
196
+ padding: 0;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ margin-left: auto;
201
+ }
202
+ .modal__content {
203
+ position: relative;
204
+ display: flex;
205
+ flex-direction: column;
206
+ align-items: flex-start;
207
+ width: min(100% - 1.6rem, var(--modal-content-max-width, 600px));
208
+ background: var(--modal-content-bg, var(--token-surface-default));
209
+ padding: var(--modal-content-padding, 1.5rem);
210
+ border: var(--modal-content-border, 1px solid var(--token-border-grey));
211
+ border-radius: var(--modal-content-border-radius, 12px);
212
+ }
213
+ @media screen and (min-width: 768px) {
214
+ .modal__content {
215
+ padding: var(--modal-content-padding, 2rem);
216
+ }
217
+ }</style>
@@ -0,0 +1,86 @@
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
+ showClose?: boolean;
7
+ children?: Snippet;
8
+ class?: string;
9
+ onClose?: () => void;
10
+ transition?: TransitionFn;
11
+ transitionParams?: Record<string, unknown>;
12
+ };
13
+ /**
14
+ * Modal dialog component with backdrop click and Escape key close behaviour.
15
+ * Uses the native `<dialog>` element for accessibility.
16
+ *
17
+ * @example Basic usage with title
18
+ * ```svelte
19
+ * <Modal bind:isOpen title="Confirm action" onClose={() => (isOpen = false)}>
20
+ * <p>Are you sure you want to proceed?</p>
21
+ * </Modal>
22
+ * ```
23
+ *
24
+ * @example Without title
25
+ * ```svelte
26
+ * <Modal bind:isOpen onClose={() => (isOpen = false)}>
27
+ * <form>...</form>
28
+ * </Modal>
29
+ * ```
30
+ *
31
+ * @example Slide in from the left
32
+ * ```svelte
33
+ * <script>
34
+ * import { fly } from 'svelte/transition';
35
+ * </script>
36
+ *
37
+ * <Modal
38
+ * bind:isOpen
39
+ * title="Slide modal"
40
+ * onClose={() => (isOpen = false)}
41
+ * transition={fly}
42
+ * transitionParams={{ x: -300, duration: 200 }}
43
+ * />
44
+ * ```
45
+ *
46
+ * @example Scale up from centre
47
+ * ```svelte
48
+ * <script>
49
+ * import { scale } from 'svelte/transition';
50
+ * </script>
51
+ *
52
+ * <Modal
53
+ * bind:isOpen
54
+ * onClose={() => (isOpen = false)}
55
+ * transition={scale}
56
+ * transitionParams={{ start: 0.9, duration: 150 }}
57
+ * >
58
+ * <p>Scaled content</p>
59
+ * </Modal>
60
+ * ```
61
+ *
62
+ * CSS Custom Properties:
63
+ * - `--modal-overlay-bg`: Backdrop colour (default: rgba(0, 0, 0, 0.6))
64
+ * - `--modal-padding-top`: Top offset from viewport (default: var(--header-height))
65
+ * - `--modal-content-max-width`: Max width of the content panel (default: 600px)
66
+ * - `--modal-content-bg`: Content background (default: var(--token-surface-default))
67
+ * - `--modal-content-border`: Content border (default: 1px solid var(--token-border-grey))
68
+ * - `--modal-content-border-radius`: Content border radius (default: 12px)
69
+ * - `--modal-content-padding`: Content padding (default: 1.5rem / 2rem on tablet)
70
+ * - `--modal-header-border`: Header bottom border (default: 1px solid var(--token-border-grey))
71
+ * - `--modal-close-colour`: Close icon colour (default: var(--token-icon-grey))
72
+ *
73
+ * Props:
74
+ * - `title` (optional): Modal heading text.
75
+ * - `isOpen` (bindable): Controls modal visibility.
76
+ * - `showClose` (default: true): Whether to render the close button.
77
+ * - `children`: Slot content (Snippet).
78
+ * - `class`: Custom CSS class.
79
+ * - `onClose`: Callback when the modal should close.
80
+ * - `transition`: Custom transition function (default: fade).
81
+ * - `transitionParams`: Transition parameters.
82
+ */
83
+ declare const Modal: import("svelte").Component<ModalProps, {}, "isOpen">;
84
+ type Modal = ReturnType<typeof Modal>;
85
+ export default Modal;
86
+ //# 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,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,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;AA2EF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;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"}
@@ -1,3 +1,4 @@
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainsleydev/sveltekit-helper",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "SvelteKit utilities, components and helpers for ainsley.dev builds",
5
5
  "license": "MIT",
6
6
  "type": "module",