@douglasneuroinformatics/libui 4.6.0 → 4.7.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/dist/{chunk-LEIF262P.js → chunk-LUBCG4SG.js} +18 -9
- package/dist/chunk-LUBCG4SG.js.map +1 -0
- package/dist/{chunk-X2FQXQHP.js → chunk-QD5QDCYF.js} +2 -2
- package/dist/components.js +2 -2
- package/dist/hooks.d.ts +25 -2
- package/dist/hooks.js +1 -1
- package/dist/providers.js +44 -22
- package/dist/providers.js.map +1 -1
- package/package.json +1 -1
- package/src/hooks/useDestructiveAction/useDestructiveAction.test.ts +221 -0
- package/src/hooks/useDestructiveAction/useDestructiveAction.ts +35 -5
- package/src/hooks/useDestructiveAction/useDestructiveActionStore.test.ts +30 -7
- package/src/hooks/useDestructiveAction/useDestructiveActionStore.ts +23 -8
- package/src/providers/CoreProvider/CoreProvider.stories.tsx +52 -19
- package/src/providers/CoreProvider/DestructiveActionDialog.tsx +46 -23
- package/dist/chunk-LEIF262P.js.map +0 -1
- /package/dist/{chunk-X2FQXQHP.js.map → chunk-QD5QDCYF.js.map} +0 -0
|
@@ -28,26 +28,35 @@ import { useCallback } from "react";
|
|
|
28
28
|
// src/hooks/useDestructiveAction/useDestructiveActionStore.ts
|
|
29
29
|
import { create } from "zustand";
|
|
30
30
|
var useDestructiveActionStore = create((set) => ({
|
|
31
|
-
addPendingDestructiveAction: (action) => {
|
|
31
|
+
addPendingDestructiveAction: (action, options) => {
|
|
32
32
|
set((state) => ({
|
|
33
|
-
pendingDestructiveActions: [...state.pendingDestructiveActions, action]
|
|
33
|
+
pendingDestructiveActions: [...state.pendingDestructiveActions, { action, id: crypto.randomUUID(), ...options }]
|
|
34
34
|
}));
|
|
35
35
|
},
|
|
36
|
-
deletePendingDestructiveAction: (
|
|
36
|
+
deletePendingDestructiveAction: (id) => {
|
|
37
37
|
set((state) => ({
|
|
38
38
|
...state,
|
|
39
|
-
pendingDestructiveActions: state.pendingDestructiveActions.filter((
|
|
39
|
+
pendingDestructiveActions: state.pendingDestructiveActions.filter((def) => def.id !== id)
|
|
40
40
|
}));
|
|
41
41
|
},
|
|
42
42
|
pendingDestructiveActions: []
|
|
43
43
|
}));
|
|
44
44
|
|
|
45
45
|
// src/hooks/useDestructiveAction/useDestructiveAction.ts
|
|
46
|
-
function useDestructiveAction(
|
|
46
|
+
function useDestructiveAction(arg) {
|
|
47
47
|
const addPendingDestructiveAction = useDestructiveActionStore((store) => store.addPendingDestructiveAction);
|
|
48
|
-
return useCallback(
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
return useCallback(
|
|
49
|
+
(...args) => {
|
|
50
|
+
if (arg === void 0) {
|
|
51
|
+
const [action2, options2] = args;
|
|
52
|
+
addPendingDestructiveAction(action2, options2);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const { action, ...options } = typeof arg === "function" ? { action: arg } : arg;
|
|
56
|
+
addPendingDestructiveAction(() => action(...args), options);
|
|
57
|
+
},
|
|
58
|
+
[arg, addPendingDestructiveAction]
|
|
59
|
+
);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
// src/hooks/useDownload/useDownload.ts
|
|
@@ -402,4 +411,4 @@ export {
|
|
|
402
411
|
useTranslation,
|
|
403
412
|
useWindowSize
|
|
404
413
|
};
|
|
405
|
-
//# sourceMappingURL=chunk-
|
|
414
|
+
//# sourceMappingURL=chunk-LUBCG4SG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useChart/useChart.ts","../src/context/ChartContext.tsx","../src/hooks/useDestructiveAction/useDestructiveAction.ts","../src/hooks/useDestructiveAction/useDestructiveActionStore.ts","../src/hooks/useDownload/useDownload.ts","../src/hooks/useNotificationsStore/useNotificationsStore.ts","../src/hooks/useEventCallback/useEventCallback.ts","../src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts","../src/hooks/useEventListener/useEventListener.ts","../src/hooks/useInterval/useInterval.ts","../src/hooks/useMediaQuery/useMediaQuery.ts","../src/hooks/useOnClickOutside/useOnClickOutside.ts","../src/hooks/useStorage/useStorage.ts","../src/hooks/useStorage/useLocalStorage.ts","../src/hooks/useStorage/useSessionStorage.ts","../src/hooks/useTheme/useTheme.ts","../src/hooks/useTranslation/useTranslation.ts","../src/hooks/useWindowSize/useWindowSize.ts"],"sourcesContent":["import { useContext } from 'react';\n\nimport { ChartContext } from '@/context/ChartContext';\n\nexport function useChart() {\n const context = useContext(ChartContext);\n if (!context) {\n throw new Error('useChart must be used within a <ChartContainer />');\n }\n return context;\n}\n","import { createContext } from 'react';\n\nimport type { ChartConfig } from '@/components';\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nexport const ChartContext = createContext<ChartContextProps | null>(null);\n","import { useCallback } from 'react';\n\nimport { useDestructiveActionStore } from './useDestructiveActionStore';\n\nimport type { DestructiveAction, DestructiveActionOptions, DestructiveActionParams } from './useDestructiveActionStore';\n\n/**\n * Returns a function that accepts a destructive action and optional configuration.\n * @returns A function that takes an action and options to add to the destructive action queue\n */\nexport function useDestructiveAction(): (action: DestructiveAction, options?: DestructiveActionOptions) => void;\n/**\n * Returns a function that wraps the provided action for destructive confirmation.\n * @param action - The destructive action to wrap\n * @returns A function that takes the action's arguments and adds it to the destructive action queue\n */\nexport function useDestructiveAction<TArgs extends any[]>(action: DestructiveAction<TArgs>): (...args: TArgs) => void;\n/**\n * Returns a function that wraps the provided action with configuration for destructive confirmation.\n * @param params - The action and its configuration (title, description)\n * @returns A function that takes the action's arguments and adds it to the destructive action queue\n */\nexport function useDestructiveAction<TArgs extends any[]>(\n params: DestructiveActionParams<TArgs>\n): (...args: TArgs) => void;\nexport function useDestructiveAction<TArgs extends any[]>(\n arg?: DestructiveAction<TArgs> | DestructiveActionParams<TArgs>\n): (...args: TArgs) => void {\n const addPendingDestructiveAction = useDestructiveActionStore((store) => store.addPendingDestructiveAction);\n return useCallback(\n (...args: TArgs) => {\n if (arg === undefined) {\n const [action, options] = args as unknown as [DestructiveAction, DestructiveActionOptions | undefined];\n addPendingDestructiveAction(action, options);\n return;\n }\n const { action, ...options } = typeof arg === 'function' ? { action: arg } : arg;\n addPendingDestructiveAction(() => action(...args), options);\n },\n [arg, addPendingDestructiveAction]\n );\n}\n","import type { Promisable } from 'type-fest';\nimport { create } from 'zustand';\n\ntype DestructiveAction<TArgs extends any[] = any[]> = (...args: TArgs) => Promisable<void>;\n\ntype DestructiveActionOptions = {\n description?: string;\n title?: string;\n};\n\ntype DestructiveActionParams<TArgs extends any[] = any[]> = DestructiveActionOptions & {\n action: DestructiveAction<TArgs>;\n};\n\ntype DestructiveActionDef<TArgs extends any[] = any[]> = DestructiveActionParams<TArgs> & {\n id: string;\n};\n\nexport type DestructiveActionStore = {\n addPendingDestructiveAction: (action: DestructiveAction, options?: DestructiveActionOptions) => void;\n deletePendingDestructiveAction: (id: string) => void;\n pendingDestructiveActions: DestructiveActionDef[];\n};\n\nexport const useDestructiveActionStore = create<DestructiveActionStore>((set) => ({\n addPendingDestructiveAction: (action, options) => {\n set((state) => ({\n pendingDestructiveActions: [...state.pendingDestructiveActions, { action, id: crypto.randomUUID(), ...options }]\n }));\n },\n deletePendingDestructiveAction: (id) => {\n set((state) => ({\n ...state,\n pendingDestructiveActions: state.pendingDestructiveActions.filter((def) => def.id !== id)\n }));\n },\n pendingDestructiveActions: []\n}));\n\nexport type { DestructiveAction, DestructiveActionDef, DestructiveActionOptions, DestructiveActionParams };\n","import { useEffect, useState } from 'react';\n\nimport type { Promisable } from 'type-fest';\n\nimport { useNotificationsStore } from '../useNotificationsStore';\n\ntype DownloadTextOptions = {\n blobType: 'text/csv' | 'text/plain';\n};\n\ntype DownloadBlobOptions = {\n blobType: 'application/zip' | 'image/jpeg' | 'image/png' | 'image/webp';\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface DownloadFunction {\n (filename: string, data: Blob, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: () => Promisable<Blob>, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: string, options?: DownloadTextOptions): Promise<void>;\n (filename: string, data: () => Promisable<string>, options?: DownloadTextOptions): Promise<void>;\n}\n\ntype Downloadable = {\n blobType: string;\n data: Blob | string;\n filename: string;\n id: string;\n};\n\n/**\n * Used to trigger downloads of arbitrary data to the client\n * @returns A function to invoke the download\n */\nexport function useDownload(): DownloadFunction {\n const notifications = useNotificationsStore();\n const [downloads, setDownloads] = useState<Downloadable[]>([]);\n\n useEffect(() => {\n if (downloads.length) {\n const { blobType, data, filename, id } = downloads.at(-1)!;\n const anchor = document.createElement('a');\n document.body.appendChild(anchor);\n const blob = new Blob([data], { type: blobType });\n const url = URL.createObjectURL(blob);\n anchor.href = url;\n anchor.download = filename;\n anchor.click();\n URL.revokeObjectURL(url);\n anchor.remove();\n setDownloads((prevDownloads) => prevDownloads.filter((item) => item.id !== id));\n }\n }, [downloads]);\n\n return async (filename, _data, options) => {\n try {\n const data = typeof _data === 'function' ? await _data() : _data;\n if (typeof data !== 'string' && !options?.blobType) {\n throw new Error(\"argument 'blobType' must be defined when download is called with a Blob object\");\n }\n setDownloads((prevDownloads) => [\n ...prevDownloads,\n { blobType: options?.blobType ?? 'text/plain', data, filename, id: crypto.randomUUID() }\n ]);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n notifications.addNotification({\n message,\n title: 'Error',\n type: 'error'\n });\n }\n };\n}\n","import { create } from 'zustand';\n\nexport type NotificationInterface = {\n id: number;\n message?: string;\n title?: string;\n type: 'error' | 'info' | 'success' | 'warning';\n variant?: 'critical' | 'standard';\n};\n\nexport type NotificationsStore = {\n addNotification: (notification: Omit<NotificationInterface, 'id'>) => void;\n dismissNotification: (id: number) => void;\n notifications: NotificationInterface[];\n};\n\nexport const useNotificationsStore = create<NotificationsStore>((set) => ({\n addNotification: (notification) => {\n set((state) => ({\n notifications: [...state.notifications, { id: Date.now(), ...notification }]\n }));\n },\n dismissNotification: (id) => {\n set((state) => ({\n notifications: state.notifications.filter((notification) => notification.id !== id)\n }));\n },\n notifications: []\n}));\n","import { useCallback, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useEventCallback<Args extends unknown[], R>(fn: (...args: Args) => R) {\n const ref = useRef<typeof fn>(() => {\n throw new Error('Cannot call an event handler while rendering.');\n });\n\n useIsomorphicLayoutEffect(() => {\n ref.current = fn;\n }, [fn]);\n\n return useCallback((...args: Args) => ref.current(...args), [ref]);\n}\n","import { useEffect, useLayoutEffect } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nexport const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;\n","import { useEffect, useRef } from 'react';\nimport type { RefObject } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\n// MediaQueryList Event based useEventListener interface\nfunction useEventListener<K extends keyof MediaQueryListEventMap>(\n eventName: K,\n handler: (event: MediaQueryListEventMap[K]) => void,\n element: RefObject<MediaQueryList>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Window Event based useEventListener interface\nfunction useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n element?: undefined,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Element Event based useEventListener interface\nfunction useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Document Event based useEventListener interface\nfunction useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n element: RefObject<Document>,\n options?: AddEventListenerOptions | boolean\n): void;\n\nfunction useEventListener<\n KW extends keyof WindowEventMap,\n KH extends keyof HTMLElementEventMap,\n KM extends keyof MediaQueryListEventMap,\n T extends HTMLElement | MediaQueryList | void = void\n>(\n eventName: KH | KM | KW,\n handler: (event: Event | HTMLElementEventMap[KH] | MediaQueryListEventMap[KM] | WindowEventMap[KW]) => void,\n element?: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n) {\n // Create a ref that stores handler\n const savedHandler = useRef(handler);\n\n useIsomorphicLayoutEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(() => {\n // Define the listening target\n const targetElement: T | Window = element?.current ?? window;\n\n if (!(targetElement && targetElement.addEventListener)) return;\n\n // Create event listener that calls handler function stored in ref\n const listener: typeof handler = (event) => savedHandler.current(event);\n\n targetElement.addEventListener(eventName, listener, options);\n\n // Remove event listener on cleanup\n return () => {\n targetElement.removeEventListener(eventName, listener, options);\n };\n }, [eventName, element, options]);\n}\n\nexport { useEventListener };\n","import { useEffect, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useInterval(callback: () => void, delay: null | number) {\n const savedCallback = useRef(callback);\n\n // Remember the latest callback if it changes.\n useIsomorphicLayoutEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n // Set up the interval.\n useEffect(() => {\n // Don't schedule if no delay is specified.\n // Note: 0 is a valid value for delay.\n if (!delay && delay !== 0) {\n return;\n }\n\n const id = setInterval(() => savedCallback.current(), delay);\n\n return () => clearInterval(id);\n }, [delay]);\n}\n","import { useEffect, useState } from 'react';\n\nimport { isBrowser } from '@/utils';\n\n/**\n * Get the result of an arbitrary CSS media query\n *\n * @param query - the CSS media query\n * @returns a boolean indicating the result of the query\n * @example\n * // true if the viewport is at least 768px wide\n * const matches = useMediaQuery('(min-width: 768px)')\n */\nexport function useMediaQuery(query: string): boolean {\n const getMatches = (query: string): boolean => {\n // Prevents SSR issues\n if (isBrowser()) {\n return window.matchMedia(query).matches;\n }\n return false;\n };\n\n const [matches, setMatches] = useState<boolean>(getMatches(query));\n\n function handleChange() {\n setMatches(getMatches(query));\n }\n\n useEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n // Triggered at the first client-side load and if query changes\n handleChange();\n\n matchMedia.addEventListener('change', handleChange);\n\n return () => {\n matchMedia.removeEventListener('change', handleChange);\n };\n }, [query]);\n\n return matches;\n}\n","import type { RefObject } from 'react';\n\nimport { useEventListener } from '../useEventListener';\n\ntype Handler = (event: MouseEvent) => void;\n\nexport function useOnClickOutside<T extends HTMLElement | null = HTMLElement>(\n ref: RefObject<T>,\n handler: Handler,\n mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'\n): void {\n useEventListener(mouseEvent, (event) => {\n const el = ref.current;\n\n // Do nothing if clicking ref's element or descendent elements\n if (!el || el.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n });\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { Dispatch, SetStateAction } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nimport { useEventCallback } from '../useEventCallback';\nimport { useEventListener } from '../useEventListener';\n\ntype StorageName = 'localStorage' | 'sessionStorage';\n\ntype StorageEventMap = {\n [K in StorageName]: CustomEvent;\n};\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-object-type\n interface WindowEventMap extends StorageEventMap {}\n}\n\n/**\n * Represents the options for customizing the behavior of serialization and deserialization.\n * @template T - The type of the state to be stored in storage.\n */\ntype UseStorageOptions<T> = {\n /** A function to deserialize the stored value. */\n deserializer?: (value: string) => T;\n /**\n * If `true` (default), the hook will initialize reading the storage. In SSR, you should set it to `false`, returning the initial value initially.\n * @default true\n */\n initializeWithValue?: boolean;\n /** A function to serialize the value before storing it. */\n serializer?: (value: T) => string;\n};\n\n/**\n * Custom hook that uses local or session storage to persist state across page reloads.\n * @template T - The type of the state to be stored in storage.\n * @param key - The key under which the value will be stored in storage.\n * @param initialValue - The initial value of the state or a function that returns the initial value.\n * @param options - Options for customizing the behavior of serialization and deserialization (optional).\n * @returns A tuple containing the stored value and a function to set the value.\n * @public\n * @example\n * ```tsx\n * const [count, setCount] = useStorage('count', 0);\n * // Access the `count` value and the `setCount` function to update it.\n * ```\n */\nexport function useStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n storageName: StorageName,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n const { initializeWithValue = true } = options;\n const storage = window[storageName];\n\n const serializer = useCallback<(value: T) => string>(\n (value) => {\n if (options.serializer) {\n return options.serializer(value);\n }\n return JSON.stringify(value);\n },\n [options]\n );\n\n const deserializer = useCallback<(value: string) => T>(\n (value) => {\n if (options.deserializer) {\n return options.deserializer(value);\n } else if (value === 'undefined') {\n return undefined as unknown as T;\n }\n const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch (err) {\n console.error(`Error parsing JSON: ${(err as Error).message}`);\n return defaultValue;\n }\n return parsed as T;\n },\n [options, initialValue]\n );\n\n const readValue = useCallback((): T => {\n const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;\n if (!isBrowser()) {\n return initialValueToUse;\n }\n const raw = storage.getItem(key);\n return raw ? deserializer(raw) : initialValueToUse;\n }, [initialValue, key, deserializer]);\n\n const [storedValue, setStoredValue] = useState(() => {\n if (initializeWithValue) {\n return readValue();\n }\n return initialValue instanceof Function ? initialValue() : initialValue;\n });\n\n const setValue: Dispatch<SetStateAction<T>> = useEventCallback((value) => {\n if (!isBrowser()) {\n console.warn(`Tried setting storage key “${key}” even though environment is not a client`);\n }\n try {\n const newValue = value instanceof Function ? value(readValue()) : value;\n storage.setItem(key, serializer(newValue));\n setStoredValue(newValue);\n window.dispatchEvent(new StorageEvent(storageName, { key }));\n } catch (error) {\n console.warn(`Error setting storage key “${key}”:`, error);\n }\n });\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [key]);\n\n const handleStorageChange = useCallback(\n (event: CustomEvent | StorageEvent) => {\n if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {\n return;\n }\n setStoredValue(readValue());\n },\n [key, readValue]\n );\n\n // this only works for other documents, not the current one\n useEventListener('storage', handleStorageChange);\n\n useEventListener(storageName, handleStorageChange);\n\n return [storedValue, setValue];\n}\n\nexport type { StorageName, UseStorageOptions };\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses local storage to persist state across page reloads */\nexport function useLocalStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'localStorage', options);\n}\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses session storage to persist state across page reloads */\nexport function useSessionStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'sessionStorage', options);\n}\n","import { useEffect, useState } from 'react';\n\n// this is required since our storybook manager plugin cannot use vite aliases\nimport { isBrowser } from '../../utils';\n\ntype Theme = 'dark' | 'light';\n\ntype UpdateTheme = (theme: Theme) => void;\n\n/** @private */\nconst DEFAULT_THEME: Theme = 'light';\n\n/** @private */\nconst THEME_ATTRIBUTE = 'data-mode';\n\n/** @private */\nconst THEME_KEY = 'theme';\n\n/** @private */\nconst SYS_DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\n/**\n * Returns the current theme and a function to update the current theme\n *\n * The reason the implementation of this hook is rather convoluted is for\n * cases where the theme is updated outside this hook\n */\nfunction useTheme(): readonly [Theme, UpdateTheme] {\n // Initial theme value is based on the value saved in local storage or the system theme\n const [theme, setTheme] = useState<Theme>(() => {\n if (!isBrowser()) {\n return DEFAULT_THEME;\n }\n const savedTheme = window.localStorage.getItem(THEME_KEY);\n let initialTheme: Theme;\n if (savedTheme === 'dark' || savedTheme === 'light') {\n initialTheme = savedTheme;\n } else {\n initialTheme = window.matchMedia(SYS_DARK_MEDIA_QUERY).matches ? 'dark' : 'light';\n }\n document.documentElement.setAttribute(THEME_ATTRIBUTE, initialTheme);\n return initialTheme;\n });\n\n useEffect(() => {\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === THEME_ATTRIBUTE) {\n const updatedTheme = (mutation.target as HTMLHtmlElement).getAttribute(THEME_ATTRIBUTE);\n if (updatedTheme === 'light' || updatedTheme === 'dark') {\n window.localStorage.setItem(THEME_KEY, updatedTheme);\n setTheme(updatedTheme);\n } else {\n console.error(`Unexpected value for 'data-mode' attribute: ${updatedTheme}`);\n }\n }\n });\n });\n observer.observe(document.documentElement, {\n attributes: true\n });\n return () => observer.disconnect();\n }, []);\n\n // When the user wants to change the theme\n const updateTheme = (theme: Theme) => {\n document.documentElement.setAttribute(THEME_ATTRIBUTE, theme);\n };\n\n return [theme, updateTheme] as const;\n}\n\nexport { DEFAULT_THEME, SYS_DARK_MEDIA_QUERY, type Theme, THEME_ATTRIBUTE, THEME_KEY, useTheme };\n","import { useEffect, useMemo, useState } from 'react';\n\nimport type {\n TranslateFunction,\n TranslationKey,\n TranslationKeyForNamespace,\n TranslationNamespace,\n TranslatorType\n} from '@/i18n';\n\n// this is required since our storybook manager plugin cannot use vite aliases\nimport { i18n } from '../../i18n';\n\nexport function useTranslation(): TranslatorType<TranslationKey>;\nexport function useTranslation<TNamespace extends TranslationNamespace>(\n namespace: TNamespace\n): TranslatorType<TranslationKeyForNamespace<TNamespace>>;\nexport function useTranslation(namespace?: TranslationNamespace): TranslatorType<string> {\n const [resolvedLanguage, setResolvedLanguage] = useState(i18n.resolvedLanguage);\n const { changeLanguage, t } = useMemo(() => {\n const t: TranslateFunction<string> = (target, options) => {\n if (typeof target === 'object') {\n return i18n.t(target, options);\n }\n return i18n.t((namespace ? `${namespace}.${target}` : target) as TranslationKey, options);\n };\n return {\n changeLanguage: i18n.changeLanguage.bind(i18n),\n t\n };\n }, []);\n\n useEffect(() => {\n i18n.addEventListener('languageChange', setResolvedLanguage);\n return () => {\n i18n.removeEventListener('languageChange', setResolvedLanguage);\n };\n }, []);\n\n return {\n changeLanguage,\n resolvedLanguage,\n t\n };\n}\n","import { useState } from 'react';\n\nimport { useEventListener } from '../useEventListener';\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport type WindowSize = {\n height: number;\n width: number;\n};\n\nexport function useWindowSize(): WindowSize {\n const [windowSize, setWindowSize] = useState<WindowSize>({\n height: 0,\n width: 0\n });\n\n const handleSize = () => {\n setWindowSize({\n height: window.innerHeight,\n width: window.innerWidth\n });\n };\n\n useEventListener('resize', handleSize);\n\n // Set size at the first client-side load\n useIsomorphicLayoutEffect(() => {\n handleSize();\n }, []);\n\n return windowSize;\n}\n"],"mappings":"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","names":["action","options","create","useCallback","useEffect","useEffect","useCallback","useEffect","useRef","useRef","useEffect","useEffect","useRef","useRef","useEffect","useEffect","useState","query","useState","useEffect","useCallback","useEffect","useState","useCallback","useState","useEffect","useEffect","useState","useState","useEffect","theme","useEffect","useState","useState","t","useEffect","useState","useState"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
useNotificationsStore,
|
|
4
4
|
useTranslation
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-LUBCG4SG.js";
|
|
6
6
|
import {
|
|
7
7
|
cn
|
|
8
8
|
} from "./chunk-HCQE34RL.js";
|
|
@@ -322,4 +322,4 @@ export {
|
|
|
322
322
|
Dialog,
|
|
323
323
|
NotificationHub
|
|
324
324
|
};
|
|
325
|
-
//# sourceMappingURL=chunk-
|
|
325
|
+
//# sourceMappingURL=chunk-QD5QDCYF.js.map
|
package/dist/components.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
Dialog,
|
|
7
7
|
NotificationHub,
|
|
8
8
|
buttonVariants
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QD5QDCYF.js";
|
|
10
10
|
import {
|
|
11
11
|
ChartContext,
|
|
12
12
|
useChart,
|
|
13
13
|
useNotificationsStore,
|
|
14
14
|
useTheme,
|
|
15
15
|
useTranslation
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-LUBCG4SG.js";
|
|
17
17
|
import "./chunk-GT5NL2RJ.js";
|
|
18
18
|
import {
|
|
19
19
|
cn
|
package/dist/hooks.d.ts
CHANGED
|
@@ -9,9 +9,32 @@ declare function useChart(): {
|
|
|
9
9
|
config: ChartConfig;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
type DestructiveAction = () => Promisable<void>;
|
|
12
|
+
type DestructiveAction<TArgs extends any[] = any[]> = (...args: TArgs) => Promisable<void>;
|
|
13
|
+
type DestructiveActionOptions = {
|
|
14
|
+
description?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
};
|
|
17
|
+
type DestructiveActionParams<TArgs extends any[] = any[]> = DestructiveActionOptions & {
|
|
18
|
+
action: DestructiveAction<TArgs>;
|
|
19
|
+
};
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Returns a function that accepts a destructive action and optional configuration.
|
|
23
|
+
* @returns A function that takes an action and options to add to the destructive action queue
|
|
24
|
+
*/
|
|
25
|
+
declare function useDestructiveAction(): (action: DestructiveAction, options?: DestructiveActionOptions) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Returns a function that wraps the provided action for destructive confirmation.
|
|
28
|
+
* @param action - The destructive action to wrap
|
|
29
|
+
* @returns A function that takes the action's arguments and adds it to the destructive action queue
|
|
30
|
+
*/
|
|
31
|
+
declare function useDestructiveAction<TArgs extends any[]>(action: DestructiveAction<TArgs>): (...args: TArgs) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Returns a function that wraps the provided action with configuration for destructive confirmation.
|
|
34
|
+
* @param params - The action and its configuration (title, description)
|
|
35
|
+
* @returns A function that takes the action's arguments and adds it to the destructive action queue
|
|
36
|
+
*/
|
|
37
|
+
declare function useDestructiveAction<TArgs extends any[]>(params: DestructiveActionParams<TArgs>): (...args: TArgs) => void;
|
|
15
38
|
|
|
16
39
|
type DownloadTextOptions = {
|
|
17
40
|
blobType: 'text/csv' | 'text/plain';
|
package/dist/hooks.js
CHANGED
package/dist/providers.js
CHANGED
|
@@ -3,53 +3,75 @@ import {
|
|
|
3
3
|
Button,
|
|
4
4
|
Dialog,
|
|
5
5
|
NotificationHub
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QD5QDCYF.js";
|
|
7
7
|
import {
|
|
8
8
|
useDestructiveActionStore,
|
|
9
9
|
useTranslation
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LUBCG4SG.js";
|
|
11
11
|
import "./chunk-GT5NL2RJ.js";
|
|
12
12
|
import "./chunk-HCQE34RL.js";
|
|
13
13
|
import "./chunk-5B62SIBQ.js";
|
|
14
14
|
|
|
15
15
|
// src/providers/CoreProvider/DestructiveActionDialog.tsx
|
|
16
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
16
17
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
18
|
var DestructiveActionDialog = () => {
|
|
18
19
|
const deletePendingDestructiveAction = useDestructiveActionStore((store) => store.deletePendingDestructiveAction);
|
|
19
20
|
const pendingDestructiveActions = useDestructiveActionStore((store) => store.pendingDestructiveActions);
|
|
20
|
-
const { t } = useTranslation();
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const { resolvedLanguage, t } = useTranslation();
|
|
22
|
+
const current = useMemo(() => {
|
|
23
|
+
if (pendingDestructiveActions[0]) {
|
|
24
|
+
return pendingDestructiveActions[0];
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}, [pendingDestructiveActions]);
|
|
28
|
+
const getDisplay = useCallback(
|
|
29
|
+
(def) => {
|
|
30
|
+
const defaultDescription = t({
|
|
31
|
+
en: "This action cannot be reversed. Please confirm that you would like to continue.",
|
|
32
|
+
fr: "Cette action ne peut \xEAtre invers\xE9e. Veuillez confirmer que vous souhaitez poursuivre."
|
|
33
|
+
});
|
|
34
|
+
const defaultTitle = t({
|
|
35
|
+
en: "Confirm Action",
|
|
36
|
+
fr: "Confirmer l'action"
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
description: def?.description ?? defaultDescription,
|
|
40
|
+
title: def?.title ?? defaultTitle
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
[resolvedLanguage]
|
|
44
|
+
);
|
|
45
|
+
const [display, setDisplay] = useState(getDisplay(current));
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (current) {
|
|
48
|
+
setDisplay(getDisplay(current));
|
|
49
|
+
}
|
|
50
|
+
}, [current]);
|
|
23
51
|
const handleConfirm = async () => {
|
|
24
|
-
if (!
|
|
52
|
+
if (!current) {
|
|
25
53
|
return;
|
|
26
54
|
}
|
|
27
55
|
try {
|
|
28
|
-
await
|
|
56
|
+
await current.action();
|
|
29
57
|
} finally {
|
|
30
|
-
deletePendingDestructiveAction(
|
|
58
|
+
deletePendingDestructiveAction(current.id);
|
|
31
59
|
}
|
|
32
60
|
};
|
|
33
61
|
const handleCancel = () => {
|
|
34
|
-
if (
|
|
35
|
-
deletePendingDestructiveAction(
|
|
62
|
+
if (current) {
|
|
63
|
+
deletePendingDestructiveAction(current.id);
|
|
36
64
|
}
|
|
37
65
|
};
|
|
38
|
-
const handleOpenChange = (
|
|
39
|
-
if (!
|
|
40
|
-
deletePendingDestructiveAction(
|
|
66
|
+
const handleOpenChange = (isOpen) => {
|
|
67
|
+
if (!isOpen && current) {
|
|
68
|
+
deletePendingDestructiveAction(current.id);
|
|
41
69
|
}
|
|
42
70
|
};
|
|
43
|
-
return /* @__PURE__ */ jsx(Dialog, { open:
|
|
71
|
+
return /* @__PURE__ */ jsx(Dialog, { open: current !== null, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(Dialog.Content, { onOpenAutoFocus: (event) => event.preventDefault(), children: [
|
|
44
72
|
/* @__PURE__ */ jsxs(Dialog.Header, { children: [
|
|
45
|
-
/* @__PURE__ */ jsx(Dialog.Title, { children:
|
|
46
|
-
|
|
47
|
-
fr: "Confirmer l'action"
|
|
48
|
-
}) }),
|
|
49
|
-
/* @__PURE__ */ jsx(Dialog.Description, { children: t({
|
|
50
|
-
en: "This action cannot be reversed. Please confirm that you would like to continue.",
|
|
51
|
-
fr: "Cette action ne peut \xEAtre invers\xE9e. Veuillez confirmer que vous souhaitez poursuivre."
|
|
52
|
-
}) })
|
|
73
|
+
/* @__PURE__ */ jsx(Dialog.Title, { children: display.title }),
|
|
74
|
+
/* @__PURE__ */ jsx(Dialog.Description, { children: display.description })
|
|
53
75
|
] }),
|
|
54
76
|
/* @__PURE__ */ jsxs(Dialog.Footer, { children: [
|
|
55
77
|
/* @__PURE__ */ jsx(Button, { className: "min-w-16", type: "button", variant: "danger", onClick: () => void handleConfirm(), children: t("libui.yes") }),
|
package/dist/providers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/CoreProvider/DestructiveActionDialog.tsx","../src/providers/CoreProvider/CoreProvider.tsx"],"sourcesContent":["import { Button } from '@/components/Button';\nimport { Dialog } from '@/components/Dialog';\nimport { useDestructiveActionStore } from '@/hooks/useDestructiveAction/useDestructiveActionStore';\nimport { useTranslation } from '@/hooks/useTranslation';\n\nexport const DestructiveActionDialog = () => {\n const deletePendingDestructiveAction = useDestructiveActionStore((store) => store.deletePendingDestructiveAction);\n const pendingDestructiveActions = useDestructiveActionStore((store) => store.pendingDestructiveActions);\n const { t } = useTranslation();\n\n const
|
|
1
|
+
{"version":3,"sources":["../src/providers/CoreProvider/DestructiveActionDialog.tsx","../src/providers/CoreProvider/CoreProvider.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { Button } from '@/components/Button';\nimport { Dialog } from '@/components/Dialog';\nimport { useDestructiveActionStore } from '@/hooks/useDestructiveAction/useDestructiveActionStore';\nimport type { DestructiveActionDef } from '@/hooks/useDestructiveAction/useDestructiveActionStore';\nimport { useTranslation } from '@/hooks/useTranslation';\n\nexport const DestructiveActionDialog = () => {\n const deletePendingDestructiveAction = useDestructiveActionStore((store) => store.deletePendingDestructiveAction);\n const pendingDestructiveActions = useDestructiveActionStore((store) => store.pendingDestructiveActions);\n const { resolvedLanguage, t } = useTranslation();\n\n const current = useMemo<DestructiveActionDef | null>(() => {\n if (pendingDestructiveActions[0]) {\n return pendingDestructiveActions[0];\n }\n return null;\n }, [pendingDestructiveActions]);\n\n const getDisplay = useCallback(\n (def: DestructiveActionDef | null) => {\n const defaultDescription = t({\n en: 'This action cannot be reversed. Please confirm that you would like to continue.',\n fr: 'Cette action ne peut être inversée. Veuillez confirmer que vous souhaitez poursuivre.'\n });\n const defaultTitle = t({\n en: 'Confirm Action',\n fr: \"Confirmer l'action\"\n });\n return {\n description: def?.description ?? defaultDescription,\n title: def?.title ?? defaultTitle\n };\n },\n [resolvedLanguage]\n );\n\n const [display, setDisplay] = useState<{ description: string; title: string }>(getDisplay(current));\n\n useEffect(() => {\n if (current) {\n setDisplay(getDisplay(current));\n }\n }, [current]);\n\n const handleConfirm = async () => {\n if (!current) {\n return;\n }\n try {\n await current.action();\n } finally {\n deletePendingDestructiveAction(current.id);\n }\n };\n\n const handleCancel = () => {\n if (current) {\n deletePendingDestructiveAction(current.id);\n }\n };\n\n const handleOpenChange = (isOpen: boolean) => {\n if (!isOpen && current) {\n deletePendingDestructiveAction(current.id);\n }\n };\n\n return (\n <Dialog open={current !== null} onOpenChange={handleOpenChange}>\n <Dialog.Content onOpenAutoFocus={(event) => event.preventDefault()}>\n <Dialog.Header>\n <Dialog.Title>{display.title}</Dialog.Title>\n <Dialog.Description>{display.description}</Dialog.Description>\n </Dialog.Header>\n <Dialog.Footer>\n <Button className=\"min-w-16\" type=\"button\" variant=\"danger\" onClick={() => void handleConfirm()}>\n {t('libui.yes')}\n </Button>\n <Button className=\"min-w-16\" type=\"button\" variant=\"primary\" onClick={handleCancel}>\n {t('libui.no')}\n </Button>\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog>\n );\n};\n","import { DestructiveActionDialog } from './DestructiveActionDialog';\nimport { NotificationHub } from './NotificationHub';\n\nexport const CoreProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n return (\n <>\n <DestructiveActionDialog />\n <NotificationHub />\n {children}\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,aAAa,WAAW,SAAS,gBAAgB;AAwElD,SACE,KADF;AAhED,IAAM,0BAA0B,MAAM;AAC3C,QAAM,iCAAiC,0BAA0B,CAAC,UAAU,MAAM,8BAA8B;AAChH,QAAM,4BAA4B,0BAA0B,CAAC,UAAU,MAAM,yBAAyB;AACtG,QAAM,EAAE,kBAAkB,EAAE,IAAI,eAAe;AAE/C,QAAM,UAAU,QAAqC,MAAM;AACzD,QAAI,0BAA0B,CAAC,GAAG;AAChC,aAAO,0BAA0B,CAAC;AAAA,IACpC;AACA,WAAO;AAAA,EACT,GAAG,CAAC,yBAAyB,CAAC;AAE9B,QAAM,aAAa;AAAA,IACjB,CAAC,QAAqC;AACpC,YAAM,qBAAqB,EAAE;AAAA,QAC3B,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AACD,YAAM,eAAe,EAAE;AAAA,QACrB,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,CAAC;AACD,aAAO;AAAA,QACL,aAAa,KAAK,eAAe;AAAA,QACjC,OAAO,KAAK,SAAS;AAAA,MACvB;AAAA,IACF;AAAA,IACA,CAAC,gBAAgB;AAAA,EACnB;AAEA,QAAM,CAAC,SAAS,UAAU,IAAI,SAAiD,WAAW,OAAO,CAAC;AAElG,YAAU,MAAM;AACd,QAAI,SAAS;AACX,iBAAW,WAAW,OAAO,CAAC;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,gBAAgB,YAAY;AAChC,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AACA,QAAI;AACF,YAAM,QAAQ,OAAO;AAAA,IACvB,UAAE;AACA,qCAA+B,QAAQ,EAAE;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,eAAe,MAAM;AACzB,QAAI,SAAS;AACX,qCAA+B,QAAQ,EAAE;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,mBAAmB,CAAC,WAAoB;AAC5C,QAAI,CAAC,UAAU,SAAS;AACtB,qCAA+B,QAAQ,EAAE;AAAA,IAC3C;AAAA,EACF;AAEA,SACE,oBAAC,UAAO,MAAM,YAAY,MAAM,cAAc,kBAC5C,+BAAC,OAAO,SAAP,EAAe,iBAAiB,CAAC,UAAU,MAAM,eAAe,GAC/D;AAAA,yBAAC,OAAO,QAAP,EACC;AAAA,0BAAC,OAAO,OAAP,EAAc,kBAAQ,OAAM;AAAA,MAC7B,oBAAC,OAAO,aAAP,EAAoB,kBAAQ,aAAY;AAAA,OAC3C;AAAA,IACA,qBAAC,OAAO,QAAP,EACC;AAAA,0BAAC,UAAO,WAAU,YAAW,MAAK,UAAS,SAAQ,UAAS,SAAS,MAAM,KAAK,cAAc,GAC3F,YAAE,WAAW,GAChB;AAAA,MACA,oBAAC,UAAO,WAAU,YAAW,MAAK,UAAS,SAAQ,WAAU,SAAS,cACnE,YAAE,UAAU,GACf;AAAA,OACF;AAAA,KACF,GACF;AAEJ;;;AClFI,mBACE,OAAAA,MADF,QAAAC,aAAA;AAFG,IAAM,eAAwD,CAAC,EAAE,SAAS,MAAM;AACrF,SACE,gBAAAA,MAAA,YACE;AAAA,oBAAAD,KAAC,2BAAwB;AAAA,IACzB,gBAAAA,KAAC,mBAAgB;AAAA,IAChB;AAAA,KACH;AAEJ;","names":["jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.7.0",
|
|
5
5
|
"packageManager": "pnpm@10.7.1",
|
|
6
6
|
"description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
|
|
7
7
|
"author": "Joshua Unrau",
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { useDestructiveAction } from './useDestructiveAction';
|
|
5
|
+
import { useDestructiveActionStore } from './useDestructiveActionStore';
|
|
6
|
+
|
|
7
|
+
import type { DestructiveAction, DestructiveActionOptions, DestructiveActionParams } from './useDestructiveActionStore';
|
|
8
|
+
|
|
9
|
+
vi.mock('./useDestructiveActionStore', () => ({
|
|
10
|
+
useDestructiveActionStore: vi.fn()
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('useDestructiveAction', () => {
|
|
14
|
+
const mockAddPendingDestructiveAction = vi.fn();
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
(useDestructiveActionStore as any).mockImplementation((selector: any) => {
|
|
19
|
+
const store = {
|
|
20
|
+
addPendingDestructiveAction: mockAddPendingDestructiveAction
|
|
21
|
+
};
|
|
22
|
+
return selector(store);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('useDestructiveAction()', () => {
|
|
27
|
+
it('should return a function that accepts action and options', () => {
|
|
28
|
+
const { result } = renderHook(() => useDestructiveAction());
|
|
29
|
+
expect(typeof result.current).toBe('function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should call addPendingDestructiveAction with action only', () => {
|
|
33
|
+
const { result } = renderHook(() => useDestructiveAction());
|
|
34
|
+
const testAction: DestructiveAction = vi.fn();
|
|
35
|
+
|
|
36
|
+
act(() => {
|
|
37
|
+
result.current(testAction);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(testAction, undefined);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should call addPendingDestructiveAction with action and options', () => {
|
|
44
|
+
const { result } = renderHook(() => useDestructiveAction());
|
|
45
|
+
const testAction: DestructiveAction = vi.fn();
|
|
46
|
+
const options: DestructiveActionOptions = {
|
|
47
|
+
description: 'This is a test',
|
|
48
|
+
title: 'Test Action'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
act(() => {
|
|
52
|
+
result.current(testAction, options);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(testAction, options);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('useDestructiveAction(action)', () => {
|
|
60
|
+
it('should return a function that calls the action with provided args', () => {
|
|
61
|
+
const testAction = vi.fn();
|
|
62
|
+
const { result } = renderHook(() => useDestructiveAction(testAction));
|
|
63
|
+
|
|
64
|
+
const arg1 = 'test';
|
|
65
|
+
const arg2 = 123;
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
result.current(arg1, arg2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(expect.any(Function), {});
|
|
72
|
+
|
|
73
|
+
// Test that the wrapped function calls the original action with args
|
|
74
|
+
const wrappedAction = mockAddPendingDestructiveAction.mock.calls[0]![0];
|
|
75
|
+
wrappedAction();
|
|
76
|
+
expect(testAction).toHaveBeenCalledWith(arg1, arg2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should work with no arguments', () => {
|
|
80
|
+
const testAction = vi.fn();
|
|
81
|
+
const { result } = renderHook(() => useDestructiveAction(testAction));
|
|
82
|
+
|
|
83
|
+
act(() => {
|
|
84
|
+
result.current();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(expect.any(Function), {});
|
|
88
|
+
|
|
89
|
+
const wrappedAction = mockAddPendingDestructiveAction.mock.calls[0]![0];
|
|
90
|
+
wrappedAction();
|
|
91
|
+
expect(testAction).toHaveBeenCalledWith();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('useDestructiveAction(params)', () => {
|
|
96
|
+
it('should return a function that calls the action with provided args and passes options', () => {
|
|
97
|
+
const testAction = vi.fn();
|
|
98
|
+
const params: DestructiveActionParams<[string, number]> = {
|
|
99
|
+
action: testAction,
|
|
100
|
+
description: 'This is a test',
|
|
101
|
+
title: 'Test Action'
|
|
102
|
+
};
|
|
103
|
+
const { result } = renderHook(() => useDestructiveAction(params));
|
|
104
|
+
|
|
105
|
+
const arg1 = 'test';
|
|
106
|
+
const arg2 = 123;
|
|
107
|
+
|
|
108
|
+
act(() => {
|
|
109
|
+
result.current(arg1, arg2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(expect.any(Function), {
|
|
113
|
+
description: 'This is a test',
|
|
114
|
+
title: 'Test Action'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// test that the wrapped function calls the original action with args
|
|
118
|
+
const wrappedAction = mockAddPendingDestructiveAction.mock.calls[0]![0];
|
|
119
|
+
wrappedAction();
|
|
120
|
+
expect(testAction).toHaveBeenCalledWith(arg1, arg2);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should work with only action in params', () => {
|
|
124
|
+
const testAction = vi.fn();
|
|
125
|
+
const params: DestructiveActionParams<[]> = {
|
|
126
|
+
action: testAction
|
|
127
|
+
};
|
|
128
|
+
const { result } = renderHook(() => useDestructiveAction(params));
|
|
129
|
+
|
|
130
|
+
act(() => {
|
|
131
|
+
result.current();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(expect.any(Function), {});
|
|
135
|
+
|
|
136
|
+
const wrappedAction = mockAddPendingDestructiveAction.mock.calls[0]![0];
|
|
137
|
+
wrappedAction();
|
|
138
|
+
expect(testAction).toHaveBeenCalledWith();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should work with partial options in params', () => {
|
|
142
|
+
const testAction = vi.fn();
|
|
143
|
+
const params: DestructiveActionParams<[string]> = {
|
|
144
|
+
action: testAction,
|
|
145
|
+
title: 'Only Title'
|
|
146
|
+
};
|
|
147
|
+
const { result } = renderHook(() => useDestructiveAction(params));
|
|
148
|
+
|
|
149
|
+
act(() => {
|
|
150
|
+
result.current('test');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(mockAddPendingDestructiveAction).toHaveBeenCalledWith(expect.any(Function), {
|
|
154
|
+
title: 'Only Title'
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('callback stability', () => {
|
|
160
|
+
it('should return the same callback function when dependencies do not change', () => {
|
|
161
|
+
const testAction = vi.fn();
|
|
162
|
+
const { rerender, result } = renderHook(() => useDestructiveAction(testAction));
|
|
163
|
+
|
|
164
|
+
const firstCallback = result.current;
|
|
165
|
+
rerender();
|
|
166
|
+
const secondCallback = result.current;
|
|
167
|
+
|
|
168
|
+
expect(firstCallback).toBe(secondCallback);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should return a new callback function when action changes', () => {
|
|
172
|
+
const testAction1 = vi.fn();
|
|
173
|
+
const testAction2 = vi.fn();
|
|
174
|
+
let action = testAction1;
|
|
175
|
+
|
|
176
|
+
const { rerender, result } = renderHook(() => useDestructiveAction(action));
|
|
177
|
+
|
|
178
|
+
const firstCallback = result.current;
|
|
179
|
+
|
|
180
|
+
action = testAction2;
|
|
181
|
+
rerender();
|
|
182
|
+
|
|
183
|
+
const secondCallback = result.current;
|
|
184
|
+
|
|
185
|
+
expect(firstCallback).not.toBe(secondCallback);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should return a new callback function when params change', () => {
|
|
189
|
+
const testAction = vi.fn();
|
|
190
|
+
let params: DestructiveActionParams<[]> = { action: testAction, title: 'First' };
|
|
191
|
+
|
|
192
|
+
const { rerender, result } = renderHook(() => useDestructiveAction(params));
|
|
193
|
+
|
|
194
|
+
const firstCallback = result.current;
|
|
195
|
+
|
|
196
|
+
params = { action: testAction, title: 'Second' };
|
|
197
|
+
rerender();
|
|
198
|
+
|
|
199
|
+
const secondCallback = result.current;
|
|
200
|
+
|
|
201
|
+
expect(firstCallback).not.toBe(secondCallback);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('store integration', () => {
|
|
206
|
+
it('should call useDestructiveActionStore with correct selector', () => {
|
|
207
|
+
renderHook(() => useDestructiveAction());
|
|
208
|
+
|
|
209
|
+
expect(useDestructiveActionStore).toHaveBeenCalledWith(expect.any(Function));
|
|
210
|
+
|
|
211
|
+
// Test the selector function
|
|
212
|
+
const selector = (useDestructiveActionStore as any).mock.calls[0][0];
|
|
213
|
+
const mockStore = {
|
|
214
|
+
addPendingDestructiveAction: mockAddPendingDestructiveAction,
|
|
215
|
+
otherProperty: 'should not be selected'
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
expect(selector(mockStore)).toBe(mockAddPendingDestructiveAction);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -2,11 +2,41 @@ import { useCallback } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { useDestructiveActionStore } from './useDestructiveActionStore';
|
|
4
4
|
|
|
5
|
-
import type { DestructiveAction } from './useDestructiveActionStore';
|
|
5
|
+
import type { DestructiveAction, DestructiveActionOptions, DestructiveActionParams } from './useDestructiveActionStore';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Returns a function that accepts a destructive action and optional configuration.
|
|
9
|
+
* @returns A function that takes an action and options to add to the destructive action queue
|
|
10
|
+
*/
|
|
11
|
+
export function useDestructiveAction(): (action: DestructiveAction, options?: DestructiveActionOptions) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Returns a function that wraps the provided action for destructive confirmation.
|
|
14
|
+
* @param action - The destructive action to wrap
|
|
15
|
+
* @returns A function that takes the action's arguments and adds it to the destructive action queue
|
|
16
|
+
*/
|
|
17
|
+
export function useDestructiveAction<TArgs extends any[]>(action: DestructiveAction<TArgs>): (...args: TArgs) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Returns a function that wraps the provided action with configuration for destructive confirmation.
|
|
20
|
+
* @param params - The action and its configuration (title, description)
|
|
21
|
+
* @returns A function that takes the action's arguments and adds it to the destructive action queue
|
|
22
|
+
*/
|
|
23
|
+
export function useDestructiveAction<TArgs extends any[]>(
|
|
24
|
+
params: DestructiveActionParams<TArgs>
|
|
25
|
+
): (...args: TArgs) => void;
|
|
26
|
+
export function useDestructiveAction<TArgs extends any[]>(
|
|
27
|
+
arg?: DestructiveAction<TArgs> | DestructiveActionParams<TArgs>
|
|
28
|
+
): (...args: TArgs) => void {
|
|
8
29
|
const addPendingDestructiveAction = useDestructiveActionStore((store) => store.addPendingDestructiveAction);
|
|
9
|
-
return useCallback(
|
|
10
|
-
|
|
11
|
-
|
|
30
|
+
return useCallback(
|
|
31
|
+
(...args: TArgs) => {
|
|
32
|
+
if (arg === undefined) {
|
|
33
|
+
const [action, options] = args as unknown as [DestructiveAction, DestructiveActionOptions | undefined];
|
|
34
|
+
addPendingDestructiveAction(action, options);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const { action, ...options } = typeof arg === 'function' ? { action: arg } : arg;
|
|
38
|
+
addPendingDestructiveAction(() => action(...args), options);
|
|
39
|
+
},
|
|
40
|
+
[arg, addPendingDestructiveAction]
|
|
41
|
+
);
|
|
12
42
|
}
|
|
@@ -4,7 +4,7 @@ import * as zustand from 'zustand';
|
|
|
4
4
|
|
|
5
5
|
import { useDestructiveActionStore } from './useDestructiveActionStore';
|
|
6
6
|
|
|
7
|
-
import type { DestructiveAction } from './useDestructiveActionStore';
|
|
7
|
+
import type { DestructiveAction, DestructiveActionOptions } from './useDestructiveActionStore';
|
|
8
8
|
|
|
9
9
|
describe('useDestructiveActionStore', () => {
|
|
10
10
|
beforeAll(() => {
|
|
@@ -32,7 +32,9 @@ describe('useDestructiveActionStore', () => {
|
|
|
32
32
|
act(() => {
|
|
33
33
|
result.current.addPendingDestructiveAction(testAction);
|
|
34
34
|
});
|
|
35
|
-
expect(result.current.pendingDestructiveActions).
|
|
35
|
+
expect(result.current.pendingDestructiveActions).toHaveLength(1);
|
|
36
|
+
expect(result.current.pendingDestructiveActions[0]!.action).toBe(testAction);
|
|
37
|
+
expect(result.current.pendingDestructiveActions[0]!.id).toBeDefined();
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
it('should add multiple actions to the array', () => {
|
|
@@ -43,7 +45,25 @@ describe('useDestructiveActionStore', () => {
|
|
|
43
45
|
result.current.addPendingDestructiveAction(testAction1);
|
|
44
46
|
result.current.addPendingDestructiveAction(testAction2);
|
|
45
47
|
});
|
|
46
|
-
expect(result.current.pendingDestructiveActions).
|
|
48
|
+
expect(result.current.pendingDestructiveActions).toHaveLength(2);
|
|
49
|
+
expect(result.current.pendingDestructiveActions[0]!.action).toBe(testAction1);
|
|
50
|
+
expect(result.current.pendingDestructiveActions[1]!.action).toBe(testAction2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should add action with options', () => {
|
|
54
|
+
const { result } = renderHook(() => useDestructiveActionStore());
|
|
55
|
+
const testAction: DestructiveAction = vi.fn();
|
|
56
|
+
const options: DestructiveActionOptions = {
|
|
57
|
+
description: 'This is a test action',
|
|
58
|
+
title: 'Test Action'
|
|
59
|
+
};
|
|
60
|
+
act(() => {
|
|
61
|
+
result.current.addPendingDestructiveAction(testAction, options);
|
|
62
|
+
});
|
|
63
|
+
expect(result.current.pendingDestructiveActions).toHaveLength(1);
|
|
64
|
+
expect(result.current.pendingDestructiveActions[0]!.action).toBe(testAction);
|
|
65
|
+
expect(result.current.pendingDestructiveActions[0]!.title).toBe('Test Action');
|
|
66
|
+
expect(result.current.pendingDestructiveActions[0]!.description).toBe('This is a test action');
|
|
47
67
|
});
|
|
48
68
|
});
|
|
49
69
|
|
|
@@ -60,18 +80,21 @@ describe('useDestructiveActionStore', () => {
|
|
|
60
80
|
result.current.addPendingDestructiveAction(testAction3);
|
|
61
81
|
});
|
|
62
82
|
|
|
83
|
+
const idToDelete = result.current.pendingDestructiveActions[1]!.id;
|
|
84
|
+
|
|
63
85
|
act(() => {
|
|
64
|
-
result.current.deletePendingDestructiveAction(
|
|
86
|
+
result.current.deletePendingDestructiveAction(idToDelete);
|
|
65
87
|
});
|
|
66
88
|
|
|
67
|
-
expect(result.current.pendingDestructiveActions).
|
|
89
|
+
expect(result.current.pendingDestructiveActions).toHaveLength(2);
|
|
90
|
+
expect(result.current.pendingDestructiveActions[0]!.action).toBe(testAction1);
|
|
91
|
+
expect(result.current.pendingDestructiveActions[1]!.action).toBe(testAction3);
|
|
68
92
|
});
|
|
69
93
|
|
|
70
94
|
it('should handle removing from empty array', () => {
|
|
71
95
|
const { result } = renderHook(() => useDestructiveActionStore());
|
|
72
|
-
const testAction: DestructiveAction = vi.fn();
|
|
73
96
|
act(() => {
|
|
74
|
-
result.current.deletePendingDestructiveAction(
|
|
97
|
+
result.current.deletePendingDestructiveAction('non-existent-id');
|
|
75
98
|
});
|
|
76
99
|
expect(result.current.pendingDestructiveActions).toEqual([]);
|
|
77
100
|
});
|
|
@@ -1,25 +1,40 @@
|
|
|
1
1
|
import type { Promisable } from 'type-fest';
|
|
2
2
|
import { create } from 'zustand';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type DestructiveAction<TArgs extends any[] = any[]> = (...args: TArgs) => Promisable<void>;
|
|
5
|
+
|
|
6
|
+
type DestructiveActionOptions = {
|
|
7
|
+
description?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type DestructiveActionParams<TArgs extends any[] = any[]> = DestructiveActionOptions & {
|
|
12
|
+
action: DestructiveAction<TArgs>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type DestructiveActionDef<TArgs extends any[] = any[]> = DestructiveActionParams<TArgs> & {
|
|
16
|
+
id: string;
|
|
17
|
+
};
|
|
5
18
|
|
|
6
19
|
export type DestructiveActionStore = {
|
|
7
|
-
addPendingDestructiveAction: (action: DestructiveAction) => void;
|
|
8
|
-
deletePendingDestructiveAction: (
|
|
9
|
-
pendingDestructiveActions:
|
|
20
|
+
addPendingDestructiveAction: (action: DestructiveAction, options?: DestructiveActionOptions) => void;
|
|
21
|
+
deletePendingDestructiveAction: (id: string) => void;
|
|
22
|
+
pendingDestructiveActions: DestructiveActionDef[];
|
|
10
23
|
};
|
|
11
24
|
|
|
12
25
|
export const useDestructiveActionStore = create<DestructiveActionStore>((set) => ({
|
|
13
|
-
addPendingDestructiveAction: (action) => {
|
|
26
|
+
addPendingDestructiveAction: (action, options) => {
|
|
14
27
|
set((state) => ({
|
|
15
|
-
pendingDestructiveActions: [...state.pendingDestructiveActions, action]
|
|
28
|
+
pendingDestructiveActions: [...state.pendingDestructiveActions, { action, id: crypto.randomUUID(), ...options }]
|
|
16
29
|
}));
|
|
17
30
|
},
|
|
18
|
-
deletePendingDestructiveAction: (
|
|
31
|
+
deletePendingDestructiveAction: (id) => {
|
|
19
32
|
set((state) => ({
|
|
20
33
|
...state,
|
|
21
|
-
pendingDestructiveActions: state.pendingDestructiveActions.filter((
|
|
34
|
+
pendingDestructiveActions: state.pendingDestructiveActions.filter((def) => def.id !== id)
|
|
22
35
|
}));
|
|
23
36
|
},
|
|
24
37
|
pendingDestructiveActions: []
|
|
25
38
|
}));
|
|
39
|
+
|
|
40
|
+
export type { DestructiveAction, DestructiveActionDef, DestructiveActionOptions, DestructiveActionParams };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
1
3
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
4
|
|
|
5
|
+
import { Input, Label } from '@/components';
|
|
3
6
|
import { Button } from '@/components/Button';
|
|
4
7
|
import { useDestructiveAction } from '@/hooks/useDestructiveAction';
|
|
5
8
|
import { useNotificationsStore } from '@/hooks/useNotificationsStore';
|
|
@@ -8,24 +11,47 @@ import { CoreProvider } from './CoreProvider';
|
|
|
8
11
|
|
|
9
12
|
type Story = StoryObj<typeof CoreProvider>;
|
|
10
13
|
|
|
11
|
-
const
|
|
14
|
+
const NotificationsChildren = () => {
|
|
12
15
|
const addNotification = useNotificationsStore((store) => store.addNotification);
|
|
13
|
-
|
|
16
|
+
|
|
14
17
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
<Button
|
|
19
|
+
type="button"
|
|
20
|
+
onClick={() => {
|
|
21
|
+
addNotification({
|
|
22
|
+
message: 'Hello World',
|
|
23
|
+
type: 'info'
|
|
24
|
+
});
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
Add Notification
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DestructiveActionsChildren = () => {
|
|
33
|
+
const [title, setTitle] = useState('');
|
|
34
|
+
const [description, setDescription] = useState('');
|
|
35
|
+
|
|
36
|
+
const destructiveAction = useDestructiveAction({
|
|
37
|
+
action: (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
38
|
+
alert(`Delete at Event Time: ${event.timeStamp}`);
|
|
39
|
+
},
|
|
40
|
+
description: description || undefined,
|
|
41
|
+
title: title || undefined
|
|
42
|
+
});
|
|
27
43
|
|
|
28
|
-
|
|
44
|
+
return (
|
|
45
|
+
<div className="flex flex-col gap-6">
|
|
46
|
+
<div className="flex flex-col gap-2">
|
|
47
|
+
<Label>Title</Label>
|
|
48
|
+
<Input value={title} onChange={(event) => setTitle(event.target.value)} />
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex flex-col gap-2">
|
|
51
|
+
<Label>Description</Label>
|
|
52
|
+
<Input value={description} onChange={(event) => setDescription(event.target.value)} />
|
|
53
|
+
</div>
|
|
54
|
+
<Button className="w-min" type="button" variant="danger" onClick={destructiveAction}>
|
|
29
55
|
Add Action
|
|
30
56
|
</Button>
|
|
31
57
|
</div>
|
|
@@ -33,12 +59,19 @@ const Children = () => {
|
|
|
33
59
|
};
|
|
34
60
|
|
|
35
61
|
const meta: Meta<typeof CoreProvider> = {
|
|
36
|
-
args: {
|
|
37
|
-
children: <Children />
|
|
38
|
-
},
|
|
39
62
|
component: CoreProvider
|
|
40
63
|
};
|
|
41
64
|
|
|
42
65
|
export default meta;
|
|
43
66
|
|
|
44
|
-
export const
|
|
67
|
+
export const Notifications: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
children: <NotificationsChildren />
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const DescructiveActions: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
children: <DestructiveActionsChildren />
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -1,55 +1,78 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
|
|
1
3
|
import { Button } from '@/components/Button';
|
|
2
4
|
import { Dialog } from '@/components/Dialog';
|
|
3
5
|
import { useDestructiveActionStore } from '@/hooks/useDestructiveAction/useDestructiveActionStore';
|
|
6
|
+
import type { DestructiveActionDef } from '@/hooks/useDestructiveAction/useDestructiveActionStore';
|
|
4
7
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
5
8
|
|
|
6
9
|
export const DestructiveActionDialog = () => {
|
|
7
10
|
const deletePendingDestructiveAction = useDestructiveActionStore((store) => store.deletePendingDestructiveAction);
|
|
8
11
|
const pendingDestructiveActions = useDestructiveActionStore((store) => store.pendingDestructiveActions);
|
|
9
|
-
const { t } = useTranslation();
|
|
12
|
+
const { resolvedLanguage, t } = useTranslation();
|
|
13
|
+
|
|
14
|
+
const current = useMemo<DestructiveActionDef | null>(() => {
|
|
15
|
+
if (pendingDestructiveActions[0]) {
|
|
16
|
+
return pendingDestructiveActions[0];
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}, [pendingDestructiveActions]);
|
|
10
20
|
|
|
11
|
-
const
|
|
12
|
-
|
|
21
|
+
const getDisplay = useCallback(
|
|
22
|
+
(def: DestructiveActionDef | null) => {
|
|
23
|
+
const defaultDescription = t({
|
|
24
|
+
en: 'This action cannot be reversed. Please confirm that you would like to continue.',
|
|
25
|
+
fr: 'Cette action ne peut être inversée. Veuillez confirmer que vous souhaitez poursuivre.'
|
|
26
|
+
});
|
|
27
|
+
const defaultTitle = t({
|
|
28
|
+
en: 'Confirm Action',
|
|
29
|
+
fr: "Confirmer l'action"
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
description: def?.description ?? defaultDescription,
|
|
33
|
+
title: def?.title ?? defaultTitle
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
[resolvedLanguage]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const [display, setDisplay] = useState<{ description: string; title: string }>(getDisplay(current));
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (current) {
|
|
43
|
+
setDisplay(getDisplay(current));
|
|
44
|
+
}
|
|
45
|
+
}, [current]);
|
|
13
46
|
|
|
14
47
|
const handleConfirm = async () => {
|
|
15
|
-
if (!
|
|
48
|
+
if (!current) {
|
|
16
49
|
return;
|
|
17
50
|
}
|
|
18
51
|
try {
|
|
19
|
-
await
|
|
52
|
+
await current.action();
|
|
20
53
|
} finally {
|
|
21
|
-
deletePendingDestructiveAction(
|
|
54
|
+
deletePendingDestructiveAction(current.id);
|
|
22
55
|
}
|
|
23
56
|
};
|
|
24
57
|
|
|
25
58
|
const handleCancel = () => {
|
|
26
|
-
if (
|
|
27
|
-
deletePendingDestructiveAction(
|
|
59
|
+
if (current) {
|
|
60
|
+
deletePendingDestructiveAction(current.id);
|
|
28
61
|
}
|
|
29
62
|
};
|
|
30
63
|
|
|
31
64
|
const handleOpenChange = (isOpen: boolean) => {
|
|
32
|
-
if (!isOpen &&
|
|
33
|
-
deletePendingDestructiveAction(
|
|
65
|
+
if (!isOpen && current) {
|
|
66
|
+
deletePendingDestructiveAction(current.id);
|
|
34
67
|
}
|
|
35
68
|
};
|
|
36
69
|
|
|
37
70
|
return (
|
|
38
|
-
<Dialog open={
|
|
71
|
+
<Dialog open={current !== null} onOpenChange={handleOpenChange}>
|
|
39
72
|
<Dialog.Content onOpenAutoFocus={(event) => event.preventDefault()}>
|
|
40
73
|
<Dialog.Header>
|
|
41
|
-
<Dialog.Title>
|
|
42
|
-
|
|
43
|
-
en: 'Confirm Action',
|
|
44
|
-
fr: "Confirmer l'action"
|
|
45
|
-
})}
|
|
46
|
-
</Dialog.Title>
|
|
47
|
-
<Dialog.Description>
|
|
48
|
-
{t({
|
|
49
|
-
en: 'This action cannot be reversed. Please confirm that you would like to continue.',
|
|
50
|
-
fr: 'Cette action ne peut être inversée. Veuillez confirmer que vous souhaitez poursuivre.'
|
|
51
|
-
})}
|
|
52
|
-
</Dialog.Description>
|
|
74
|
+
<Dialog.Title>{display.title}</Dialog.Title>
|
|
75
|
+
<Dialog.Description>{display.description}</Dialog.Description>
|
|
53
76
|
</Dialog.Header>
|
|
54
77
|
<Dialog.Footer>
|
|
55
78
|
<Button className="min-w-16" type="button" variant="danger" onClick={() => void handleConfirm()}>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useChart/useChart.ts","../src/context/ChartContext.tsx","../src/hooks/useDestructiveAction/useDestructiveAction.ts","../src/hooks/useDestructiveAction/useDestructiveActionStore.ts","../src/hooks/useDownload/useDownload.ts","../src/hooks/useNotificationsStore/useNotificationsStore.ts","../src/hooks/useEventCallback/useEventCallback.ts","../src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts","../src/hooks/useEventListener/useEventListener.ts","../src/hooks/useInterval/useInterval.ts","../src/hooks/useMediaQuery/useMediaQuery.ts","../src/hooks/useOnClickOutside/useOnClickOutside.ts","../src/hooks/useStorage/useStorage.ts","../src/hooks/useStorage/useLocalStorage.ts","../src/hooks/useStorage/useSessionStorage.ts","../src/hooks/useTheme/useTheme.ts","../src/hooks/useTranslation/useTranslation.ts","../src/hooks/useWindowSize/useWindowSize.ts"],"sourcesContent":["import { useContext } from 'react';\n\nimport { ChartContext } from '@/context/ChartContext';\n\nexport function useChart() {\n const context = useContext(ChartContext);\n if (!context) {\n throw new Error('useChart must be used within a <ChartContainer />');\n }\n return context;\n}\n","import { createContext } from 'react';\n\nimport type { ChartConfig } from '@/components';\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nexport const ChartContext = createContext<ChartContextProps | null>(null);\n","import { useCallback } from 'react';\n\nimport { useDestructiveActionStore } from './useDestructiveActionStore';\n\nimport type { DestructiveAction } from './useDestructiveActionStore';\n\nexport function useDestructiveAction(destructiveAction: DestructiveAction) {\n const addPendingDestructiveAction = useDestructiveActionStore((store) => store.addPendingDestructiveAction);\n return useCallback(() => {\n addPendingDestructiveAction(destructiveAction);\n }, [destructiveAction, addPendingDestructiveAction]);\n}\n","import type { Promisable } from 'type-fest';\nimport { create } from 'zustand';\n\nexport type DestructiveAction = () => Promisable<void>;\n\nexport type DestructiveActionStore = {\n addPendingDestructiveAction: (action: DestructiveAction) => void;\n deletePendingDestructiveAction: (action: DestructiveAction) => void;\n pendingDestructiveActions: DestructiveAction[];\n};\n\nexport const useDestructiveActionStore = create<DestructiveActionStore>((set) => ({\n addPendingDestructiveAction: (action) => {\n set((state) => ({\n pendingDestructiveActions: [...state.pendingDestructiveActions, action]\n }));\n },\n deletePendingDestructiveAction: (action) => {\n set((state) => ({\n ...state,\n pendingDestructiveActions: state.pendingDestructiveActions.filter((_action) => _action !== action)\n }));\n },\n pendingDestructiveActions: []\n}));\n","import { useEffect, useState } from 'react';\n\nimport type { Promisable } from 'type-fest';\n\nimport { useNotificationsStore } from '../useNotificationsStore';\n\ntype DownloadTextOptions = {\n blobType: 'text/csv' | 'text/plain';\n};\n\ntype DownloadBlobOptions = {\n blobType: 'application/zip' | 'image/jpeg' | 'image/png' | 'image/webp';\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface DownloadFunction {\n (filename: string, data: Blob, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: () => Promisable<Blob>, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: string, options?: DownloadTextOptions): Promise<void>;\n (filename: string, data: () => Promisable<string>, options?: DownloadTextOptions): Promise<void>;\n}\n\ntype Downloadable = {\n blobType: string;\n data: Blob | string;\n filename: string;\n id: string;\n};\n\n/**\n * Used to trigger downloads of arbitrary data to the client\n * @returns A function to invoke the download\n */\nexport function useDownload(): DownloadFunction {\n const notifications = useNotificationsStore();\n const [downloads, setDownloads] = useState<Downloadable[]>([]);\n\n useEffect(() => {\n if (downloads.length) {\n const { blobType, data, filename, id } = downloads.at(-1)!;\n const anchor = document.createElement('a');\n document.body.appendChild(anchor);\n const blob = new Blob([data], { type: blobType });\n const url = URL.createObjectURL(blob);\n anchor.href = url;\n anchor.download = filename;\n anchor.click();\n URL.revokeObjectURL(url);\n anchor.remove();\n setDownloads((prevDownloads) => prevDownloads.filter((item) => item.id !== id));\n }\n }, [downloads]);\n\n return async (filename, _data, options) => {\n try {\n const data = typeof _data === 'function' ? await _data() : _data;\n if (typeof data !== 'string' && !options?.blobType) {\n throw new Error(\"argument 'blobType' must be defined when download is called with a Blob object\");\n }\n setDownloads((prevDownloads) => [\n ...prevDownloads,\n { blobType: options?.blobType ?? 'text/plain', data, filename, id: crypto.randomUUID() }\n ]);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n notifications.addNotification({\n message,\n title: 'Error',\n type: 'error'\n });\n }\n };\n}\n","import { create } from 'zustand';\n\nexport type NotificationInterface = {\n id: number;\n message?: string;\n title?: string;\n type: 'error' | 'info' | 'success' | 'warning';\n variant?: 'critical' | 'standard';\n};\n\nexport type NotificationsStore = {\n addNotification: (notification: Omit<NotificationInterface, 'id'>) => void;\n dismissNotification: (id: number) => void;\n notifications: NotificationInterface[];\n};\n\nexport const useNotificationsStore = create<NotificationsStore>((set) => ({\n addNotification: (notification) => {\n set((state) => ({\n notifications: [...state.notifications, { id: Date.now(), ...notification }]\n }));\n },\n dismissNotification: (id) => {\n set((state) => ({\n notifications: state.notifications.filter((notification) => notification.id !== id)\n }));\n },\n notifications: []\n}));\n","import { useCallback, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useEventCallback<Args extends unknown[], R>(fn: (...args: Args) => R) {\n const ref = useRef<typeof fn>(() => {\n throw new Error('Cannot call an event handler while rendering.');\n });\n\n useIsomorphicLayoutEffect(() => {\n ref.current = fn;\n }, [fn]);\n\n return useCallback((...args: Args) => ref.current(...args), [ref]);\n}\n","import { useEffect, useLayoutEffect } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nexport const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;\n","import { useEffect, useRef } from 'react';\nimport type { RefObject } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\n// MediaQueryList Event based useEventListener interface\nfunction useEventListener<K extends keyof MediaQueryListEventMap>(\n eventName: K,\n handler: (event: MediaQueryListEventMap[K]) => void,\n element: RefObject<MediaQueryList>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Window Event based useEventListener interface\nfunction useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n element?: undefined,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Element Event based useEventListener interface\nfunction useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Document Event based useEventListener interface\nfunction useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n element: RefObject<Document>,\n options?: AddEventListenerOptions | boolean\n): void;\n\nfunction useEventListener<\n KW extends keyof WindowEventMap,\n KH extends keyof HTMLElementEventMap,\n KM extends keyof MediaQueryListEventMap,\n T extends HTMLElement | MediaQueryList | void = void\n>(\n eventName: KH | KM | KW,\n handler: (event: Event | HTMLElementEventMap[KH] | MediaQueryListEventMap[KM] | WindowEventMap[KW]) => void,\n element?: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n) {\n // Create a ref that stores handler\n const savedHandler = useRef(handler);\n\n useIsomorphicLayoutEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(() => {\n // Define the listening target\n const targetElement: T | Window = element?.current ?? window;\n\n if (!(targetElement && targetElement.addEventListener)) return;\n\n // Create event listener that calls handler function stored in ref\n const listener: typeof handler = (event) => savedHandler.current(event);\n\n targetElement.addEventListener(eventName, listener, options);\n\n // Remove event listener on cleanup\n return () => {\n targetElement.removeEventListener(eventName, listener, options);\n };\n }, [eventName, element, options]);\n}\n\nexport { useEventListener };\n","import { useEffect, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useInterval(callback: () => void, delay: null | number) {\n const savedCallback = useRef(callback);\n\n // Remember the latest callback if it changes.\n useIsomorphicLayoutEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n // Set up the interval.\n useEffect(() => {\n // Don't schedule if no delay is specified.\n // Note: 0 is a valid value for delay.\n if (!delay && delay !== 0) {\n return;\n }\n\n const id = setInterval(() => savedCallback.current(), delay);\n\n return () => clearInterval(id);\n }, [delay]);\n}\n","import { useEffect, useState } from 'react';\n\nimport { isBrowser } from '@/utils';\n\n/**\n * Get the result of an arbitrary CSS media query\n *\n * @param query - the CSS media query\n * @returns a boolean indicating the result of the query\n * @example\n * // true if the viewport is at least 768px wide\n * const matches = useMediaQuery('(min-width: 768px)')\n */\nexport function useMediaQuery(query: string): boolean {\n const getMatches = (query: string): boolean => {\n // Prevents SSR issues\n if (isBrowser()) {\n return window.matchMedia(query).matches;\n }\n return false;\n };\n\n const [matches, setMatches] = useState<boolean>(getMatches(query));\n\n function handleChange() {\n setMatches(getMatches(query));\n }\n\n useEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n // Triggered at the first client-side load and if query changes\n handleChange();\n\n matchMedia.addEventListener('change', handleChange);\n\n return () => {\n matchMedia.removeEventListener('change', handleChange);\n };\n }, [query]);\n\n return matches;\n}\n","import type { RefObject } from 'react';\n\nimport { useEventListener } from '../useEventListener';\n\ntype Handler = (event: MouseEvent) => void;\n\nexport function useOnClickOutside<T extends HTMLElement | null = HTMLElement>(\n ref: RefObject<T>,\n handler: Handler,\n mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'\n): void {\n useEventListener(mouseEvent, (event) => {\n const el = ref.current;\n\n // Do nothing if clicking ref's element or descendent elements\n if (!el || el.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n });\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { Dispatch, SetStateAction } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nimport { useEventCallback } from '../useEventCallback';\nimport { useEventListener } from '../useEventListener';\n\ntype StorageName = 'localStorage' | 'sessionStorage';\n\ntype StorageEventMap = {\n [K in StorageName]: CustomEvent;\n};\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-object-type\n interface WindowEventMap extends StorageEventMap {}\n}\n\n/**\n * Represents the options for customizing the behavior of serialization and deserialization.\n * @template T - The type of the state to be stored in storage.\n */\ntype UseStorageOptions<T> = {\n /** A function to deserialize the stored value. */\n deserializer?: (value: string) => T;\n /**\n * If `true` (default), the hook will initialize reading the storage. In SSR, you should set it to `false`, returning the initial value initially.\n * @default true\n */\n initializeWithValue?: boolean;\n /** A function to serialize the value before storing it. */\n serializer?: (value: T) => string;\n};\n\n/**\n * Custom hook that uses local or session storage to persist state across page reloads.\n * @template T - The type of the state to be stored in storage.\n * @param key - The key under which the value will be stored in storage.\n * @param initialValue - The initial value of the state or a function that returns the initial value.\n * @param options - Options for customizing the behavior of serialization and deserialization (optional).\n * @returns A tuple containing the stored value and a function to set the value.\n * @public\n * @example\n * ```tsx\n * const [count, setCount] = useStorage('count', 0);\n * // Access the `count` value and the `setCount` function to update it.\n * ```\n */\nexport function useStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n storageName: StorageName,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n const { initializeWithValue = true } = options;\n const storage = window[storageName];\n\n const serializer = useCallback<(value: T) => string>(\n (value) => {\n if (options.serializer) {\n return options.serializer(value);\n }\n return JSON.stringify(value);\n },\n [options]\n );\n\n const deserializer = useCallback<(value: string) => T>(\n (value) => {\n if (options.deserializer) {\n return options.deserializer(value);\n } else if (value === 'undefined') {\n return undefined as unknown as T;\n }\n const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch (err) {\n console.error(`Error parsing JSON: ${(err as Error).message}`);\n return defaultValue;\n }\n return parsed as T;\n },\n [options, initialValue]\n );\n\n const readValue = useCallback((): T => {\n const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;\n if (!isBrowser()) {\n return initialValueToUse;\n }\n const raw = storage.getItem(key);\n return raw ? deserializer(raw) : initialValueToUse;\n }, [initialValue, key, deserializer]);\n\n const [storedValue, setStoredValue] = useState(() => {\n if (initializeWithValue) {\n return readValue();\n }\n return initialValue instanceof Function ? initialValue() : initialValue;\n });\n\n const setValue: Dispatch<SetStateAction<T>> = useEventCallback((value) => {\n if (!isBrowser()) {\n console.warn(`Tried setting storage key “${key}” even though environment is not a client`);\n }\n try {\n const newValue = value instanceof Function ? value(readValue()) : value;\n storage.setItem(key, serializer(newValue));\n setStoredValue(newValue);\n window.dispatchEvent(new StorageEvent(storageName, { key }));\n } catch (error) {\n console.warn(`Error setting storage key “${key}”:`, error);\n }\n });\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [key]);\n\n const handleStorageChange = useCallback(\n (event: CustomEvent | StorageEvent) => {\n if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {\n return;\n }\n setStoredValue(readValue());\n },\n [key, readValue]\n );\n\n // this only works for other documents, not the current one\n useEventListener('storage', handleStorageChange);\n\n useEventListener(storageName, handleStorageChange);\n\n return [storedValue, setValue];\n}\n\nexport type { StorageName, UseStorageOptions };\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses local storage to persist state across page reloads */\nexport function useLocalStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'localStorage', options);\n}\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses session storage to persist state across page reloads */\nexport function useSessionStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'sessionStorage', options);\n}\n","import { useEffect, useState } from 'react';\n\n// this is required since our storybook manager plugin cannot use vite aliases\nimport { isBrowser } from '../../utils';\n\ntype Theme = 'dark' | 'light';\n\ntype UpdateTheme = (theme: Theme) => void;\n\n/** @private */\nconst DEFAULT_THEME: Theme = 'light';\n\n/** @private */\nconst THEME_ATTRIBUTE = 'data-mode';\n\n/** @private */\nconst THEME_KEY = 'theme';\n\n/** @private */\nconst SYS_DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\n/**\n * Returns the current theme and a function to update the current theme\n *\n * The reason the implementation of this hook is rather convoluted is for\n * cases where the theme is updated outside this hook\n */\nfunction useTheme(): readonly [Theme, UpdateTheme] {\n // Initial theme value is based on the value saved in local storage or the system theme\n const [theme, setTheme] = useState<Theme>(() => {\n if (!isBrowser()) {\n return DEFAULT_THEME;\n }\n const savedTheme = window.localStorage.getItem(THEME_KEY);\n let initialTheme: Theme;\n if (savedTheme === 'dark' || savedTheme === 'light') {\n initialTheme = savedTheme;\n } else {\n initialTheme = window.matchMedia(SYS_DARK_MEDIA_QUERY).matches ? 'dark' : 'light';\n }\n document.documentElement.setAttribute(THEME_ATTRIBUTE, initialTheme);\n return initialTheme;\n });\n\n useEffect(() => {\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === THEME_ATTRIBUTE) {\n const updatedTheme = (mutation.target as HTMLHtmlElement).getAttribute(THEME_ATTRIBUTE);\n if (updatedTheme === 'light' || updatedTheme === 'dark') {\n window.localStorage.setItem(THEME_KEY, updatedTheme);\n setTheme(updatedTheme);\n } else {\n console.error(`Unexpected value for 'data-mode' attribute: ${updatedTheme}`);\n }\n }\n });\n });\n observer.observe(document.documentElement, {\n attributes: true\n });\n return () => observer.disconnect();\n }, []);\n\n // When the user wants to change the theme\n const updateTheme = (theme: Theme) => {\n document.documentElement.setAttribute(THEME_ATTRIBUTE, theme);\n };\n\n return [theme, updateTheme] as const;\n}\n\nexport { DEFAULT_THEME, SYS_DARK_MEDIA_QUERY, type Theme, THEME_ATTRIBUTE, THEME_KEY, useTheme };\n","import { useEffect, useMemo, useState } from 'react';\n\nimport type {\n TranslateFunction,\n TranslationKey,\n TranslationKeyForNamespace,\n TranslationNamespace,\n TranslatorType\n} from '@/i18n';\n\n// this is required since our storybook manager plugin cannot use vite aliases\nimport { i18n } from '../../i18n';\n\nexport function useTranslation(): TranslatorType<TranslationKey>;\nexport function useTranslation<TNamespace extends TranslationNamespace>(\n namespace: TNamespace\n): TranslatorType<TranslationKeyForNamespace<TNamespace>>;\nexport function useTranslation(namespace?: TranslationNamespace): TranslatorType<string> {\n const [resolvedLanguage, setResolvedLanguage] = useState(i18n.resolvedLanguage);\n const { changeLanguage, t } = useMemo(() => {\n const t: TranslateFunction<string> = (target, options) => {\n if (typeof target === 'object') {\n return i18n.t(target, options);\n }\n return i18n.t((namespace ? `${namespace}.${target}` : target) as TranslationKey, options);\n };\n return {\n changeLanguage: i18n.changeLanguage.bind(i18n),\n t\n };\n }, []);\n\n useEffect(() => {\n i18n.addEventListener('languageChange', setResolvedLanguage);\n return () => {\n i18n.removeEventListener('languageChange', setResolvedLanguage);\n };\n }, []);\n\n return {\n changeLanguage,\n resolvedLanguage,\n t\n };\n}\n","import { useState } from 'react';\n\nimport { useEventListener } from '../useEventListener';\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport type WindowSize = {\n height: number;\n width: number;\n};\n\nexport function useWindowSize(): WindowSize {\n const [windowSize, setWindowSize] = useState<WindowSize>({\n height: 0,\n width: 0\n });\n\n const handleSize = () => {\n setWindowSize({\n height: window.innerHeight,\n width: window.innerWidth\n });\n };\n\n useEventListener('resize', handleSize);\n\n // Set size at the first client-side load\n useIsomorphicLayoutEffect(() => {\n handleSize();\n }, []);\n\n return windowSize;\n}\n"],"mappings":"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","names":["create","useCallback","useEffect","useEffect","useCallback","useEffect","useRef","useRef","useEffect","useEffect","useRef","useRef","useEffect","useEffect","useState","query","useState","useEffect","useCallback","useEffect","useState","useCallback","useState","useEffect","useEffect","useState","useState","useEffect","theme","useEffect","useState","useState","t","useEffect","useState","useState"]}
|
|
File without changes
|