@abhivarde/svelte-drawer 0.0.20 → 0.0.22

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/README.md CHANGED
@@ -231,6 +231,135 @@ Snap points allow the drawer to rest at predefined heights, creating an iOS-like
231
231
  - Use `bind:activeSnapPoint` to programmatically control the current position
232
232
  - Use `onSnapPointChange` callback to react to snap changes
233
233
 
234
+ ### Portal Support
235
+
236
+ Render the drawer in a portal to avoid z-index conflicts in complex layouts.
237
+ ```svelte
238
+ <script>
239
+ import { Drawer, DrawerOverlay, DrawerContent, DrawerHandle } from '@abhivarde/svelte-drawer';
240
+
241
+ let open = $state(false);
242
+ </script>
243
+
244
+ <!-- Enable portal (renders at end of body) -->
245
+ <Drawer bind:open portal={true}>
246
+ <DrawerOverlay class="fixed inset-0 bg-black/40" />
247
+ <DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg p-4">
248
+ <DrawerHandle class="mb-8" />
249
+ <h2>Portal Drawer</h2>
250
+ <p>This drawer is rendered in a portal, preventing z-index issues.</p>
251
+ </DrawerContent>
252
+ </Drawer>
253
+
254
+ <!-- Custom portal container -->
255
+ <Drawer bind:open portal={true} portalContainer="#custom-portal">
256
+ <DrawerOverlay />
257
+ <DrawerContent>
258
+ <h2>Custom Portal</h2>
259
+ </DrawerContent>
260
+ </Drawer>
261
+
262
+ <div id="custom-portal"></div>
263
+ ```
264
+
265
+ **When to use portals:**
266
+ - Complex layouts with nested z-index contexts
267
+ - Third-party component libraries with fixed positioning
268
+ - Modals inside scrollable containers
269
+ - Preventing overflow: hidden conflicts
270
+
271
+ ### Header & Footer Components
272
+
273
+ Optional pre-styled header and footer components for quick setup.
274
+
275
+ #### DrawerHeader
276
+ ```svelte
277
+ <script>
278
+ import { Drawer, DrawerOverlay, DrawerContent, DrawerHeader, DrawerHandle } from '@abhivarde/svelte-drawer';
279
+
280
+ let open = $state(false);
281
+ </script>
282
+
283
+ <!-- With title and description -->
284
+ <Drawer bind:open>
285
+ <DrawerOverlay class="fixed inset-0 bg-black/40" />
286
+ <DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg">
287
+ <DrawerHeader
288
+ title="Drawer Title"
289
+ description="Optional description text"
290
+ showCloseButton={true}
291
+ />
292
+ <div class="p-4">
293
+ <p>Drawer content here</p>
294
+ </div>
295
+ </DrawerContent>
296
+ </Drawer>
297
+
298
+ <!-- Custom header content -->
299
+ <Drawer bind:open>
300
+ <DrawerOverlay />
301
+ <DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg">
302
+ <DrawerHeader>
303
+ <div class="flex items-center gap-3">
304
+ <img src="/icon.png" alt="Icon" class="w-8 h-8" />
305
+ <div>
306
+ <h2 class="font-semibold">Custom Header</h2>
307
+ <p class="text-sm text-gray-600">Your custom content</p>
308
+ </div>
309
+ </div>
310
+ </DrawerHeader>
311
+ <div class="p-4">
312
+ <p>Drawer content</p>
313
+ </div>
314
+ </DrawerContent>
315
+ </Drawer>
316
+ ```
317
+
318
+ #### DrawerFooter
319
+ ```svelte
320
+ <script>
321
+ import { Drawer, DrawerOverlay, DrawerContent, DrawerFooter } from '@abhivarde/svelte-drawer';
322
+
323
+ let open = $state(false);
324
+ </script>
325
+
326
+ <!-- Simple footer with custom content -->
327
+ <Drawer bind:open>
328
+ <DrawerOverlay class="fixed inset-0 bg-black/40" />
329
+ <DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg flex flex-col">
330
+ <div class="p-4 flex-1">
331
+ <h2>Drawer Content</h2>
332
+ </div>
333
+ <DrawerFooter>
334
+ <button onclick={() => open = false} class="px-4 py-2 bg-gray-200 rounded">
335
+ Cancel
336
+ </button>
337
+ <button class="px-4 py-2 bg-black text-white rounded">
338
+ Confirm
339
+ </button>
340
+ </DrawerFooter>
341
+ </DrawerContent>
342
+ </Drawer>
343
+
344
+ <!-- Custom footer with links -->
345
+ <Drawer bind:open>
346
+ <DrawerOverlay />
347
+ <DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg flex flex-col">
348
+ <div class="p-4 flex-1">
349
+ <h2>Content</h2>
350
+ </div>
351
+ <DrawerFooter class="bg-gray-50">
352
+ <div class="flex justify-between w-full">
353
+ <a href="/privacy" class="text-sm text-gray-600 hover:text-gray-900">Privacy</a>
354
+ <a href="/terms" class="text-sm text-gray-600 hover:text-gray-900">Terms</a>
355
+ </div>
356
+ </DrawerFooter>
357
+ </DrawerContent>
358
+ </Drawer>
359
+ ```
360
+
361
+ **Note:** These components are **optional**. You can still build custom headers and footers using plain HTML/Svelte markup without importing these components.
362
+
234
363
  ## Variants
235
364
 
236
365
  Available variants for `DrawerVariants` component:
@@ -259,9 +388,11 @@ Main wrapper component that manages drawer state and animations.
259
388
  - `onOpenChange` (function, optional) - Callback when open state changes
260
389
  - `direction` ('bottom' | 'top' | 'left' | 'right', default: 'bottom') - Direction from which drawer slides
261
390
  - `closeOnEscape` (boolean, optional, default: true) - Whether Escape key closes the drawer
262
- - `snapPoints` (number[], optional) - Array of snap positions between 0-1, where 1 is fully open (e.g., `[0.25, 0.5, 0.9]`)
391
+ - `snapPoints` (number[], optional) - Array of snap positions between 0-1
263
392
  - `activeSnapPoint` (number, bindable, optional) - Current active snap point value
264
- - `onSnapPointChange` (function, optional) - Callback fired when the drawer snaps to a different point
393
+ - `onSnapPointChange` (function, optional) - Callback fired when snap changes
394
+ - `portal` (boolean, optional, default: false) - Render drawer in a portal
395
+ - `portalContainer` (HTMLElement | string, optional) - Custom portal container element or selector
265
396
 
266
397
  ### DrawerOverlay
267
398
 
@@ -318,6 +449,38 @@ Pre-styled drawer content with built-in variants.
318
449
  - `class` (string, optional) - Additional CSS classes for styling
319
450
  - `trapFocus` (boolean, optional, default: true) - Whether to trap focus inside drawer
320
451
 
452
+ ### DrawerHeader
453
+
454
+ Optional pre-styled header component.
455
+
456
+ **Props:**
457
+
458
+ - `title` (string, optional) - Header title text
459
+ - `description` (string, optional) - Description text below title
460
+ - `showCloseButton` (boolean, optional, default: true) - Show close button
461
+ - `onClose` (function, optional) - Custom close handler
462
+ - `class` (string, optional) - CSS classes for styling
463
+
464
+ **Features:**
465
+
466
+ - Automatic close button with drawer context
467
+ - Customizable via children render
468
+ - Border and padding included
469
+
470
+ ### DrawerFooter
471
+
472
+ Optional pre-styled footer component.
473
+
474
+ **Props:**
475
+
476
+ - `class` (string, optional) - CSS classes for styling
477
+
478
+ **Features:**
479
+
480
+ - Automatic border and spacing
481
+ - Flexible content via children
482
+ - Works with mt-auto for bottom positioning
483
+
321
484
  ## Demo
322
485
 
323
486
  Visit [drawer.abhivarde.in](https://drawer.abhivarde.in) to see live examples.
@@ -2,6 +2,7 @@
2
2
  import { Tween } from "svelte/motion";
3
3
  import { expoOut } from "svelte/easing";
4
4
  import { setContext, onMount } from "svelte";
5
+ import DrawerPortal from "./DrawerPortal.svelte";
5
6
 
6
7
  let {
7
8
  open = $bindable(false),
@@ -11,6 +12,8 @@
11
12
  snapPoints = undefined,
12
13
  activeSnapPoint = $bindable(undefined),
13
14
  onSnapPointChange = undefined,
15
+ portal = false,
16
+ portalContainer = undefined,
14
17
  children,
15
18
  } = $props();
16
19
 
@@ -143,4 +146,10 @@
143
146
  });
144
147
  </script>
145
148
 
146
- {@render children()}
149
+ {#if portal}
150
+ <DrawerPortal container={portalContainer}>
151
+ {@render children()}
152
+ </DrawerPortal>
153
+ {:else}
154
+ {@render children()}
155
+ {/if}
@@ -6,6 +6,8 @@ declare const Drawer: import("svelte").Component<{
6
6
  snapPoints?: any;
7
7
  activeSnapPoint?: any;
8
8
  onSnapPointChange?: any;
9
+ portal?: boolean;
10
+ portalContainer?: any;
9
11
  children: any;
10
12
  }, {}, "open" | "activeSnapPoint">;
11
13
  type Drawer = ReturnType<typeof Drawer>;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ let {
3
+ class: className = "",
4
+ children,
5
+ actions,
6
+ ...restProps
7
+ }: {
8
+ class?: string;
9
+ children?: any;
10
+ actions?: any;
11
+ [key: string]: any;
12
+ } = $props();
13
+ </script>
14
+
15
+ <div class="p-4 border-t border-gray-200 mt-auto {className}" {...restProps}>
16
+ {#if children}
17
+ {@render children()}
18
+ {:else if actions}
19
+ <div class="flex gap-3 justify-end">
20
+ {@render actions()}
21
+ </div>
22
+ {/if}
23
+ </div>
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ class?: string;
3
+ children?: any;
4
+ actions?: any;
5
+ [key: string]: any;
6
+ };
7
+ declare const DrawerFooter: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type DrawerFooter = ReturnType<typeof DrawerFooter>;
9
+ export default DrawerFooter;
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ let {
3
+ title,
4
+ description,
5
+ showCloseButton = true,
6
+ onClose,
7
+ class: className = "",
8
+ children,
9
+ ...restProps
10
+ }: {
11
+ title?: string;
12
+ description?: string;
13
+ showCloseButton?: boolean;
14
+ onClose?: () => void;
15
+ class?: string;
16
+ children?: any;
17
+ [key: string]: any;
18
+ } = $props();
19
+
20
+ import { getContext } from "svelte";
21
+
22
+ const drawer = getContext<any>("drawer");
23
+
24
+ function handleClose() {
25
+ if (onClose) {
26
+ onClose();
27
+ } else {
28
+ drawer?.closeDrawer();
29
+ }
30
+ }
31
+ </script>
32
+
33
+ <div
34
+ class="flex items-center justify-between p-4 border-b border-gray-200 {className}"
35
+ {...restProps}
36
+ >
37
+ {#if children}
38
+ {@render children()}
39
+ {:else}
40
+ <div class="flex-1">
41
+ {#if title}
42
+ <h2 class="text-lg font-semibold text-gray-900">{title}</h2>
43
+ {/if}
44
+ {#if description}
45
+ <p class="text-sm text-gray-600 mt-1">{description}</p>
46
+ {/if}
47
+ </div>
48
+
49
+ {#if showCloseButton}
50
+ <button
51
+ onclick={handleClose}
52
+ class="p-2 rounded-md hover:bg-gray-100 transition-colors"
53
+ aria-label="Close drawer"
54
+ >
55
+ <svg
56
+ class="w-5 h-5 text-gray-500"
57
+ fill="none"
58
+ stroke="currentColor"
59
+ viewBox="0 0 24 24"
60
+ >
61
+ <path
62
+ stroke-linecap="round"
63
+ stroke-linejoin="round"
64
+ stroke-width="2"
65
+ d="M6 18L18 6M6 6l12 12"
66
+ />
67
+ </svg>
68
+ </button>
69
+ {/if}
70
+ {/if}
71
+ </div>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ title?: string;
3
+ description?: string;
4
+ showCloseButton?: boolean;
5
+ onClose?: () => void;
6
+ class?: string;
7
+ children?: any;
8
+ [key: string]: any;
9
+ };
10
+ declare const DrawerHeader: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type DrawerHeader = ReturnType<typeof DrawerHeader>;
12
+ export default DrawerHeader;
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+
4
+ let {
5
+ children,
6
+ container = undefined,
7
+ }: {
8
+ children?: any;
9
+ container?: HTMLElement | string;
10
+ } = $props();
11
+
12
+ let portalContainer = $state<HTMLElement | null>(null);
13
+ let mounted = $state(false);
14
+
15
+ onMount(() => {
16
+ if (container) {
17
+ if (typeof container === "string") {
18
+ portalContainer = document.querySelector(container);
19
+ } else {
20
+ portalContainer = container;
21
+ }
22
+ } else {
23
+ portalContainer = document.createElement("div");
24
+ portalContainer.setAttribute("data-drawer-portal", "");
25
+ document.body.appendChild(portalContainer);
26
+ }
27
+
28
+ mounted = true;
29
+
30
+ return () => {
31
+ if (
32
+ !container &&
33
+ portalContainer &&
34
+ document.body.contains(portalContainer)
35
+ ) {
36
+ document.body.removeChild(portalContainer);
37
+ }
38
+ };
39
+ });
40
+ </script>
41
+
42
+ {#if mounted && portalContainer}
43
+ {#each [children] as child}
44
+ {@render child?.()}
45
+ {/each}
46
+ {/if}
47
+
48
+ <style>
49
+ :global([data-drawer-portal]) {
50
+ position: relative;
51
+ z-index: 9999;
52
+ }
53
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ children?: any;
3
+ container?: HTMLElement | string;
4
+ };
5
+ declare const DrawerPortal: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type DrawerPortal = ReturnType<typeof DrawerPortal>;
7
+ export default DrawerPortal;
package/dist/index.d.ts CHANGED
@@ -3,4 +3,7 @@ export { default as DrawerContent } from "./components/DrawerContent.svelte";
3
3
  export { default as DrawerOverlay } from "./components/DrawerOverlay.svelte";
4
4
  export { default as DrawerVariants } from "./components/DrawerVariants.svelte";
5
5
  export { default as DrawerHandle } from "./components/DrawerHandle.svelte";
6
- export type { DrawerProps, DrawerContentProps, DrawerOverlayProps, DrawerHandleProps, DrawerVariant, DrawerVariantsProps, } from "./types";
6
+ export { default as DrawerPortal } from "./components/DrawerPortal.svelte";
7
+ export { default as DrawerHeader } from "./components/DrawerHeader.svelte";
8
+ export { default as DrawerFooter } from "./components/DrawerFooter.svelte";
9
+ export type { DrawerProps, DrawerContentProps, DrawerOverlayProps, DrawerHandleProps, DrawerVariant, DrawerVariantsProps, DrawerPortalProps, DrawerHeaderProps, DrawerFooterProps, } from "./types";
package/dist/index.js CHANGED
@@ -3,3 +3,6 @@ export { default as DrawerContent } from "./components/DrawerContent.svelte";
3
3
  export { default as DrawerOverlay } from "./components/DrawerOverlay.svelte";
4
4
  export { default as DrawerVariants } from "./components/DrawerVariants.svelte";
5
5
  export { default as DrawerHandle } from "./components/DrawerHandle.svelte";
6
+ export { default as DrawerPortal } from "./components/DrawerPortal.svelte";
7
+ export { default as DrawerHeader } from "./components/DrawerHeader.svelte";
8
+ export { default as DrawerFooter } from "./components/DrawerFooter.svelte";
package/dist/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface DrawerProps {
6
6
  snapPoints?: number[];
7
7
  activeSnapPoint?: number;
8
8
  onSnapPointChange?: (snapPoint: number) => void;
9
+ portal?: boolean;
10
+ portalContainer?: HTMLElement | string;
9
11
  }
10
12
  export interface DrawerContentProps {
11
13
  class?: string;
@@ -17,6 +19,19 @@ export interface DrawerOverlayProps {
17
19
  export interface DrawerHandleProps {
18
20
  class?: string;
19
21
  }
22
+ export interface DrawerPortalProps {
23
+ container?: HTMLElement | string;
24
+ }
25
+ export interface DrawerHeaderProps {
26
+ title?: string;
27
+ description?: string;
28
+ showCloseButton?: boolean;
29
+ onClose?: () => void;
30
+ class?: string;
31
+ }
32
+ export interface DrawerFooterProps {
33
+ class?: string;
34
+ }
20
35
  export type DrawerVariant = "default" | "sheet" | "dialog" | "minimal" | "sidebar";
21
36
  export interface DrawerVariantsProps {
22
37
  variant?: DrawerVariant;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abhivarde/svelte-drawer",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "A drawer component for Svelte 5, inspired by Vaul",
5
5
  "author": "Abhi Varde",
6
6
  "license": "MIT",