@marianmeres/stuic 1.0.3

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 ADDED
@@ -0,0 +1,15 @@
1
+ # @marianmeres/stuic
2
+
3
+ *S*velte *T*ailwind _UI_ *C*omponents.
4
+
5
+ OPINIONATED, UNSTABLE, AND IN PROGRESS...
6
+
7
+ Ongoing effort to build reusable Svelte UI primitives as I need them, inspired by many
8
+ other libs, mainly:
9
+
10
+ - https://www.skeleton.dev/
11
+ - https://flowbite-svelte.com/
12
+ - https://captaincodeman.github.io/svelte-headlessui/
13
+ - https://www.svelteui.org/
14
+
15
+ Cherry-picked, combined, adjusted and tweaked to my personal preference and needs.
@@ -0,0 +1,3 @@
1
+ export declare const clickOutside: (node: HTMLElement, callback: () => void) => {
2
+ destroy(): void;
3
+ };
@@ -0,0 +1,15 @@
1
+ export const clickOutside = (node, callback) => {
2
+ const handleClick = (event) => {
3
+ if (!event?.target)
4
+ return;
5
+ if (node && !node.contains(event.target) && !event.defaultPrevented) {
6
+ callback();
7
+ }
8
+ };
9
+ document.addEventListener('click', handleClick, true);
10
+ return {
11
+ destroy() {
12
+ document.removeEventListener('click', handleClick, true);
13
+ },
14
+ };
15
+ };
@@ -0,0 +1,4 @@
1
+ export declare function focusTrap(node: HTMLElement, enabled: boolean): {
2
+ update(newArgs: boolean): void;
3
+ destroy(): void;
4
+ };
@@ -0,0 +1,66 @@
1
+ // copied from skeleton
2
+ // Action: Focus Trap
3
+ export function focusTrap(node, enabled) {
4
+ const elemWhitelist = 'a[href]:not([tabindex="-1"]), button:not([tabindex="-1"]), input:not([tabindex="-1"]), textarea:not([tabindex="-1"]), select:not([tabindex="-1"]), details:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])';
5
+ let elemFirst;
6
+ let elemLast;
7
+ // When the first element is selected, shift+tab pressed, jump to the last selectable item.
8
+ function onFirstElemKeydown(e) {
9
+ if (e.shiftKey && e.code === 'Tab') {
10
+ e.preventDefault();
11
+ elemLast.focus();
12
+ }
13
+ }
14
+ // When the last item selected, tab pressed, jump to the first selectable item.
15
+ function onLastElemKeydown(e) {
16
+ if (!e.shiftKey && e.code === 'Tab') {
17
+ e.preventDefault();
18
+ elemFirst.focus();
19
+ }
20
+ }
21
+ const onScanElements = (fromObserver) => {
22
+ if (enabled === false)
23
+ return;
24
+ // Gather all focusable elements
25
+ const focusableElems = Array.from(node.querySelectorAll(elemWhitelist));
26
+ if (focusableElems.length) {
27
+ // Set first/last focusable elements
28
+ elemFirst = focusableElems[0];
29
+ elemLast = focusableElems[focusableElems.length - 1];
30
+ // Auto-focus first focusable element only when not called from observer
31
+ if (!fromObserver)
32
+ elemFirst.focus();
33
+ // Listen for keydown on first & last element
34
+ elemFirst.addEventListener('keydown', onFirstElemKeydown);
35
+ elemLast.addEventListener('keydown', onLastElemKeydown);
36
+ }
37
+ };
38
+ onScanElements(false);
39
+ function onCleanUp() {
40
+ if (elemFirst)
41
+ elemFirst.removeEventListener('keydown', onFirstElemKeydown);
42
+ if (elemLast)
43
+ elemLast.removeEventListener('keydown', onLastElemKeydown);
44
+ }
45
+ // When children of node are changed (added or removed)
46
+ const onObservationChange = (mutationRecords, observer) => {
47
+ if (mutationRecords.length) {
48
+ onCleanUp();
49
+ onScanElements(true);
50
+ }
51
+ return observer;
52
+ };
53
+ const observer = new MutationObserver(onObservationChange);
54
+ observer.observe(node, { childList: true, subtree: true });
55
+ // Lifecycle
56
+ return {
57
+ update(newArgs) {
58
+ enabled = newArgs;
59
+ newArgs ? onScanElements(false) : onCleanUp();
60
+ },
61
+ destroy() {
62
+ onCleanUp();
63
+ observer.disconnect();
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,201 @@
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import { twMerge } from "tailwind-merge";
3
+ const dispatch = createEventDispatcher();
4
+ let _class = "";
5
+ export { _class as class };
6
+ export let railClass = "";
7
+ export let headerClass = "";
8
+ export let contentClass = "";
9
+ export let sidebarLeftClass = "";
10
+ export let pageClass = "";
11
+ export let pageHeaderClass = "";
12
+ export let pageMainClass = "";
13
+ export let pageFooterClass = "";
14
+ export let sidebarRightClass = "";
15
+ export let footerClass = "";
16
+ export let scrollbarGutter = "auto";
17
+ export let id = "shell";
18
+ export let pageFlexGrow = 3;
19
+ $:
20
+ _sidebarFlexCls = pageFlexGrow ? "flex-1" : "flex-none";
21
+ const flexMap = ["flex-1", "flex-1", "flex-[2]", "flex-[3]", "flex-[4]", "flex-[5]"];
22
+ $:
23
+ _pageFlexCls = flexMap[pageFlexGrow] || "flex-1";
24
+ let shell;
25
+ let rail;
26
+ let header;
27
+ let sidebarLeft;
28
+ let page;
29
+ let pageHeader;
30
+ let pageMain;
31
+ let pageFooter;
32
+ let sidebarRight;
33
+ let footer;
34
+ $:
35
+ if (shell)
36
+ dispatch("element", { shell });
37
+ $:
38
+ if (rail)
39
+ dispatch("element", { rail });
40
+ $:
41
+ if (header)
42
+ dispatch("element", { header });
43
+ $:
44
+ if (sidebarLeft)
45
+ dispatch("element", { sidebarLeft });
46
+ $:
47
+ if (page)
48
+ dispatch("element", { page });
49
+ $:
50
+ if (pageHeader)
51
+ dispatch("element", { pageHeader });
52
+ $:
53
+ if (pageMain)
54
+ dispatch("element", { pageMain });
55
+ $:
56
+ if (pageFooter)
57
+ dispatch("element", { pageFooter });
58
+ $:
59
+ if (sidebarRight)
60
+ dispatch("element", { sidebarRight });
61
+ $:
62
+ if (footer)
63
+ dispatch("element", { footer });
64
+ </script>
65
+
66
+ <!-- shell -->
67
+ <div
68
+ bind:this={shell}
69
+ {id}
70
+ data-shell="shell"
71
+ class={twMerge(`w-full h-full flex overflow-hidden ${_class}`)}
72
+ >
73
+ <!-- shell > rail -->
74
+ {#if $$slots.rail}
75
+ <div
76
+ bind:this={rail}
77
+ data-shell="rail"
78
+ class={twMerge(`flex-none overflow-x-hidden overflow-y-auto ${railClass}`)}
79
+ >
80
+ <slot name="rail" />
81
+ </div>
82
+ {/if}
83
+
84
+ <!-- shell > div-->
85
+ <div class="h-full flex-1 flex flex-col overflow-hidden">
86
+ <!-- shell > div > header -->
87
+ {#if $$slots.header}
88
+ <header
89
+ bind:this={header}
90
+ data-shell="header"
91
+ class={twMerge(`flex-none z-10 ${headerClass}`)}
92
+ >
93
+ <slot name="header" />
94
+ </header>
95
+ {/if}
96
+
97
+ <!-- shell > div > content -->
98
+ <div
99
+ data-shell="content"
100
+ class={twMerge(`flex-auto w-full h-full flex overflow-hidden ${contentClass}`)}
101
+ >
102
+ <!-- shell > div > content > sidebar-left -->
103
+ {#if $$slots.sidebarLeft}
104
+ <aside
105
+ bind:this={sidebarLeft}
106
+ data-shell="sidebar-left"
107
+ class={twMerge(
108
+ `${_sidebarFlexCls} overflow-x-hidden overflow-y-auto w-auto ${sidebarLeftClass}`
109
+ )}
110
+ >
111
+ <slot name="sidebarLeft" />
112
+ </aside>
113
+ {/if}
114
+
115
+ <!-- shell > div > content > page -->
116
+ <div
117
+ bind:this={page}
118
+ data-shell="page"
119
+ class={twMerge(`${_pageFlexCls} overflow-x-hidden flex flex-col ${pageClass}`)}
120
+ style:scrollbar-gutter={scrollbarGutter}
121
+ on:scroll
122
+ >
123
+ <!-- shell > div > content > page > page-header -->
124
+ {#if $$slots.pageHeader}
125
+ <header
126
+ bind:this={pageHeader}
127
+ data-shell="page-header"
128
+ class={twMerge(`flex-none ${pageHeaderClass}`)}
129
+ >
130
+ <slot name="pageHeader" />
131
+ </header>
132
+ {/if}
133
+
134
+ <!-- shell > div > content > page > page-main -->
135
+ <main
136
+ bind:this={pageMain}
137
+ data-shell="page-main"
138
+ class={twMerge(`flex-auto ${pageMainClass}`)}
139
+ >
140
+ <slot />
141
+ </main>
142
+
143
+ <!-- shell > div > content > page > page-footer -->
144
+ {#if $$slots.pageFooter}
145
+ <footer
146
+ bind:this={pageFooter}
147
+ data-shell="page-footer"
148
+ class={twMerge(`flex-none ${pageFooterClass}`)}
149
+ >
150
+ <slot name="pageFooter" />
151
+ </footer>
152
+ {/if}
153
+ </div>
154
+
155
+ <!-- shell > div > content > sidebar-right -->
156
+ {#if $$slots.sidebarRight}
157
+ <aside
158
+ bind:this={sidebarRight}
159
+ data-shell="sidebar-right"
160
+ class={twMerge(
161
+ `${_sidebarFlexCls} overflow-x-hidden overflow-y-auto ${sidebarRightClass}`
162
+ )}
163
+ >
164
+ <slot name="sidebarRight" />
165
+ </aside>
166
+ {/if}
167
+ </div>
168
+
169
+ <!-- shell > div > footer -->
170
+ {#if $$slots.footer}
171
+ <footer
172
+ bind:this={footer}
173
+ data-shell="footer"
174
+ class={twMerge(`flex-none ${footerClass}`)}
175
+ >
176
+ <slot name="footer" />
177
+ </footer>
178
+ {/if}
179
+ </div>
180
+ </div>
181
+
182
+ <style>/* from: https://www.skeleton.dev/components/app-shell
183
+
184
+ The App Shell will need to expand to fill all available space within your app's body tag.
185
+ Open /src/app.html and add the following classes. This wrapping element is required
186
+ and the style of display: contents should remain.
187
+
188
+ <body>
189
+ <div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
190
+ </body>
191
+
192
+ Then update your global stylesheet with the following. This will disable overflow for
193
+ html and body tags to prevent duplicate scroll bars.
194
+
195
+ html, body { @apply h-full overflow-hidden; }
196
+ */
197
+ :global(html),
198
+ :global(body) {
199
+ height: 100vh !important;
200
+ overflow: hidden !important;
201
+ }</style>
@@ -0,0 +1,41 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ class?: string | undefined;
5
+ railClass?: string | undefined;
6
+ headerClass?: string | undefined;
7
+ contentClass?: string | undefined;
8
+ sidebarLeftClass?: string | undefined;
9
+ pageClass?: string | undefined;
10
+ pageHeaderClass?: string | undefined;
11
+ pageMainClass?: string | undefined;
12
+ pageFooterClass?: string | undefined;
13
+ sidebarRightClass?: string | undefined;
14
+ footerClass?: string | undefined;
15
+ scrollbarGutter?: string | undefined;
16
+ id?: string | undefined;
17
+ pageFlexGrow?: 0 | 1 | 2 | 3 | 4 | 5 | undefined;
18
+ };
19
+ events: {
20
+ scroll: Event;
21
+ element: CustomEvent<any>;
22
+ } & {
23
+ [evt: string]: CustomEvent<any>;
24
+ };
25
+ slots: {
26
+ rail: {};
27
+ header: {};
28
+ sidebarLeft: {};
29
+ pageHeader: {};
30
+ default: {};
31
+ pageFooter: {};
32
+ sidebarRight: {};
33
+ footer: {};
34
+ };
35
+ };
36
+ export type AppShellProps = typeof __propDef.props;
37
+ export type AppShellEvents = typeof __propDef.events;
38
+ export type AppShellSlots = typeof __propDef.slots;
39
+ export default class AppShell extends SvelteComponent<AppShellProps, AppShellEvents, AppShellSlots> {
40
+ }
41
+ export {};
@@ -0,0 +1,51 @@
1
+ <script context="module">export class BackdropConfig {
2
+ static class = "";
3
+ static fadeInDuration = 50;
4
+ static fadeOutDuration = 150;
5
+ }
6
+ </script>
7
+
8
+ <script>import { createEventDispatcher } from "svelte";
9
+ import { fade } from "svelte/transition";
10
+ import { twMerge } from "tailwind-merge";
11
+ import { focusTrap } from "../../actions/focus-trap.js";
12
+ import { prefersReducedMotionStore } from "../../utils/prefers-reduced-motion.js";
13
+ const dispatch = createEventDispatcher();
14
+ export let useFocusTrap = true;
15
+ let _class = "";
16
+ export { _class as class };
17
+ export let fadeInDuration = BackdropConfig.fadeInDuration;
18
+ export let fadeOutDuration = BackdropConfig.fadeOutDuration;
19
+ export let transitionEnabled = !$prefersReducedMotionStore;
20
+ $:
21
+ if (!transitionEnabled) {
22
+ fadeInDuration = 0;
23
+ fadeOutDuration = 0;
24
+ }
25
+ let el;
26
+ $:
27
+ if (el)
28
+ dispatch("element", { backdrop: el });
29
+ </script>
30
+
31
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
32
+ <!-- prettier-ignore -->
33
+ <div
34
+ bind:this={el}
35
+ class={twMerge(`
36
+ fixed inset-0 flex z-10
37
+ ${BackdropConfig.class} ${_class}
38
+ `.trim())}
39
+ on:click
40
+ on:mousedown
41
+ on:touchstart|passive
42
+ on:touchend|passive
43
+ on:keydown={(e) => e.code === 'Escape' && dispatch('escape')}
44
+ in:fade={{ duration: fadeInDuration }}
45
+ out:fade={{ duration: fadeOutDuration }}
46
+ use:focusTrap={useFocusTrap}
47
+ role="presentation"
48
+ tabindex="-1"
49
+ >
50
+ <slot />
51
+ </div>
@@ -0,0 +1,34 @@
1
+ import { SvelteComponent } from "svelte";
2
+ export declare class BackdropConfig {
3
+ static class: string;
4
+ static fadeInDuration: number;
5
+ static fadeOutDuration: number;
6
+ }
7
+ declare const __propDef: {
8
+ props: {
9
+ useFocusTrap?: boolean | undefined;
10
+ class?: string | undefined;
11
+ fadeInDuration?: number | undefined;
12
+ fadeOutDuration?: number | undefined;
13
+ transitionEnabled?: boolean | undefined;
14
+ };
15
+ events: {
16
+ click: MouseEvent;
17
+ mousedown: MouseEvent;
18
+ touchstart: TouchEvent;
19
+ touchend: TouchEvent;
20
+ escape: CustomEvent<any>;
21
+ element: CustomEvent<any>;
22
+ } & {
23
+ [evt: string]: CustomEvent<any>;
24
+ };
25
+ slots: {
26
+ default: {};
27
+ };
28
+ };
29
+ export type BackdropProps = typeof __propDef.props;
30
+ export type BackdropEvents = typeof __propDef.events;
31
+ export type BackdropSlots = typeof __propDef.slots;
32
+ export default class Backdrop extends SvelteComponent<BackdropProps, BackdropEvents, BackdropSlots> {
33
+ }
34
+ export {};
@@ -0,0 +1,130 @@
1
+ <script context="module">import { twMerge } from "tailwind-merge";
2
+ export class ButtonConfig {
3
+ static defaultSize = "md";
4
+ static defaultShadow = false;
5
+ static defaultRounded = true;
6
+ static defaultVariant = void 0;
7
+ // hover:brightness-[1.15]
8
+ static presetBase = `
9
+ text-base
10
+ text-center whitespace-nowrap
11
+ inline-flex justify-center items-center gap-x-1
12
+ border border-transparent
13
+ hover:brightness-[1.1]
14
+ active:brightness-[0.95]
15
+ disabled:!cursor-not-allowed disabled:!opacity-50 disabled:hover:brightness-100
16
+ no-underline
17
+ `.trim();
18
+ static presetSquare = "p-0 aspect-square";
19
+ static presetsRounded = {
20
+ xs: "rounded",
21
+ sm: "rounded",
22
+ md: "rounded-md",
23
+ lg: "rounded-md",
24
+ xl: "rounded-lg"
25
+ };
26
+ static presetsShadow = {
27
+ xs: "shadow-sm dark:shadow-black",
28
+ sm: "shadow dark:shadow-black",
29
+ md: "shadow dark:shadow-black",
30
+ lg: "shadow-md dark:shadow-black",
31
+ xl: "shadow-md dark:shadow-black"
32
+ };
33
+ static presetsSize = {
34
+ xs: "px-2 py-0.5 text-xs",
35
+ sm: "px-3 py-1 text-sm",
36
+ md: "px-3.5 py-1 text-base",
37
+ lg: "px-4 py-1.5 text-lg",
38
+ xl: "px-4 py-2 text-xl"
39
+ };
40
+ static classBySize = {
41
+ xs: "",
42
+ sm: "",
43
+ md: "",
44
+ lg: "",
45
+ xl: ""
46
+ };
47
+ static class = "";
48
+ // to be defined at consumer level...
49
+ static variant = {};
50
+ }
51
+ </script>
52
+
53
+ <script>let _class = "";
54
+ export { _class as class };
55
+ export let href = "";
56
+ export let type = "button";
57
+ export let shadow = ButtonConfig.defaultShadow;
58
+ export let rounded = ButtonConfig.defaultRounded;
59
+ export let variant = ButtonConfig.defaultVariant;
60
+ export let square = false;
61
+ export let disabled = false;
62
+ const _whitelist = ["xs", "sm", "md", "lg", "xl"];
63
+ export let size = ButtonConfig.defaultSize;
64
+ $:
65
+ if (!_whitelist.includes(size))
66
+ size = ButtonConfig.defaultSize;
67
+ let buttonClass;
68
+ $:
69
+ buttonClass = twMerge(
70
+ ButtonConfig.presetBase,
71
+ // either full, or config, or none
72
+ rounded ? rounded === "full" || square ? "rounded-full" : ButtonConfig.presetsRounded[size] : "",
73
+ //
74
+ shadow && ButtonConfig.presetsShadow[size],
75
+ //
76
+ ButtonConfig.presetsSize[size],
77
+ //
78
+ ButtonConfig.classBySize[size],
79
+ //
80
+ ButtonConfig.class,
81
+ //
82
+ // variant && ButtonConfig.variant?.[variant],
83
+ variant && `${variant || ""}`.split(" ").reduce((m, v) => {
84
+ m += ButtonConfig.variant?.[v] + " ";
85
+ return m;
86
+ }, ""),
87
+ //
88
+ square && ButtonConfig.presetSquare,
89
+ //
90
+ _class
91
+ );
92
+ </script>
93
+
94
+ {#if href}
95
+ <a
96
+ {href}
97
+ class={buttonClass}
98
+ {...$$restProps}
99
+ role="button"
100
+ on:click
101
+ on:change
102
+ on:keydown
103
+ on:keyup
104
+ on:touchstart|passive
105
+ on:touchend
106
+ on:touchcancel
107
+ on:mouseenter
108
+ on:mouseleave
109
+ >
110
+ <slot />
111
+ </a>
112
+ {:else}
113
+ <button
114
+ {type}
115
+ {disabled}
116
+ class={buttonClass}
117
+ {...$$restProps}
118
+ on:click
119
+ on:change
120
+ on:keydown
121
+ on:keyup
122
+ on:touchstart|passive
123
+ on:touchend
124
+ on:touchcancel
125
+ on:mouseenter
126
+ on:mouseleave
127
+ >
128
+ <slot />
129
+ </button>
130
+ {/if}
@@ -0,0 +1,76 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ export declare class ButtonConfig {
4
+ static defaultSize: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
5
+ static defaultShadow: boolean;
6
+ static defaultRounded: boolean;
7
+ static defaultVariant: string | undefined;
8
+ static presetBase: string;
9
+ static presetSquare: string;
10
+ static presetsRounded: {
11
+ xs: string;
12
+ sm: string;
13
+ md: string;
14
+ lg: string;
15
+ xl: string;
16
+ };
17
+ static presetsShadow: {
18
+ xs: string;
19
+ sm: string;
20
+ md: string;
21
+ lg: string;
22
+ xl: string;
23
+ };
24
+ static presetsSize: {
25
+ xs: string;
26
+ sm: string;
27
+ md: string;
28
+ lg: string;
29
+ xl: string;
30
+ };
31
+ static classBySize: {
32
+ xs: string;
33
+ sm: string;
34
+ md: string;
35
+ lg: string;
36
+ xl: string;
37
+ };
38
+ static class: string;
39
+ static variant: Record<string, string>;
40
+ }
41
+ declare const __propDef: {
42
+ props: {
43
+ [x: string]: any;
44
+ class?: string | undefined;
45
+ href?: string | undefined;
46
+ type?: HTMLButtonAttributes['type'];
47
+ shadow?: boolean | undefined;
48
+ rounded?: boolean | "full" | undefined;
49
+ variant?: string | undefined;
50
+ square?: boolean | undefined;
51
+ disabled?: boolean | undefined;
52
+ size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined;
53
+ };
54
+ events: {
55
+ click: MouseEvent;
56
+ change: Event;
57
+ keydown: KeyboardEvent;
58
+ keyup: KeyboardEvent;
59
+ touchstart: TouchEvent;
60
+ touchend: TouchEvent;
61
+ touchcancel: TouchEvent;
62
+ mouseenter: MouseEvent;
63
+ mouseleave: MouseEvent;
64
+ } & {
65
+ [evt: string]: CustomEvent<any>;
66
+ };
67
+ slots: {
68
+ default: {};
69
+ };
70
+ };
71
+ export type ButtonProps = typeof __propDef.props;
72
+ export type ButtonEvents = typeof __propDef.events;
73
+ export type ButtonSlots = typeof __propDef.slots;
74
+ export default class Button extends SvelteComponent<ButtonProps, ButtonEvents, ButtonSlots> {
75
+ }
76
+ export {};
@@ -0,0 +1,89 @@
1
+ <script context="module">import { createClog } from "@marianmeres/clog";
2
+ import { createSwitchStore } from "@marianmeres/switch-store";
3
+ import { createEventDispatcher } from "svelte";
4
+ import { slide, fly } from "svelte/transition";
5
+ import { twMerge } from "tailwind-merge";
6
+ import { prefersReducedMotionStore } from "../../utils/prefers-reduced-motion.js";
7
+ import Backdrop from "../Backdrop/Backdrop.svelte";
8
+ import { clickOutside } from "../../actions/click-outside.js";
9
+ export const createDrawerStore = (open = false) => createSwitchStore(open);
10
+ </script>
11
+
12
+ <script>const clog = createClog("Drawer");
13
+ const dispatch = createEventDispatcher();
14
+ export let drawer;
15
+ export let position = "left";
16
+ const _whitelist = ["left", "top", "right", "bottom"];
17
+ $:
18
+ if (!_whitelist.includes(position))
19
+ position = "left";
20
+ let _class = "";
21
+ export { _class as class };
22
+ export let backdropClass = "";
23
+ export let labelledby = "";
24
+ export let describedby = "";
25
+ export let transitionDuration = 250;
26
+ export let transitionEnabled = !$prefersReducedMotionStore;
27
+ export let animOffset = "66vw";
28
+ $:
29
+ fadeInDuration = transitionEnabled ? Math.min(transitionDuration * 0.66, 200) : 0;
30
+ const _presetsClsBackdrop = {
31
+ left: `justify-start`,
32
+ right: `justify-end`,
33
+ top: `items-start`,
34
+ bottom: `items-end`
35
+ };
36
+ const _presetsCls = {
37
+ left: `w-full sm:w-[66vw] h-full`,
38
+ right: `w-full sm:w-[66vw] h-full`,
39
+ top: `w-full h-full sm:h-[66vh]`,
40
+ bottom: `w-full h-full sm:h-[66vh]`
41
+ };
42
+ $:
43
+ _presetsAnim = {
44
+ left: { x: `-${animOffset}`, y: 0 },
45
+ right: { x: animOffset, y: 0 },
46
+ top: { x: 0, y: `-${animOffset}` },
47
+ bottom: { x: 0, y: animOffset }
48
+ };
49
+ let el;
50
+ $:
51
+ if (el)
52
+ dispatch("element", { drawer: el });
53
+ </script>
54
+
55
+ <!-- prettier-ignore -->
56
+ {#if $drawer.isOpen}
57
+ <Backdrop
58
+ class={twMerge(`
59
+ ${_presetsClsBackdrop[position] || ''} ${backdropClass}
60
+ `.trim())}
61
+ on:escape
62
+ on:click={(e) => dispatch('click_backdrop')}
63
+ {fadeInDuration}
64
+ fadeOutDuration={transitionEnabled ? transitionDuration : 0}
65
+ on:element
66
+ >
67
+ <!--
68
+ svelte-ignore
69
+ a11y-click-events-have-key-events
70
+ a11y-no-noninteractive-element-interactions
71
+ -->
72
+ <div
73
+ bind:this={el}
74
+ on:click|stopPropagation
75
+ aria-modal="true"
76
+ role="dialog"
77
+ aria-labelledby={labelledby}
78
+ aria-describedby={describedby}
79
+ transition:fly={{
80
+ duration: transitionEnabled ? transitionDuration : 0,
81
+ ...(_presetsAnim[position] || {}),
82
+ }}
83
+ class={twMerge(`overflow-y-auto ${_presetsCls[position] || ''} ${_class}`.trim())}
84
+ use:clickOutside={() => dispatch('click_outside')}
85
+ >
86
+ <slot />
87
+ </div>
88
+ </Backdrop>
89
+ {/if}
@@ -0,0 +1,32 @@
1
+ import { SvelteComponent } from "svelte";
2
+ export type DrawerStore = ReturnType<typeof createDrawerStore>;
3
+ export declare const createDrawerStore: (open?: boolean) => import("@marianmeres/switch-store").SwitchStore<any>;
4
+ declare const __propDef: {
5
+ props: {
6
+ drawer: import("@marianmeres/switch-store").SwitchStore<any>;
7
+ position?: "left" | "top" | "right" | "bottom" | undefined;
8
+ class?: string | undefined;
9
+ backdropClass?: string | undefined;
10
+ labelledby?: string | undefined;
11
+ describedby?: string | undefined;
12
+ transitionDuration?: number | undefined;
13
+ transitionEnabled?: boolean | undefined;
14
+ animOffset?: string | number | undefined;
15
+ };
16
+ events: {
17
+ escape: CustomEvent<any>;
18
+ element: CustomEvent<any>;
19
+ click: MouseEvent;
20
+ } & {
21
+ [evt: string]: CustomEvent<any>;
22
+ };
23
+ slots: {
24
+ default: {};
25
+ };
26
+ };
27
+ export type DrawerProps = typeof __propDef.props;
28
+ export type DrawerEvents = typeof __propDef.events;
29
+ export type DrawerSlots = typeof __propDef.slots;
30
+ export default class Drawer extends SvelteComponent<DrawerProps, DrawerEvents, DrawerSlots> {
31
+ }
32
+ export {};
@@ -0,0 +1,28 @@
1
+ <script context="module">export class ColorScheme {
2
+ static KEY = "color-scheme";
3
+ static getSystemValue = () => window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
4
+ static getLocalValue = () => localStorage.getItem(ColorScheme.KEY);
5
+ static getValue = () => {
6
+ return ColorScheme.KEY in localStorage ? localStorage.getItem(ColorScheme.KEY) : ColorScheme.getSystemValue();
7
+ };
8
+ static toggle = () => {
9
+ const isDark = window.document.documentElement.classList.toggle("dark");
10
+ localStorage.setItem(ColorScheme.KEY, isDark ? "dark" : "light");
11
+ };
12
+ static reset = () => {
13
+ localStorage.removeItem(ColorScheme.KEY);
14
+ };
15
+ }
16
+ </script>
17
+
18
+ <svelte:head>
19
+ <script>
20
+ const KEY = 'color-scheme';
21
+ const cls = window.document.documentElement.classList;
22
+ if (KEY in localStorage) {
23
+ localStorage.getItem(KEY) === 'dark' ? cls.add('dark') : cls.remove('dark');
24
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
25
+ cls.add('dark');
26
+ }
27
+ </script>
28
+ </svelte:head>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponent } from "svelte";
2
+ export declare class ColorScheme {
3
+ static readonly KEY = "color-scheme";
4
+ static getSystemValue: () => "dark" | "light";
5
+ static getLocalValue: () => string | null;
6
+ static getValue: () => string | null;
7
+ static toggle: () => void;
8
+ static reset: () => void;
9
+ }
10
+ declare const __propDef: {
11
+ props: Record<string, never>;
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ };
17
+ export type PrefersColorSchemeProps = typeof __propDef.props;
18
+ export type PrefersColorSchemeEvents = typeof __propDef.events;
19
+ export type PrefersColorSchemeSlots = typeof __propDef.slots;
20
+ export default class PrefersColorScheme extends SvelteComponent<PrefersColorSchemeProps, PrefersColorSchemeEvents, PrefersColorSchemeSlots> {
21
+ }
22
+ export {};
@@ -0,0 +1,16 @@
1
+ <script>import { twMerge } from "tailwind-merge";
2
+ let _class = "";
3
+ export { _class as class };
4
+ export let size = "1em";
5
+ </script>
6
+
7
+ <svg
8
+ fill="none"
9
+ viewBox="0 0 24 24"
10
+ stroke-width="1.5"
11
+ stroke="currentColor"
12
+ style="width:{size};height:{size};"
13
+ class={twMerge(`inline ${_class}`)}
14
+ >
15
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
16
+ </svg>
@@ -0,0 +1,17 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ class?: string | undefined;
5
+ size?: string | undefined;
6
+ };
7
+ events: {
8
+ [evt: string]: CustomEvent<any>;
9
+ };
10
+ slots: {};
11
+ };
12
+ export type XProps = typeof __propDef.props;
13
+ export type XEvents = typeof __propDef.events;
14
+ export type XSlots = typeof __propDef.slots;
15
+ export default class X extends SvelteComponent<XProps, XEvents, XSlots> {
16
+ }
17
+ export {};
@@ -0,0 +1,6 @@
1
+ export { default as AppShell } from './components/AppShell/AppShell.svelte';
2
+ export { default as Backdrop, BackdropConfig, } from './components/Backdrop/Backdrop.svelte';
3
+ export { default as Button, ButtonConfig } from './components/Button/Button.svelte';
4
+ export { default as PrefersColorScheme, ColorScheme, } from './components/PrefersColorScheme/PrefersColorScheme.svelte';
5
+ export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer.svelte';
6
+ export { default as X } from './components/X/X.svelte';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // Reexport your entry components here
2
+ //
3
+ export { default as AppShell } from './components/AppShell/AppShell.svelte';
4
+ //
5
+ export { default as Backdrop, BackdropConfig, } from './components/Backdrop/Backdrop.svelte';
6
+ // export { BackdropConfig } from './components/Backdrop/backdrop.js';
7
+ //
8
+ export { default as Button, ButtonConfig } from './components/Button/Button.svelte';
9
+ //
10
+ export { default as PrefersColorScheme, ColorScheme, } from './components/PrefersColorScheme/PrefersColorScheme.svelte';
11
+ //
12
+ export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer.svelte';
13
+ //
14
+ export { default as X } from './components/X/X.svelte';
@@ -0,0 +1,3 @@
1
+ export type SvelteEvent<E extends Event = Event, T extends EventTarget = Element> = E & {
2
+ currentTarget: EventTarget & T;
3
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ /// <reference types="svelte" />
2
+ /**
3
+ * Indicates that the user has enabled reduced motion on their device.
4
+ *
5
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
6
+ */
7
+ export declare const prefersReducedMotionStore: import("svelte/store").Readable<boolean>;
@@ -0,0 +1,26 @@
1
+ import { readable } from 'svelte/store';
2
+ import { BROWSER } from 'esm-env';
3
+ /** Prefers reduced motion */
4
+ const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
5
+ function prefersReducedMotion() {
6
+ if (!BROWSER)
7
+ return false;
8
+ return window.matchMedia(reducedMotionQuery).matches;
9
+ }
10
+ /**
11
+ * Indicates that the user has enabled reduced motion on their device.
12
+ *
13
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
14
+ */
15
+ export const prefersReducedMotionStore = readable(prefersReducedMotion(), (set) => {
16
+ if (BROWSER) {
17
+ const setReducedMotion = (event) => {
18
+ set(event.matches);
19
+ };
20
+ const mediaQueryList = window.matchMedia(reducedMotionQuery);
21
+ mediaQueryList.addEventListener('change', setReducedMotion);
22
+ return () => {
23
+ mediaQueryList.removeEventListener('change', setReducedMotion);
24
+ };
25
+ }
26
+ });
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@marianmeres/stuic",
3
+ "version": "1.0.3",
4
+ "scripts": {
5
+ "dev": "vite dev",
6
+ "build": "vite build && npm run package",
7
+ "build:watch": "fswatch -o src | xargs -n1 -I{} npm run build",
8
+ "preview": "vite preview",
9
+ "package": "svelte-kit sync && svelte-package && publint",
10
+ "prepublishOnly": "npm run package",
11
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13
+ "lint": "prettier --check .",
14
+ "format": "prettier --write .",
15
+ "prettier": "npm run format",
16
+ "release:minor": "release -v minor",
17
+ "release": "release -v patch"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "svelte": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "!dist/**/*.test.*",
28
+ "!dist/**/*.spec.*"
29
+ ],
30
+ "peerDependencies": {
31
+ "svelte": "^4.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@marianmeres/icons-fns": "^2.0.2",
35
+ "@marianmeres/parse-boolean": "^1.1.7",
36
+ "@marianmeres/random-human-readable": "^1.6.1",
37
+ "@marianmeres/release": "^1.1.2",
38
+ "@sveltejs/adapter-auto": "^2.0.0",
39
+ "@sveltejs/kit": "^1.27.4",
40
+ "@sveltejs/package": "^2.0.0",
41
+ "autoprefixer": "^10.4.16",
42
+ "clsx": "^2.0.0",
43
+ "esm-env": "^1.0.0",
44
+ "postcss": "^8.4.32",
45
+ "prettier": "^3.0.0",
46
+ "prettier-plugin-svelte": "^3.0.0",
47
+ "publint": "^0.1.9",
48
+ "sass": "^1.69.5",
49
+ "svelte": "^4.2.7",
50
+ "svelte-check": "^3.6.0",
51
+ "tailwindcss": "^3.3.6",
52
+ "tslib": "^2.4.1",
53
+ "typescript": "^5.0.0",
54
+ "vite": "^4.4.2"
55
+ },
56
+ "svelte": "./dist/index.js",
57
+ "types": "./dist/index.d.ts",
58
+ "type": "module",
59
+ "dependencies": {
60
+ "@marianmeres/clog": "^1.0.1",
61
+ "@marianmeres/switch-store": "^1.3.1",
62
+ "tailwind-merge": "^2.1.0"
63
+ }
64
+ }