@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"}
@@ -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.0",
4
4
  "description": "SvelteKit utilities, components and helpers for ainsley.dev builds",
5
5
  "license": "MIT",
6
6
  "type": "module",