Skip to content

Commit

Permalink
breaking: Refactor global stores for Modal, Toast, and Drawer (#…
Browse files Browse the repository at this point in the history
…1831)

Co-authored-by: endigo9740 <gundamx9740@gmail.com>
  • Loading branch information
AdrianGonz97 and endigo9740 committed Aug 9, 2023
1 parent c812b36 commit 0b7add1
Show file tree
Hide file tree
Showing 27 changed files with 272 additions and 109 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-sheep-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": major
---

breaking: Refactored global stores to use contexts for `Toast`, `Drawer`, and `Modal` utilities
12 changes: 5 additions & 7 deletions packages/skeleton/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ export type { PopupSettings } from './utilities/Popup/types.js';
export type { Transition, TransitionParams } from './internal/transitions.js';
export type { CssClasses, SvelteEvent } from './types.js';

// Stores ---

// Utilities ---
export { storeHighlightJs } from './utilities/CodeBlock/stores.js';
export { storePopup } from './utilities/Popup/popup.js';
export { drawerStore } from './utilities/Drawer/stores.js';
export { modalStore } from './utilities/Modal/stores.js';
export { toastStore } from './utilities/Toast/stores.js';

// Utilities ---
export { getDrawerStore } from './utilities/Drawer/stores.js';
export { getModalStore } from './utilities/Modal/stores.js';
export { getToastStore } from './utilities/Toast/stores.js';
export { initializeStores } from './utilities/index.js';

// Lightswitch
export {
Expand Down
3 changes: 2 additions & 1 deletion packages/skeleton/src/lib/utilities/Drawer/Drawer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// Drawer Utils
import type { DrawerSettings } from './types.js';
import { drawerStore } from './stores.js';
import { getDrawerStore } from './stores.js';
import { fade, fly } from 'svelte/transition';
import { dynamicTransition } from '../../internal/transitions.js';
Expand Down Expand Up @@ -85,6 +85,7 @@
let elemBackdrop: HTMLElement;
let elemDrawer: HTMLElement;
let anim = { x: 0, y: 0 };
const drawerStore = getDrawerStore();
// Classes
const cBackdrop = 'fixed top-0 left-0 right-0 bottom-0 flex';
Expand Down
39 changes: 36 additions & 3 deletions packages/skeleton/src/lib/utilities/Drawer/stores.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
// Drawer Stores

import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
import type { DrawerSettings } from './types.js';

const DRAWER_STORE_KEY = 'drawerStore';

/**
* Retrieves the `drawerStore`.
*
* @example
* ```ts
* import { getDrawerStore } from "@skeletonlabs/skeleton";
*
* const drawerStore = getDrawerStore();
*
* drawerStore.open();
* ```
*/
export function getDrawerStore(): DrawerStore {
const drawerStore = getContext<DrawerStore | undefined>(DRAWER_STORE_KEY);

if (!drawerStore)
throw new Error(
'drawerStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!'
);

return drawerStore;
}

/**
* Initializes the `drawerStore`.
*/
export function initializeDrawerStore(): DrawerStore {
const drawerStore = drawerService();

return setContext(DRAWER_STORE_KEY, drawerStore);
}

type DrawerStore = ReturnType<typeof drawerService>;
function drawerService() {
const { subscribe, set, update } = writable<DrawerSettings>({});
return {
Expand All @@ -22,6 +58,3 @@ function drawerService() {
})
};
}

// Exports
export const drawerStore = drawerService();
4 changes: 3 additions & 1 deletion packages/skeleton/src/lib/utilities/Modal/Modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
// Types
import type { CssClasses, SvelteEvent } from '../../index.js';
import { modalStore } from './stores.js';
import { focusTrap } from '../../actions/FocusTrap/focusTrap.js';
import { getModalStore } from './stores.js';
import type { ModalComponent, ModalSettings } from './types.js';
// Props
Expand Down Expand Up @@ -116,6 +116,8 @@
let currentComponent: ModalComponent | undefined;
let registeredInteractionWithBackdrop = false;
const modalStore = getModalStore();
// Modal Store Subscription
modalStore.subscribe((modals: ModalSettings[]) => {
if (!modals.length) return;
Expand Down
4 changes: 3 additions & 1 deletion packages/skeleton/src/lib/utilities/Modal/Modal.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';

import { modalStore } from '$lib/utilities/Modal/stores.js';
import { getModalStore } from '$lib/utilities/Modal/stores.js';
import type { ModalSettings } from '$lib/utilities/Modal/types.js';

import Modal from '$lib/utilities/Modal/Modal.svelte';
Expand All @@ -27,6 +27,8 @@ const modalPrompt: ModalSettings = {
};

describe('Modal.svelte', () => {
const modalStore = getModalStore();

it('Renders modal alert', async () => {
modalStore.trigger(modalAlert);
const { getByTestId } = render(Modal);
Expand Down
38 changes: 36 additions & 2 deletions packages/skeleton/src/lib/utilities/Modal/stores.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
// Modal Store Queue

import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
import type { ModalSettings } from './types.js';

const MODAL_STORE_KEY = 'modalStore';

/**
* Retrieves the `modalStore`.
*
* @example
* ```ts
* import { getmodalStore } from "@skeletonlabs/skeleton";
*
* const modalStore = getModalStore();
*
* modalStore.trigger({ type: "alert", title: "Welcome!" });
* ```
*/
export function getModalStore(): ModalStore {
const modalStore = getContext<ModalStore | undefined>(MODAL_STORE_KEY);

if (!modalStore)
throw new Error(
'modalStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!'
);

return modalStore;
}

/**
* Initializes the `modalStore`.
*/
export function initializeModalStore(): ModalStore {
const modalStore = modalService();

return setContext(MODAL_STORE_KEY, modalStore);
}

type ModalStore = ReturnType<typeof modalService>;
function modalService() {
const { subscribe, set, update } = writable<ModalSettings[]>([]);
return {
Expand All @@ -25,5 +61,3 @@ function modalService() {
clear: () => set([])
};
}

export const modalStore = modalService();
3 changes: 2 additions & 1 deletion packages/skeleton/src/lib/utilities/Toast/Toast.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
import { flip } from 'svelte/animate';
// Stores
import { toastStore } from './stores.js';
import { getToastStore } from './stores.js';
const toastStore = getToastStore();
// Props
/** Set the toast position.
Expand Down
4 changes: 3 additions & 1 deletion packages/skeleton/src/lib/utilities/Toast/Toats.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';

import { toastStore } from '$lib/utilities/Toast/stores.js';
import { getToastStore } from '$lib/utilities/Toast/stores.js';
import type { ToastSettings } from './types.js';
import Toast from '$lib/utilities/Toast/Toast.svelte';

Expand All @@ -17,6 +17,8 @@ const toastMessage: ToastSettings = {
};

describe('Toast.svelte', () => {
const toastStore = getToastStore();

it('Renders modal alert', async () => {
toastStore.trigger(toastMessage);
const { getByTestId } = render(Toast);
Expand Down
97 changes: 67 additions & 30 deletions packages/skeleton/src/lib/utilities/Toast/stores.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,85 @@
// Toast Store Queue

import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
import type { ToastSettings, Toast } from './types.js';

const toastDefaults: ToastSettings = { message: 'Missing Toast Message', autohide: true, timeout: 5000 };

const TOAST_STORE_KEY = 'toastStore';

/**
* Retrieves the `toastStore`.
*
* @example
* ```ts
* import { getToastStore } from "@skeletonlabs/skeleton";
*
* const toastStore = getToastStore();
*
* toastStore.open({ message: "Welcome!" });
* ```
*/
export function getToastStore(): ToastStore {
const toastStore = getContext<ToastStore | undefined>(TOAST_STORE_KEY);

if (!toastStore)
throw new Error(
'toastStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!'
);

return toastStore;
}

/**
* Initializes the `toastStore`.
*/
export function initializeToastStore(): ToastStore {
const toastStore = toastService();

return setContext(TOAST_STORE_KEY, toastStore);
}

// Note for security; differentiates the queued toasts
function randomUUID(): string {
const random = Math.random();
return Number(random).toString(32);
}

// If toast should auto-hide, wait X time, then close by ID
function handleAutoHide(toast: Toast) {
if (toast.autohide === true) {
return setTimeout(() => {
toastStore.close(toast.id);
}, toast.timeout);
}
}

type ToastStore = ReturnType<typeof toastService>;
function toastService() {
const { subscribe, set, update } = writable<Toast[]>([]);

/** Remove toast in queue*/
const close = (id: string) =>
update((tStore) => {
if (tStore.length > 0) {
const index = tStore.findIndex((t) => t.id === id);
const selectedToast = tStore[index];
if (selectedToast) {
// Trigger Callback
if (selectedToast.callback) selectedToast.callback({ id, status: 'closed' });
// Clear timeout
if (selectedToast.timeoutId) clearTimeout(selectedToast.timeoutId);
// Remove
tStore.splice(index, 1);
}
}
return tStore;
});

// If toast should auto-hide, wait X time, then close by ID
function handleAutoHide(toast: Toast) {
if (toast.autohide === true) {
return setTimeout(() => {
close(toast.id);
}, toast.timeout);
}
}

return {
subscribe,
close,
/** Add a new toast to the queue. */
trigger: (toast: ToastSettings) => {
const id: string = randomUUID();
Expand All @@ -43,30 +99,13 @@ function toastService() {
});
return id;
},
/** Remove toast in queue*/
close: (id: string) =>
update((tStore) => {
if (tStore.length > 0) {
const index = tStore.findIndex((t) => t.id === id);
const selectedToast = tStore[index];
if (selectedToast) {
// Trigger Callback
if (selectedToast.callback) selectedToast.callback({ id, status: 'closed' });
// Clear timeout
if (selectedToast.timeoutId) clearTimeout(selectedToast.timeoutId);
// Remove
tStore.splice(index, 1);
}
}
return tStore;
}),
/** remain visible on hover */
/** Remain visible on hover */
freeze: (index: number) =>
update((tStore) => {
if (tStore.length > 0) clearTimeout(tStore[index].timeoutId);
return tStore;
}),
/** cancel remain visible on leave */
/** Cancel remain visible on leave */
unfreeze: (index: number) =>
update((tStore) => {
if (tStore.length > 0) tStore[index].timeoutId = handleAutoHide(tStore[index]);
Expand All @@ -76,5 +115,3 @@ function toastService() {
clear: () => set([])
};
}

export const toastStore = toastService();
26 changes: 26 additions & 0 deletions packages/skeleton/src/lib/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { initializeModalStore } from './Modal/stores.js';
import { initializeToastStore } from './Toast/stores.js';
import { initializeDrawerStore } from './Drawer/stores.js';

/**
* Used to initialize the stores for the `Modal`, `Toast`, and `Drawer` utilities.
*
* @example
* ```svelte
* <!-- App's root +layout.svelte -->
* <script>
* import { initializeStores, Toast, Modal, Drawer } from "@skeletonlabs/skeleton";
*
* initializeStores();
* </script>
*
* <Toast />
* <Modal />
* <Drawer />
* ```
*/
export function initializeStores() {
initializeModalStore();
initializeToastStore();
initializeDrawerStore();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
import DocsIcon from '$lib/components/DocsIcon/DocsIcon.svelte';
// Components & Utilities
import { AppBar, LightSwitch, popup, modalStore } from '@skeletonlabs/skeleton';
import { AppBar, LightSwitch, popup, getModalStore } from '@skeletonlabs/skeleton';
// Stores
import { drawerStore } from '@skeletonlabs/skeleton';
import { getDrawerStore } from '@skeletonlabs/skeleton';
import { storeTheme } from '$lib/stores/stores';
const drawerStore = getDrawerStore();
// Local
let isOsMac = false;
const modalStore = getModalStore();
// Set Search Keyboard Shortcut
if (browser) {
Expand Down
Loading

0 comments on commit 0b7add1

Please sign in to comment.