@hashrytech/quick-components-kit 0.15.7 → 0.16.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @hashrytech/quick-components-kit
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - refactor: Drawer, Overlay and adding Portal component
8
+
3
9
  ## 0.15.7
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @action stopInteraction
3
+ *
4
+ * A flexible Svelte action that prevents event propagation and/or default browser behavior
5
+ * for specified events using the capture phase. Useful for modals, drawers, popups, or any
6
+ * element where you want to intercept user interaction early in the event flow.
7
+ *
8
+ * @param node - The target HTML element to attach event listeners to.
9
+ * @param options - An optional object to configure the behavior.
10
+ * - `stop` (default: `true`) — Calls `event.stopPropagation()` to prevent bubbling.
11
+ * - `prevent` (default: `false`) — Calls `event.preventDefault()` to cancel default behavior.
12
+ * - `events` (default: `['click', 'mousedown', 'mouseup', 'touchstart']`) — DOM event types to intercept.
13
+ *
14
+ * @example
15
+ * ```svelte
16
+ * <div use:stopInteraction />
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```svelte
21
+ * <div use:stopInteraction={{ prevent: true }} />
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```svelte
26
+ * <div use:stopInteraction={{ stop: true, prevent: true, events: ['click', 'touchstart'] }} />
27
+ * ```
28
+ */
29
+ export type StopInteractionOptions = {
30
+ stop?: boolean;
31
+ prevent?: boolean;
32
+ events?: string[];
33
+ };
34
+ export declare function stopInteraction(node: HTMLElement, options?: StopInteractionOptions): {
35
+ destroy(): void;
36
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @action stopInteraction
3
+ *
4
+ * A flexible Svelte action that prevents event propagation and/or default browser behavior
5
+ * for specified events using the capture phase. Useful for modals, drawers, popups, or any
6
+ * element where you want to intercept user interaction early in the event flow.
7
+ *
8
+ * @param node - The target HTML element to attach event listeners to.
9
+ * @param options - An optional object to configure the behavior.
10
+ * - `stop` (default: `true`) — Calls `event.stopPropagation()` to prevent bubbling.
11
+ * - `prevent` (default: `false`) — Calls `event.preventDefault()` to cancel default behavior.
12
+ * - `events` (default: `['click', 'mousedown', 'mouseup', 'touchstart']`) — DOM event types to intercept.
13
+ *
14
+ * @example
15
+ * ```svelte
16
+ * <div use:stopInteraction />
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```svelte
21
+ * <div use:stopInteraction={{ prevent: true }} />
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```svelte
26
+ * <div use:stopInteraction={{ stop: true, prevent: true, events: ['click', 'touchstart'] }} />
27
+ * ```
28
+ */
29
+ export function stopInteraction(node, options = { stop: true, prevent: false }) {
30
+ const { stop = true, prevent = false, events = ['click'] } = options;
31
+ const cleanups = events.map((event) => {
32
+ const handler = (e) => {
33
+ if (stop)
34
+ e.stopPropagation();
35
+ if (prevent)
36
+ e.preventDefault();
37
+ };
38
+ node.addEventListener(event, handler, true); // capture
39
+ return () => node.removeEventListener(event, handler, true);
40
+ });
41
+ return {
42
+ destroy() {
43
+ cleanups.forEach((fn) => fn());
44
+ }
45
+ };
46
+ }
@@ -1,10 +1,52 @@
1
- <script lang="ts" module>
1
+ <!--
2
+ @component Drawer
3
+
4
+ A flexible slide-in drawer component built with Svelte 5 and Tailwind CSS. Supports positional rendering (`left`, `right`, `top`, `bottom`), transition control, accessibility, and optional background overlay. Typically used for side menus, filters, or modal-like panels.
5
+
6
+ ## Props
7
+
8
+ - `open?`: `boolean` — Whether the drawer is visible. Bindable.
9
+ - `escapeKeyClose?`: `boolean` — If true, pressing Escape closes the drawer. Default: `true`.
10
+ - `disableBodyScroll?`: `boolean` — Prevents body scrolling when drawer is open. Default: `true`.
11
+ - `ariaLabel?`: `string` — Accessibility label for the drawer. Default: `"Drawer"`.
12
+ - `position?`: `"left" | "right" | "top" | "bottom"` — Direction the drawer slides from. Default: `"left"`.
13
+ - `transitionDuration?`: `number` — Duration of fly transition in milliseconds. Defaults to `config.transitionDuration`.
14
+ - `transitionDistance?`: `number` — Distance the drawer slides in from. Default: `240`.
15
+ - `overlayClasses?`: `string` — Additional classes passed to the background overlay.
16
+ - `class?`: `ClassNameValue` — Tailwind classes for the drawer content container.
17
+ - `children?`: `Snippet` — Svelte fragment rendered inside the drawer content.
18
+
19
+ ## Slots
20
+
21
+ This component uses `children` as a rendered snippet via `{@render}`.
22
+
23
+ ## Overlay
24
+
25
+ The `<Overlay>` component is used to darken the background and optionally block interaction. It is clickable and will trigger the drawer to close unless stopped by event propagation. You can customize its styling using the `overlayClasses` prop.
26
+
27
+ ## Usage
28
+
29
+ ```svelte
30
+ <script>
31
+ import Drawer from '@hashrytech/quick-components-kit';
32
+ let isOpen = false;
33
+ </script>
34
+
35
+ <Drawer bind:open={isOpen} position="right" ariaLabel="Menu" transitionDuration={300}>
36
+ <p class="p-4">Drawer content goes here</p>
37
+ </Drawer>
38
+ ```
39
+ -->
40
+
41
+ <script lang="ts" module>
2
42
  import { type Snippet } from 'svelte';
3
43
  import type { ClassNameValue } from 'tailwind-merge';
4
44
  import { fly } from 'svelte/transition';
5
45
  import {twMerge} from 'tailwind-merge';
6
46
  import Overlay from '../overlay/Overlay.svelte';
7
47
  import { onKeydown } from '../../actions/on-keydown.js';
48
+ import { stopInteraction } from '../../actions/stop-interaction.js';
49
+ import { config } from '../../configs/config.js';
8
50
 
9
51
  export type DrawerProps = {
10
52
  open?: boolean;
@@ -22,7 +64,18 @@
22
64
  </script>
23
65
 
24
66
  <script lang="ts">
25
- let {open=$bindable(false), escapeKeyClose=true, disableBodyScroll=true, ariaLabel="Drawer", position="left", transitionDuration=200, transitionDistance=240, overlayClasses="", children, ...props}: DrawerProps = $props();
67
+ let {
68
+ open=$bindable(false),
69
+ escapeKeyClose=true,
70
+ disableBodyScroll=true,
71
+ ariaLabel="Drawer",
72
+ position="left",
73
+ transitionDuration=config.transitionDuration,
74
+ transitionDistance=240,
75
+ overlayClasses="",
76
+ children,
77
+ ...props
78
+ }: DrawerProps = $props();
26
79
 
27
80
  const transitionProperties = {
28
81
  x: position == "left" ? -transitionDistance : position == "right" ? transitionDistance : 0,
@@ -52,7 +105,7 @@
52
105
  {#if open}
53
106
  <Overlay {transitionDuration} {disableBodyScroll} class={overlayClasses} onclick={() => open = false} />
54
107
 
55
- <div role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="{open ? 0 : -1}" aria-hidden="{!open}"
108
+ <div role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="{open ? 0 : -1}" aria-hidden="{!open}" use:stopInteraction={{ stop: true, prevent: true, events: [''] }}
56
109
  class={twMerge("fixed flex flex-col items-center gap-2 bg-white outline-0 focus:outline-0 active:outline-focus-primary focus:outline-focus-primary overflow-y-auto", postionClasses[position], props.class)}
57
110
  in:fly={transitionProperties}
58
111
  out:fly={transitionProperties}
@@ -12,6 +12,45 @@ export type DrawerProps = {
12
12
  children?: Snippet;
13
13
  class?: ClassNameValue;
14
14
  };
15
+ /**
16
+ * Drawer
17
+ *
18
+ * A flexible slide-in drawer component built with Svelte 5 and Tailwind CSS. Supports positional rendering (`left`, `right`, `top`, `bottom`), transition control, accessibility, and optional background overlay. Typically used for side menus, filters, or modal-like panels.
19
+ *
20
+ * ## Props
21
+ *
22
+ * - `open?`: `boolean` — Whether the drawer is visible. Bindable.
23
+ * - `escapeKeyClose?`: `boolean` — If true, pressing Escape closes the drawer. Default: `true`.
24
+ * - `disableBodyScroll?`: `boolean` — Prevents body scrolling when drawer is open. Default: `true`.
25
+ * - `ariaLabel?`: `string` — Accessibility label for the drawer. Default: `"Drawer"`.
26
+ * - `position?`: `"left" | "right" | "top" | "bottom"` — Direction the drawer slides from. Default: `"left"`.
27
+ * - `transitionDuration?`: `number` — Duration of fly transition in milliseconds. Defaults to `config.transitionDuration`.
28
+ * - `transitionDistance?`: `number` — Distance the drawer slides in from. Default: `240`.
29
+ * - `overlayClasses?`: `string` — Additional classes passed to the background overlay.
30
+ * - `class?`: `ClassNameValue` — Tailwind classes for the drawer content container.
31
+ * - `children?`: `Snippet` — Svelte fragment rendered inside the drawer content.
32
+ *
33
+ * ## Slots
34
+ *
35
+ * This component uses `children` as a rendered snippet via `{@render}`.
36
+ *
37
+ * ## Overlay
38
+ *
39
+ * The `<Overlay>` component is used to darken the background and optionally block interaction. It is clickable and will trigger the drawer to close unless stopped by event propagation. You can customize its styling using the `overlayClasses` prop.
40
+ *
41
+ * ## Usage
42
+ *
43
+ * ```svelte
44
+ * <script>
45
+ * import Drawer from '@hashrytech/quick-components-kit';
46
+ * let isOpen = false;
47
+ * </script>
48
+ *
49
+ * <Drawer bind:open={isOpen} position="right" ariaLabel="Menu" transitionDuration={300}>
50
+ * <p class="p-4">Drawer content goes here</p>
51
+ * </Drawer>
52
+ * ```
53
+ */
15
54
  declare const Drawer: import("svelte").Component<DrawerProps, {
16
55
  closeDrawer: () => void;
17
56
  }, "open">;
@@ -17,10 +17,10 @@
17
17
  </script>
18
18
 
19
19
  <script lang="ts">
20
- let {disableBodyScroll=true, transitionDuration=100, ariaLabel="Overlay", onclick, children, ...props}: OverlayProps = $props();
20
+ let { disableBodyScroll=true, transitionDuration=100, ariaLabel="Overlay", onclick, children, ...props }: OverlayProps = $props();
21
21
  </script>
22
22
 
23
- <div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-overlay-primary", props.class)} role="presentation" {onclick}
24
- use:disableScroll={disableBodyScroll}>
23
+ <div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-overlay-primary", props.class)} role="presentation" {onclick} use:disableScroll={disableBodyScroll}>
24
+ {@render children?.()}
25
25
  </div>
26
26
 
@@ -0,0 +1,73 @@
1
+ <!--
2
+ @component Portal
3
+
4
+ A utility component that renders its children outside the current DOM hierarchy by prepending them into a target DOM element (e.g., `document.body`). Useful for modals, drawers, toasts, and overlays to avoid z-index and layout conflicts.
5
+
6
+ ## Props
7
+
8
+ - `target?`: `HTMLElement` — The DOM node to render the content into. Defaults to `document.body` if not provided. Useful for directing content to a specific container.
9
+ - `prepend?: boolean` — If true (default), the portal will be prepended to the target node. If false, it will be appended. This controls the render order of layered content.
10
+ - `children?`: `Snippet` — The Svelte children content to render inside the portal. Use `{@render}` to render dynamic fragments.
11
+ - `class?`: `ClassNameValue` — Tailwind CSS or custom classes applied to the outer container div.
12
+
13
+ ## Behavior
14
+
15
+ - On mount, a new `div` is created and prepended to the `target`.
16
+ - On destroy, the element is removed from the DOM.
17
+ - Children are rendered via `{@render children?.()}` to support dynamic content blocks.
18
+
19
+ ## Usage
20
+
21
+ ```svelte
22
+ <script lang="ts">
23
+ import Portal from './Portal.svelte';
24
+ let show = true;
25
+ </script>
26
+
27
+ {#if show}
28
+ <Portal>
29
+ <div class="absolute top-0 left-0 h-screen w-screen bg-black/50 z-50">
30
+ <div class="bg-white p-6 rounded shadow">Hello from Portal!</div>
31
+ </div>
32
+ </Portal>
33
+ {/if}
34
+ ```
35
+ -->
36
+
37
+ <script lang="ts" module>
38
+ import { type Snippet } from 'svelte';
39
+ import type { ClassNameValue } from 'tailwind-merge';
40
+ import {twMerge} from 'tailwind-merge';
41
+ import { onMount, onDestroy } from 'svelte';
42
+ import { browser } from '$app/environment';
43
+
44
+ export type PortalProps = {
45
+ target?: HTMLElement;
46
+ prepend?: boolean;
47
+ children?: Snippet;
48
+ class?: ClassNameValue;
49
+ };
50
+
51
+ </script>
52
+
53
+ <script lang="ts">
54
+ let { target = browser ? document.body : undefined, prepend = true, children, ...props }: PortalProps = $props();
55
+ let el: HTMLDivElement;
56
+
57
+ onMount(() => {
58
+ if (!browser || !el) return;
59
+ if(prepend)
60
+ target?.prepend(el);
61
+ else
62
+ target?.append(el);
63
+ });
64
+
65
+ onDestroy(() => {
66
+ el?.remove();
67
+ });
68
+ </script>
69
+
70
+ <!-- This div will be appended to `target` -->
71
+ <div bind:this={el} class={twMerge("", props.class)}>
72
+ {@render children?.()}
73
+ </div>
@@ -0,0 +1,46 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { ClassNameValue } from 'tailwind-merge';
3
+ export type PortalProps = {
4
+ target?: HTMLElement;
5
+ prepend?: boolean;
6
+ children?: Snippet;
7
+ class?: ClassNameValue;
8
+ };
9
+ /**
10
+ * Portal
11
+ *
12
+ * A utility component that renders its children outside the current DOM hierarchy by prepending them into a target DOM element (e.g., `document.body`). Useful for modals, drawers, toasts, and overlays to avoid z-index and layout conflicts.
13
+ *
14
+ * ## Props
15
+ *
16
+ * - `target?`: `HTMLElement` — The DOM node to render the content into. Defaults to `document.body` if not provided. Useful for directing content to a specific container.
17
+ * - `prepend?: boolean` — If true (default), the portal will be prepended to the target node. If false, it will be appended. This controls the render order of layered content.
18
+ * - `children?`: `Snippet` — The Svelte children content to render inside the portal. Use `{@render}` to render dynamic fragments.
19
+ * - `class?`: `ClassNameValue` — Tailwind CSS or custom classes applied to the outer container div.
20
+ *
21
+ * ## Behavior
22
+ *
23
+ * - On mount, a new `div` is created and prepended to the `target`.
24
+ * - On destroy, the element is removed from the DOM.
25
+ * - Children are rendered via `{@render children?.()}` to support dynamic content blocks.
26
+ *
27
+ * ## Usage
28
+ *
29
+ * ```svelte
30
+ * <script lang="ts">
31
+ * import Portal from './Portal.svelte';
32
+ * let show = true;
33
+ * </script>
34
+ *
35
+ * {#if show}
36
+ * <Portal>
37
+ * <div class="absolute top-0 left-0 h-screen w-screen bg-black/50 z-50">
38
+ * <div class="bg-white p-6 rounded shadow">Hello from Portal!</div>
39
+ * </div>
40
+ * </Portal>
41
+ * {/if}
42
+ * ```
43
+ */
44
+ declare const Portal: import("svelte").Component<PortalProps, {}, "">;
45
+ type Portal = ReturnType<typeof Portal>;
46
+ export default Portal;
@@ -0,0 +1 @@
1
+ export { default as Portal } from './Portal.svelte';
@@ -0,0 +1 @@
1
+ export { default as Portal } from './Portal.svelte';
@@ -0,0 +1,3 @@
1
+ export declare const config: {
2
+ transitionDuration: number;
3
+ };
@@ -0,0 +1,3 @@
1
+ export const config = {
2
+ transitionDuration: 150,
3
+ };
package/dist/index.d.ts CHANGED
@@ -8,10 +8,12 @@ export * from './components/overlay/index.js';
8
8
  export * from './components/radio/index.js';
9
9
  export * from './components/checkbox/index.js';
10
10
  export * from './components/tab-navigation/index.js';
11
+ export * from './components/portal/index.js';
11
12
  export * from './actions/disable-scroll.js';
12
13
  export * from './actions/on-keydown.js';
13
14
  export * from './actions/lock-scroll.js';
14
15
  export * from './actions/scroll-to.js';
16
+ export * from './actions/stop-interaction.js';
15
17
  export * from './modules/fetch-client.js';
16
18
  export * from './modules/api-proxy.js';
17
19
  export * from './modules/crypto.js';
package/dist/index.js CHANGED
@@ -10,10 +10,12 @@ export * from './components/overlay/index.js';
10
10
  export * from './components/radio/index.js';
11
11
  export * from './components/checkbox/index.js';
12
12
  export * from './components/tab-navigation/index.js';
13
+ export * from './components/portal/index.js';
13
14
  export * from './actions/disable-scroll.js';
14
15
  export * from './actions/on-keydown.js';
15
16
  export * from './actions/lock-scroll.js';
16
17
  export * from './actions/scroll-to.js';
18
+ export * from './actions/stop-interaction.js';
17
19
  export * from './modules/fetch-client.js';
18
20
  export * from './modules/api-proxy.js';
19
21
  export * from './modules/crypto.js';
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/hashrytech/quick-components-kit.git"
7
7
  },
8
- "version": "0.15.7",
8
+ "version": "0.16.0",
9
9
  "license": "MIT",
10
10
  "author": "Hashry Tech",
11
11
  "files": [