UNPKG

@gmana/react-hooks

Version:
1,665 lines (1,646 loc) 46.6 kB
import * as react0 from "react"; import React$1, { CSSProperties, DependencyList, Dispatch, EffectCallback, RefObject, SetStateAction, useEffect } from "react"; //#region src/lib/use-array.d.ts /** * * @param defaultValue * @returns * @example * ```tsx * import { useArray } from '@gmana/hook'; * * export default function ArrayComponent() { * const { array, set, push, remove, filter, update, clear } = useArray([1, 2, 3, 4, 5, 6]); * * return ( * <div className="space-x-3 text-white divide-x-2 divide-gray-400"> * <div>{array.join(', ')}</div> * <button onClick={() => push(7)}>Add 7</button> * <button onClick={() => update(1, 9)}>Change Second Element To 9</button> * <button onClick={() => remove(1)}>Remove Second Element</button> * <button onClick={() => filter((n) => n < 4)}>Keep Numbers Less Than 4</button> * <button onClick={() => set([1, 2])}>Set To 1, 2</button> * <button onClick={clear}>Clear</button> * </div> * ); * } * ``` */ declare function useArray<T>(defaultValue: T[]): { array: T[]; set: react0.Dispatch<react0.SetStateAction<T[]>>; push: (element: T) => void; filter: (callback: (n: T) => boolean) => void; update: (index: number, newElement: T) => void; remove: (index: number) => void; clear: () => void; }; //#endregion //#region src/lib/use-async-effect.d.ts /** * A hook that allows you to use async functions in useEffect. * Automatically handles cleanup to prevent memory leaks. * * @param asyncFunction - The async function to execute * @param deps - Dependency array for the effect * * @example * ```tsx * import { useAsyncEffect } from '@gmana/react-hooks' * import { useState } from 'react' * * function UserProfile({ userId }: { userId: string }) { * const [user, setUser] = useState(null) * const [loading, setLoading] = useState(true) * * useAsyncEffect(async (isCancelled) => { * setLoading(true) * * try { * const response = await fetch(`/api/users/${userId}`) * const userData = await response.json() * * // Check if component is still mounted before updating state * if (!isCancelled()) { * setUser(userData) * setLoading(false) * } * } catch (error) { * if (!isCancelled()) { * console.error('Failed to fetch user:', error) * setLoading(false) * } * } * }, [userId]) * * if (loading) return <div>Loading...</div> * if (!user) return <div>User not found</div> * * return <div>Hello, {user.name}!</div> * } * ``` */ declare function useAsyncEffect(asyncFunction: (isCancelled: () => boolean) => Promise<void>, deps?: React.DependencyList): void; //#endregion //#region src/lib/use-boolean.d.ts interface ReturnType$2 { value: boolean; setValue: Dispatch<SetStateAction<boolean>>; setTrue: () => void; setFalse: () => void; toggle: () => void; } /** * * @param defaultValue * @returns * @example * import React from 'react' * import { useBoolean } from '@gmana/hook' * * export default function Component() { * const { value, setValue, setTrue, setFalse, toggle } = useBoolean(false) * * // Just an example to use "setValue" * const customToggle = () => setValue(x => !x) * * return ( * <> * <p> * Value is <code>{value.toString()}</code> * </p> * <button onClick={setTrue}>set true</button> * <button onClick={setFalse}>set false</button> * <button onClick={toggle}>toggle</button> * <button onClick={customToggle}>custom toggle</button> * </> * ) * } */ declare function useBoolean(defaultValue?: boolean): ReturnType$2; //#endregion //#region src/lib/use-clipboard.d.ts /** * Hook to copy text to clipboard. */ declare function useClipboard(): { isCopied: boolean; copyToClipboard: (text: string) => Promise<void>; }; //#endregion //#region src/lib/use-cookies.d.ts interface CookieOptions { days?: number; path?: string; [key: string]: string | number | boolean | undefined; } interface EnhancedCookieOptions extends CookieOptions { sameSite?: "strict" | "lax" | "none"; } declare const setCookie: (name: string, value: string, options?: EnhancedCookieOptions) => void; declare const getCookie: (name: string, initialValue?: string) => string; declare const removeCookie: (name: string) => void; //#endregion //#region src/lib/use-copy-to-clipboard.d.ts declare function useCopyToClipboard({ timeout, onCopy }?: { timeout?: number; onCopy?: () => void; }): { isCopied: boolean; copyToClipboard: (value: string) => void; }; //#endregion //#region src/lib/use-countdown.d.ts interface UseCountdownOptions { /** Initial countdown time in seconds */ initialTime?: number; /** Callback fired when countdown completes */ onComplete?: () => void; /** Callback fired on each tick */ onTick?: (timeLeft: number) => void; } interface UseCountdownReturn { /** Remaining time in seconds */ timeLeft: number; /** Whether the countdown is currently active */ isActive: boolean; /** Start/restart the countdown */ startTimer: () => void; /** Stop and reset the countdown */ resetTimer: () => void; /** Pause the countdown without resetting */ pauseTimer: () => void; /** Resume a paused countdown */ resumeTimer: () => void; /** Whether user can trigger a new countdown */ canResend: boolean; } /** * * @param options * @returns * @example * ```tsx * import { useCountdown } from '@gmana/react-hooks' * * // Simple usage (backward compatible) const { timeLeft, startTimer, canResend } = useCountdown(60) // Advanced usage with callbacks const timer = useCountdown({ initialTime: 60, onComplete: () => console.log("Timer finished!"), onTick: (time) => console.log(`${time}s remaining`), }) // New pause/resume functionality <button onClick={timer.pauseTimer}>Pause</button> <button onClick={timer.resumeTimer}>Resume</button> * ``` */ declare function useCountdown(options?: UseCountdownOptions | number): UseCountdownReturn; //#endregion //#region src/lib/use-counter.d.ts interface ReturnType$1 { count: number; increment: () => void; decrement: () => void; reset: () => void; setCount: Dispatch<SetStateAction<number>>; } /** * * @param initialValue * @returns * @example * import React from 'react' * import { useCounter } from '@gmana/hook' * * export default function Component() { * const { count, setCount, increment, decrement, reset } = useCounter(0) * * const multiplyBy2 = () => setCount(x => x * 2) * * return ( * <> * <p>Count is {count}</p> * <button onClick={increment}>Increment</button> * <button onClick={decrement}>Decrement</button> * <button onClick={reset}>Reset</button> * <button onClick={multiplyBy2}>Multiply by 2</button> * </> * ) * } */ declare function useCounter(initialValue?: number): ReturnType$1; //#endregion //#region src/lib/use-dark-mode.d.ts interface UseDarkModeOutput { isDarkMode: boolean; toggle: () => void; enable: () => void; disable: () => void; } /** * * @param defaultValue * @returns * @example * import React from 'react' * import { useDarkMode } from '@gmana/hook' * * export default function Component() { * const { isDarkMode, toggle, enable, disable } = useDarkMode() * * return ( * <div> * <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p> * <button onClick={toggle}>Toggle</button> * <button onClick={enable}>Enable</button> * <button onClick={disable}>Disable</button> * </div> * ) * } */ declare function useDarkMode(defaultValue?: boolean): UseDarkModeOutput; //#endregion //#region src/lib/use-debounce.d.ts interface UseDebounceOptions { /** The number of milliseconds to delay (default: 500) */ delay?: number; /** Update immediately on first call, then debounce (default: false) */ leading?: boolean; /** Update on trailing edge after delay (default: true) */ trailing?: boolean; /** Maximum time to wait before forcing an update (optional) */ maxWait?: number; } interface UseDebounceReturn<T> { /** The debounced value */ value: T; /** Immediately update to the latest value, canceling pending updates */ flush: () => void; /** Cancel any pending updates */ cancel: () => void; /** Whether there's a pending update */ isPending: boolean; } /** * Debounces a value, delaying updates until after wait milliseconds have elapsed * since the last time the debounced value was invoked. * * @param value - The value to debounce * @param options - Debounce options (or delay number for backward compatibility) * @returns The debounced value (or object with value and controls if using options) * * @example * ```tsx * // Simple usage (backward compatible) * const debouncedSearch = useDebounce(searchTerm, 500) * * // Advanced usage with options * const { value, flush, cancel, isPending } = useDebounce(searchTerm, { * delay: 500, * leading: true, * maxWait: 2000 * }) * ``` */ declare function useDebounce<T>(value: T, delay?: number): T; declare function useDebounce<T>(value: T, options: UseDebounceOptions): UseDebounceReturn<T>; //#endregion //#region src/lib/use-debounce-callback.d.ts interface UseDebouncedCallbackOptions { /** The number of milliseconds to delay */ delay: number; /** Execute immediately on first call, then debounce (default: false) */ leading?: boolean; /** Execute on trailing edge after delay (default: true) */ trailing?: boolean; /** Maximum time to wait before forcing execution (optional) */ maxWait?: number; } interface DebouncedFunction<T extends (...args: any[]) => any> { /** The debounced function */ (...args: Parameters<T>): void; /** Immediately execute with the last provided arguments */ flush: () => void; /** Cancel any pending execution */ cancel: () => void; /** Whether there's a pending execution */ isPending: () => boolean; } /** * Debounces a callback function, delaying its execution until after `delay` milliseconds * have elapsed since the last time the debounced function was invoked. * * @param callback - The function to debounce * @param options - Debounce options (or delay number for backward compatibility) * @returns A debounced version of the callback with control methods * * @example * ```tsx * // Simple usage (backward compatible) * const debouncedSave = useDebouncedCallback(saveData, 500) * * // Advanced usage with options * const debouncedSearch = useDebouncedCallback( * async (query: string) => { * const results = await searchAPI(query) * setResults(results) * }, * { * delay: 300, * leading: true, * maxWait: 1000 * } * ) * * // Manual control * <button onClick={debouncedSearch.flush}>Search Now</button> * <button onClick={debouncedSearch.cancel}>Cancel</button> * // ✅ Form with manual controls function SearchForm() { const [query, setQuery] = useState('') const search = useDebouncedCallback( (searchQuery: string) => { console.log('Searching for:', searchQuery) }, { delay: 500 } ) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() search.flush() // Execute immediately on submit } const handleCancel = () => { search.cancel() // Cancel pending search setQuery('') } return ( <form onSubmit={handleSubmit}> <input value={query} onChange={(e) => { setQuery(e.target.value) search(e.target.value) }} /> <button type="submit">Search Now</button> <button type="button" onClick={handleCancel}> Cancel </button> {search.isPending() && <span>Searching...</span>} </form> ) } // ✅ Auto-save with maxWait const autoSave = useDebouncedCallback( (content: string) => saveDocument(content), { delay: 2000, // Wait 2s after typing stops maxWait: 10000, // Force save every 10s regardless } ) // ✅ Scroll handler with leading edge const handleScroll = useDebouncedCallback( () => { const scrollTop = window.scrollY updateScrollPosition(scrollTop) }, { delay: 150, leading: true, // Update immediately on first scroll trailing: true // Update again when scrolling stops } ) useEffect(() => { window.addEventListener('scroll', handleScroll) return () => { window.removeEventListener('scroll', handleScroll) handleScroll.cancel() // Cleanup pending calls } }, [handleScroll]) * ``` */ declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, delay: number): DebouncedFunction<T>; declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, options: UseDebouncedCallbackOptions): DebouncedFunction<T>; //#endregion //#region src/lib/use-dimensions.d.ts declare function useDimensions(containerRef: React$1.RefObject<HTMLElement | null>): { width: number; height: number; }; //#endregion //#region src/lib/use-effect-once.d.ts /** * * @param effect * @example * import React, { useEffect, useState } from 'react' * import { useEffectOnce } from '@gmana/hook' * * export default function Component() { * const [data, setData] = useState<number>(0) * useEffect(() => { * console.log('Normal useEffect', { data }) * }, [data]) * * useEffectOnce(() => { * console.log('Triggered only once, on mount', { data }) * }) * * return ( * <div> * <p>Open your console</p> * <button onClick={() => setData(Date.now())}>Update data</button> * </div> * ) * } */ declare function useEffectOnce(effect: EffectCallback): void; //#endregion //#region src/lib/use-element-size.d.ts interface Size { width: number; height: number; } /** * @description * Use EventListener with simplicity by React Hook. It takes as parameters a `eventName`, * a call-back functions (handler) and optionally a reference `element`. You can see above two examples using `useRef` and `window` based event. * @returns * @example * import React, { useState } from 'react' * import { useElementSize } from '@gmana/hook' * * export default function Component() { * const [isVisible, setVisible] = useState(true) * const [squareRef, { width, height }] = useElementSize() * * const toggleVisibility = () => setVisible(x => !x) * * return ( * <> * <p>{`The square width is ${width}px and height ${height}px`}</p> * <p>Try, resize your window and-or click on the button.</p> * * <button onClick={toggleVisibility}> * {isVisible ? 'Hide' : 'Show'} square * </button> * * {isVisible && ( * <div * ref={squareRef} * style={{ * width: '50%', * paddingTop: '50%', * backgroundColor: 'aquamarine', * margin: 'auto', * }} * /> * )} * </> * ) * } */ declare function useElementSize<T extends HTMLElement = HTMLDivElement>(): [(node: T | null) => void, Size]; //#endregion //#region src/lib/use-estimate-storage.d.ts /** * Estimates the available storage quota and usage. * Returns the estimated remaining storage space in MB. * @example 'use client'; import { useEstimateStorage } from '@gmana/hook'; export function Quota() { const quota = useEstimateStorage(); return ( <div> <h1>Quota: {(quota / 1024).toFixed(2)} GB</h1> </div> ); } */ declare function useEstimateStorage(): number; //#endregion //#region src/lib/use-event-listener.d.ts /** * * @param eventName * @param handler * @param element * @example * import React, { useRef } from 'react' * import { useEventListener } from '@gmana/hook' * * export default function Component() { * // Define button ref * const buttonRef = useRef<HTMLButtonElement>(null) * * const onScroll = (event: Event) => { * console.log('window scrolled!', event) * } * * const onClick = (event: Event) => { * console.log('button clicked!', event) * } * * // example with window based event * useEventListener('scroll', onScroll) * * // example with element based event * useEventListener('click', onClick, buttonRef) * * return ( * <div style={{ minHeight: '200vh' }}> * <button ref={buttonRef}>Click me</button> * </div> * ) * } */ declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (event: WindowEventMap[K]) => void): void; /** * * @param eventName * @param handler * @param element * @example * import React, { useRef } from 'react' * import { useEventListener } from '@gmana/hook' * * export default function Component() { * // Define button ref * const buttonRef = useRef<HTMLButtonElement>(null) * * const onScroll = (event: Event) => { * console.log('window scrolled!', event) * } * * const onClick = (event: Event) => { * console.log('button clicked!', event) * } * * // example with window based event * useEventListener('scroll', onScroll) * * // example with element based event * useEventListener('click', onClick, buttonRef) * * return ( * <div style={{ minHeight: '200vh' }}> * <button ref={buttonRef}>Click me</button> * </div> * ) * } */ declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement>(eventName: K, handler: (event: HTMLElementEventMap[K]) => void, element: RefObject<T>): void; //#endregion //#region src/lib/use-fetch.d.ts interface State<T> { data?: T; error?: Error; isLoading?: boolean; } /** * @description * Here is a React Hook which aims to retrieve data on an API using the native Fetch API. * * For advanced usages and optimizations, * see these other hooks more powerful like * - `useSWR`, * - `useQuery` * - `Redux Toolkit` (`RTK Query`). * @param url * @param options * @returns * @example * import React from 'react' * import { useFetch } from '@gmana/hook' * * const url = `http://jsonplaceholder.typicode.com/posts` * * interface Post { * userId: number * id: number * title: string * body: string * } * * export default function Component() { * const { data, error } = useFetch<Post[]>(url) * * if (error) return <p>There is an error.</p> * if (!data) return <p>Loading...</p> * return <p>{data[0].title}</p> * } */ declare function useFetch<T = unknown>(url?: string, options?: RequestInit): State<T>; //#endregion //#region src/lib/use-focus-trap.d.ts declare function useFocusTrap(isActive?: boolean): react0.RefObject<HTMLElement | null>; //#endregion //#region src/lib/use-hover.d.ts /** * @example * import React, { useRef } from 'react' * import { useHover } from '@gmana/hook'; * * export default function Component() { * const hoverRef = useRef(null) * const isHover = useHover(hoverRef) * * return ( * <div ref={hoverRef}> * {`The current div is ${isHover ? `hovered` : `un-hovered`}`} * </div> * ) * } */ declare function useHover<T extends HTMLElement = HTMLElement>(ComponentRef: RefObject<T>): boolean; //#endregion //#region src/lib/use-hydrated.d.ts /** * A hook that returns whether the component has been hydrated on the client side. * This is essential for preventing hydration mismatches in Next.js and other SSR frameworks. * * Unlike useIsClient, this hook is specifically designed to handle the hydration phase * and provides a safer way to conditionally render client-only content. * * @returns boolean indicating if the component is hydrated * @example * import React from 'react' * import { useHydrated } from '@gmana/react-hooks' * * export default function Component() { * const isHydrated = useHydrated() * * return ( * <div> * <h1>My App</h1> * {isHydrated ? ( * <ClientOnlyComponent /> * ) : ( * <div>Loading client content...</div> * )} * </div> * ) * } * * @example * // Using with media queries to prevent hydration mismatches * function ResponsiveComponent() { * const isHydrated = useHydrated() * const isMobile = useMediaQuery('(max-width: 768px)') * * if (!isHydrated) { * // Return a safe default that matches SSR * return <div>Loading...</div> * } * * return ( * <div> * {isMobile ? <MobileLayout /> : <DesktopLayout />} * </div> * ) * } */ declare function useHydrated(): boolean; //#endregion //#region src/lib/use-image-on-load.d.ts interface ImageStyle { thumbnail: CSSProperties; fullSize: CSSProperties; } interface ImageOnLoadType { handleImageOnLoad: () => void; css: ImageStyle; } /** * * @returns * @example * import React, { CSSProperties } from 'react' * import { useImageOnLoad } from '@gmana/hook' * * export default function Component() { * const { handleImageOnLoad, css } = useImageOnLoad() * * const style: { [key: string]: CSSProperties } = { * wrap: { * position: 'relative', * width: 400, * height: 400, * margin: 'auto', * }, * image: { * position: 'absolute', * top: 0, * left: 0, * width: `100%`, * height: `100%`, * }, * } * * return ( * <div style={style.wrap}> * * <img * style={{ ...style.image, ...css.thumbnail }} * src="https://via.placeholder.com/150" * alt="thumbnail" * /> * * <img * onLoad={handleImageOnLoad} * style={{ ...style.image, ...css.fullSize }} * src="https://via.placeholder.com/600" * alt="fullImage" * /> * </div> * ) * } */ declare function useImageOnLoad(): ImageOnLoadType; //#endregion //#region src/lib/use-in-view.d.ts declare const useIntersection: (element: RefObject<HTMLDivElement>, rootMargin: string) => boolean; //#endregion //#region src/lib/use-intersection-observer.d.ts interface Args extends IntersectionObserverInit { freezeOnceVisible?: boolean; } /** * * @param ComponentRef * @param param1 * @returns * @example * import React, { useRef } from 'react' * import { useIntersectionObserver } from '@gmana/hook' * * const Section = (props: { title: string }) => { * const ref = useRef<HTMLDivElement | null>(null) * const entry = useIntersectionObserver(ref, {}) * const isVisible = !!entry?.isIntersecting * * console.log(`Render Section ${props.title}`, { isVisible }) * * return ( * <div * ref={ref} * style={{ * minHeight: '100vh', * display: 'flex', * border: '1px dashed #000', * fontSize: '2rem', * }} * > * <div style={{ margin: 'auto' }}>{props.title}</div> * </div> * ) * } * * export default function Component() { * return ( * <> * {Array.from({ length: 5 }).map((_, index) => ( * <Section key={index + 1} title={`${index + 1}`} /> * ))} * </> * ) * } */ declare function useIntersectionObserver(ComponentRef: RefObject<Element>, { threshold, root, rootMargin, freezeOnceVisible }: Args): IntersectionObserverEntry | undefined; //#endregion //#region src/lib/use-interval.d.ts /** * * @param callback * @param delay * @example * import React, { ChangeEvent, useState } from 'react' * import { useInterval } from '@gmana/hook' * * export default function Component() { * // The counter * const [count, setCount] = useState<number>(0) * // Dynamic delay * const [delay, setDelay] = useState<number>(1000) * // ON/OFF * const [isPlaying, setPlaying] = useState<boolean>(false) * * useInterval( * () => { * // Your custom logic here * setCount(count + 1) * }, * // Delay in milliseconds or null to stop it * isPlaying ? delay : null, * ) * * const handleChange = (event: ChangeEvent<HTMLInputElement>) => { * setDelay(Number(event.target.value)) * } * * return ( * <> * <h1>{count}</h1> * <button onClick={() => setPlaying(!isPlaying)}> * {isPlaying ? 'pause' : 'play'} * </button> * <p> * <label htmlFor="delay">Delay: </label> * <input * type="number" * name="delay" * onChange={handleChange} * value={delay} * /> * </p> * </> * ) * } */ declare function useInterval(callback: () => void, delay: number | null): void; //#endregion //#region src/lib/use-is-client.d.ts /** * * @returns * @example * import React from 'react' * import { useIsClient } from '@gmana/hook' * * export default function Component() { * const isClient = useIsClient() * * return <div>{isClient ? 'Client' : 'server'}</div> * } */ declare function useIsClient(): boolean; //#endregion //#region src/lib/use-is-first-render.d.ts /** * * @returns * @example * import React, { useEffect, useState } from 'react' * import { useIsFirstRender } from '@gmana/hook' * * export default function Component() { * const isFirst = useIsFirstRender() * const [data, setData] = useState<number>(0) * * useEffect(() => { * console.log('Normal useEffect', { data }) * }, [data]) * * return ( * <div> * <p>Open your console</p> * <p>Is first render: {isFirst ? 'yes' : 'no'}</p> * <button onClick={() => setData(Date.now())}>Update data</button> * </div> * ) * } */ declare function useIsFirstRender(): boolean; //#endregion //#region src/lib/use-is-mounted.d.ts /** * * @returns * @example * import React, { useEffect, useState } from 'react' * import { useIsMounted } from '@gmana/hook' * * const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) * * function Child() { * const [data, setData] = useState('loading') * const isMounted = useIsMounted() * * // simulate an api call and update state * useEffect(() => { * void delay(3000).then(() => { * if (isMounted()) setData('OK') * }) * }, [isMounted]) * * return <p>{data}</p> * } * * export default function Component() { * const [isVisible, setVisible] = useState<boolean>(false) * * const toggleVisibility = () => setVisible(state => !state) * * return ( * <> * <button onClick={toggleVisibility}>{isVisible ? 'Hide' : 'Show'}</button> * * {isVisible && <Child />} * </> * ) * } */ declare function useIsMounted(): () => boolean; //#endregion //#region src/lib/use-isomorphic-layout-effect.d.ts /** * * @example * import React from 'react' * import { useIsomorphicLayoutEffect } from '@gmana/hook' * * export default function Component() { * useIsomorphicLayoutEffect(() => { * console.log( * "In the browser, I'm an `useLayoutEffect`, but in SSR, I'm an `useEffect`.", * ) * }, []) * * return <p>Hello, world</p> * } */ declare const useIsomorphicLayoutEffect: typeof useEffect; //#endregion //#region src/lib/use-local-storage.d.ts declare global { interface WindowEventMap { "local-storage": CustomEvent; } } type SetValue<T> = Dispatch<SetStateAction<T>>; /** * * @param key * @param initialValue * @returns * @example * import React from 'react' * import { useLocalStorage } from '@gmana/hook' * * export default function Component() { * const [isDarkTheme, setDarkTheme] = useLocalStorage('darkTheme', true) * * const toggleTheme = () => { * setDarkTheme(prevValue => !prevValue) * } * * return ( * <button onClick={toggleTheme}> * {`The current theme is ${isDarkTheme ? `dark` : `light`}`} * </button> * ) * } */ declare function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>]; //#endregion //#region src/lib/use-locked-body.d.ts type ReturnType = [boolean, (locked: boolean) => void]; /** * * @param initialLocked * @returns * @example * import React, { CSSProperties, useState } from 'react' * import { useLockedBody } from '@gmana/hook' * * const fixedCenterStyle: CSSProperties = { * position: 'fixed', * top: '50%', * left: '50%', * transform: 'translate(-50%, -50%)', * } * * const fakeScrollableStyle: CSSProperties = { * minHeight: '150vh', * background: 'linear-gradient(palegreen, palegoldenrod, palevioletred)', * } * * // Example 1: useLockedBody as useState() * export default function App() { * const [locked, setLocked] = useLockedBody() * * const toggleLocked = () => { * setLocked(!locked) * } * * return ( * <div style={fakeScrollableStyle}> * <button style={fixedCenterStyle} onClick={toggleLocked}> * {locked ? 'unlock scroll' : 'lock scroll'} * </button> * </div> * ) * } * * // Example 2: useLockedBody with our custom state * export function App2() { * const [locked, setLocked] = useState(false) * * const toggleLocked = () => { * setLocked(!locked) * } * * useLockedBody(locked) * * return ( * <div style={fakeScrollableStyle}> * <button style={fixedCenterStyle} onClick={toggleLocked}> * {locked ? 'unlock scroll' : 'lock scroll'} * </button> * </div> * ) * } */ declare function useLockedBody(initialLocked?: boolean): ReturnType; //#endregion //#region src/lib/use-map.d.ts type MapOrEntries<K, V> = Map<K, V> | [K, V][]; interface Actions<K, V> { set: (key: K, value: V) => void; setAll: (entries: MapOrEntries<K, V>) => void; remove: (key: K) => void; reset: Map<K, V>["clear"]; } type Return<K, V> = [Omit<Map<K, V>, "set" | "clear" | "delete">, Actions<K, V>]; /** * * @param initialState * @returns * @example * import React, { Fragment } from 'react' * import { MapOrEntries, useMap } from '@gmana/hook' * * const initialValues: MapOrEntries<string, string> = [['key', '🆕']] * const otherValues: MapOrEntries<string, string> = [ * ['hello', '👋'], * ['data', '📦'], * ] * * export default function Component() { * const [map, actions] = useMap<string, string>(initialValues) * * const set = () => actions.set(String(Date.now()), '📦') * const setAll = () => actions.setAll(otherValues) * const reset = () => actions.reset() * const remove = () => actions.remove('hello') * * return ( * <div> * <button onClick={set}>Add</button> * <button onClick={reset}>Reset</button> * <button onClick={setAll}>Set new data</button> * <button onClick={remove} disabled={!map.get('hello')}> * {'Remove "hello"'} * </button> * * <pre> * Map ( * {Array.from(map.entries()).map(([key, value]) => ( * <Fragment key={key}>{`\n ${key}: ${value}`}</Fragment> * ))} * <br />) * </pre> * </div> * ) * } */ declare function useMap<K, V>(initialState?: MapOrEntries<K, V>): Return<K, V>; //#endregion //#region src/lib/use-media-query.d.ts /** * * @param query * @returns * @example const isDesktop = useMediaQuery("(min-width: 768px)") */ declare function useMediaQuery(query: string): boolean; //#endregion //#region src/lib/use-mobile.d.ts declare function useIsMobile(): boolean; //#endregion //#region src/lib/use-network-information.d.ts interface INetworkInformation { isOnline: boolean; effectiveType: string; downlink: number; rtt: number; } /** * Hook that returns network information for the browser, * including online status, effective connection type, * downlink speed, and round trip latency. * @example import { useNetworkInformation } from "@gmana/hook"; export default function Network() { const networkState = useNetworkInformation(); return <pre>{JSON.stringify(networkState, null, 2)}</pre>; } */ declare const useNetworkInformation: () => INetworkInformation | null; //#endregion //#region src/lib/use-offline-detector.d.ts /** * Hook to detect offline status. * Returns a boolean indicating if offline. * @example 'use client'; import { useOfflineDetector } from '@gmana/hook'; import { useEffect, useState } from 'react'; export function OfflineBanner() { const isOffline = useOfflineDetector(); const [showBanner, setShowBanner] = useState(() => isOffline); useEffect(() => { setShowBanner(isOffline); }, [isOffline]); return showBanner ? ( <div className="bg-gray-950 text-white px-3 py-1 flex justify-between items-center"> {isOffline ? <p>You are currently offline.</p> : <p>You are currently online.</p>} <button onClick={() => setShowBanner(false)}>Close</button> </div> ) : null; } */ declare const useOfflineDetector: () => boolean; //#endregion //#region src/lib/use-on-click-outside.d.ts type Handler = (event: MouseEvent) => void; /** * * @param ref * @param handler * @param mouseEvent * @example * import React, { useRef } from 'react' * import { useOnClickOutside } from '@gmana/tw' * * export default function Component() { * const ref = useRef(null) * * const handleClickOutside = () => { * // Your custom logic here * console.log('clicked outside') * } * * const handleClickInside = () => { * // Your custom logic here * console.log('clicked inside') * } * * useOnClickOutside(ref, handleClickOutside) * * return ( * <button * ref={ref} * onClick={handleClickInside} * style={{ width: 200, height: 200, background: 'cyan' }} * /> * ) * } // OR const ref = useRef(null); useOnClickOutside(ref, () => { setIsClick(false); }); */ declare function useOnClickOutside<T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent?: "mousedown" | "mouseup"): void; //#endregion //#region src/lib/use-previous.d.ts /** * Returns the previous value of a state or prop. * Useful for comparing current and previous values in effects. * * @param value - The value to track * @returns The previous value * * @example * ```tsx * import { usePrevious } from '@gmana/react-hooks' * import { useState, useEffect } from 'react' * * function Counter() { * const [count, setCount] = useState(0) * const prevCount = usePrevious(count) * * useEffect(() => { * if (prevCount !== undefined) { * console.log(`Count changed from ${prevCount} to ${count}`) * } * }, [count, prevCount]) * * return ( * <div> * <p>Current count: {count}</p> * <p>Previous count: {prevCount}</p> * <button onClick={() => setCount(count + 1)}>Increment</button> * </div> * ) * } * ``` */ declare function usePrevious<T>(value: T): T | undefined; //#endregion //#region src/lib/use-read-local-storage.d.ts type Value<T> = T | null; /** * * @param key * @returns * @example * import React from 'react' * import { useReadLocalStorage } from '@gmana/hook' * * export default function Component() { * // Assuming a value was set in localStorage with this key * const darkMode = useReadLocalStorage('darkMode') * * return <p>DarkMode is {darkMode ? 'enabled' : 'disabled'}</p> * } */ declare function useReadLocalStorage<T>(key: string): Value<T>; //#endregion //#region src/lib/use-screen.d.ts /** * * @returns * @example * import React from 'react' * import { useScreen } from '@gmana/hook' * * export default function Component() { * const screen = useScreen() * * return ( * <div> * The current window dimensions are:{' '} * <code> * {JSON.stringify({ width: screen?.width, height: screen?.height })} * </code> * </div> * ) * } */ declare function useScreen(): Screen | undefined; //#endregion //#region src/lib/use-script.d.ts type Status = "idle" | "loading" | "ready" | "error"; type ScriptElt = HTMLScriptElement | null; /** * * @param src * @returns * @example * import React, { useEffect } from 'react' * import { useScript } from '@gmana/hook' * * // it's an example, use your types instead * // eslint-disable-next-line @typescript-eslint/no-explicit-any * declare const jQuery: any * * export default function Component() { * // Load the script asynchronously * const status = useScript(`https://code.jquery.com/jquery-3.5.1.min.js`) * * useEffect(() => { * if (typeof jQuery !== 'undefined') { * // jQuery is loaded => print the version * // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access * alert(jQuery.fn.jquery) * } * }, [status]) * * return ( * <div> * <p>{`Current status: ${status}`}</p> * * {status === 'ready' && <p>You can use the script here.</p>} * </div> * ) * } */ declare function useScript(src: string): Status; //#endregion //#region src/lib/use-scroll.d.ts /** * Hook to track scroll position and determine if scrolled beyond a threshold. * SSR-safe implementation that prevents hydration mismatches. * * @param threshold - The scroll threshold in pixels (default: 20) * @returns boolean indicating if scrolled beyond threshold * @example * import React from 'react' * import { useScroll } from '@gmana/react-hooks' * * export default function ScrollToTop() { * const isScrolled = useScroll(100) * * return ( * <div> * {isScrolled && ( * <button onClick={() => window.scrollTo(0, 0)}> * Back to top * </button> * )} * </div> * ) * } */ declare function useScroll(threshold?: number): boolean; //#endregion //#region src/lib/use-sidebar.d.ts declare function useSidebar(): { open: boolean; onOpenChange: (open: boolean) => void; }; //#endregion //#region src/lib/use-ssr.d.ts /** * A hook that provides information about the current environment (server vs client) * and hydration status. Useful for preventing hydration mismatches in SSR applications. * * @returns Object containing environment and hydration status * @example * import React from 'react' * import { useSsr } from '@gmana/hook' * * export default function Component() { * const { isBrowser, isServer, isHydrated } = useSsr() * * // Prevents hydration mismatch by only showing client-specific content after hydration * if (!isHydrated) { * return <p>Loading...</p> * } * * return <p>{isBrowser ? 'Browser' : 'Server'}!</p> * } */ declare function useSsr(): { isBrowser: boolean; isServer: boolean; isHydrated: boolean; }; //#endregion //#region src/lib/use-step.d.ts interface Helpers { goToNextStep: () => void; goToPrevStep: () => void; reset: () => void; canGoToNextStep: boolean; canGoToPrevStep: boolean; setStep: Dispatch<SetStateAction<number>>; } /** * * @param maxStep * @returns * @example * import React from 'react' * import { useStep } from '@gmana/hook' * * export default function Component() { * const [currentStep, helpers] = useStep(5) * * const { * canGoToPrevStep, * canGoToNextStep, * goToNextStep, * goToPrevStep, * reset, * setStep, * } = helpers * * return ( * <> * <p>Current step is {currentStep}</p> * <p>Can go to previous step {canGoToPrevStep ? 'yes' : 'no'}</p> * <p>Can go to next step {canGoToNextStep ? 'yes' : 'no'}</p> * <button onClick={goToNextStep}>Go to next step</button> * <button onClick={goToPrevStep}>Go to previous step</button> * <button onClick={reset}>Reset</button> * <button onClick={() => setStep(3)}>Set to step 3</button> * </> * ) * } * */ declare function useStep(maxStep: number): [number, Helpers]; //#endregion //#region src/lib/use-ternary-dark-mode.d.ts type TernaryDarkMode = "system" | "dark" | "light"; interface UseTernaryDarkModeOutput { isDarkMode: boolean; ternaryDarkMode: TernaryDarkMode; setTernaryDarkMode: Dispatch<SetStateAction<TernaryDarkMode>>; toggleTernaryDarkMode: () => void; } /** * * @returns * @example * import React from 'react' * import { useTernaryDarkMode } from '@gmana/hook' * * export default function Component() { * const { * isDarkMode, * ternaryDarkMode, * setTernaryDarkMode, * toggleTernaryDarkMode, * } = useTernaryDarkMode() * type TernaryDarkMode = typeof ternaryDarkMode * * return ( * <div> * <p>Current theme: {isDarkMode ? 'dark' : 'light'}</p> * <p>ternaryMode: {ternaryDarkMode}</p> * <p> * Toggle between three modes * <button onClick={toggleTernaryDarkMode}> * Toggle from {ternaryDarkMode} * </button> * </p> * <p> * Select a mode * <br /> * <select * name="select-ternaryDarkMode" * onChange={ev => * setTernaryDarkMode(ev.target.value as TernaryDarkMode) * } * value={ternaryDarkMode} * > * <option value="light">light</option> * <option value="system">system</option> * <option value="dark">dark</option> * </select> * </p> * </div> * ) * } * */ declare function useTernaryDarkMode(): UseTernaryDarkModeOutput; //#endregion //#region src/lib/use-throttle.d.ts /** * Throttles a callback function, ensuring it's called at most once per wait milliseconds. * Unlike debouncing, throttling ensures the function is executed regularly at intervals. * * @param callback - The function to throttle * @param delay - The number of milliseconds to throttle executions to * @param deps - Dependency array for the callback * @returns A throttled version of the callback * * @example * ```tsx * import { useThrottle } from '@gmana/react-hooks' * import { useState } from 'react' * * function ScrollComponent() { * const [scrollY, setScrollY] = useState(0) * * const throttledScroll = useThrottle( * () => setScrollY(window.scrollY), * 100, // Update scroll position at most every 100ms * [setScrollY] * ) * * React.useEffect(() => { * window.addEventListener('scroll', throttledScroll) * return () => window.removeEventListener('scroll', throttledScroll) * }, [throttledScroll]) * * return <div>Scroll Y: {scrollY}</div> * } * ``` */ declare function useThrottle<T extends (...args: never[]) => unknown>(callback: T, delay: number, deps?: React.DependencyList): T; //#endregion //#region src/lib/use-timeout.d.ts /** * @example * ```ts * import { useState } from "react" * import useTimeout from "@gmana/use-timeout" * * export default function TimeoutComponent() { * const [count, setCount] = useState(10) * const { clear, reset } = useTimeout(() => setCount(0), 1000) * * return ( * <div> * <div>{count}</div> * <button onClick={() => setCount(c => c + 1)}>Increment</button> * <button onClick={clear}>Clear Timeout</button> * <button onClick={reset}>Reset Timeout</button> * </div> * ) * } * ``` */ declare function useTimeout(callback: () => void, delay: number): { reset: () => void; clear: () => void; }; //#endregion //#region src/lib/use-toggle.d.ts /** * * @param defaultValue * @returns * ```ts * import useToggle from "@gmana/use-toggle" * * export default function ToggleComponent() { * const [value, toggleValue] = useToggle(false) * * return ( * <div> * <div>{value.toString()}</div> * <button onClick={toggleValue}>Toggle</button> * <button onClick={() => toggleValue(true)}>Make True</button> * <button onClick={() => toggleValue(false)}>Make False</button> * </div> * ) * } * ``` */ declare function useToggle(defaultValue: boolean): (boolean | ((value: boolean) => void))[]; //#endregion //#region src/lib/use-toggle-state.d.ts type StateType = [boolean, () => void, () => void, () => void] & { state: boolean; open: () => void; close: () => void; toggle: () => void; }; //#endregion //#region src/lib/use-unload-warning.d.ts declare function useUnloadWarning(condition?: boolean): void; //#endregion //#region src/lib/use-update-effect.d.ts /** * * @param effect * @param deps * @example * import React, { useEffect, useState } from 'react' * import { useUpdateEffect } from '@gmana/hook' * * export default function Component() { * const [data, setData] = useState<number>(0) * useEffect(() => { * console.log('Normal useEffect', { data }) * }, [data]) * * useUpdateEffect(() => { * console.log('Update useEffect only', { data }) * }, [data]) * * return ( * <div> * <p>Open your console</p> * <button onClick={() => setData(Date.now())}>Update data</button> * </div> * ) * } */ declare function useUpdateEffect(effect: EffectCallback, deps?: DependencyList): void; //#endregion //#region src/lib/use-window-size.d.ts interface WindowSize { width: number; height: number; } /** * * @returns * @example * import React from 'react' * import { useWindowSize } from '@gmana/hook' * * export default function Component() { * const { width, height } = useWindowSize() * * return ( * <div> * The current window dimensions are:{' '} * <code>{JSON.stringify({ width, height })}</code> * </div> * ) * } * */ declare function useWindowSize(): WindowSize; //#endregion export { Actions, DebouncedFunction, INetworkInformation, MapOrEntries, ScriptElt, StateType, Status, UseCountdownOptions, UseCountdownReturn, UseDebounceOptions, UseDebounceReturn, UseDebouncedCallbackOptions, getCookie, removeCookie, setCookie, useArray, useAsyncEffect, useBoolean, useClipboard, useCopyToClipboard, useCountdown, useCounter, useDarkMode, useDebounce, useDebouncedCallback, useDimensions, useEffectOnce, useElementSize, useEstimateStorage, useEventListener, useFetch, useFocusTrap, useHover, useHydrated, useImageOnLoad, useIntersection, useIntersectionObserver, useInterval, useIsClient, useIsFirstRender, useIsMobile, useIsMounted, useIsomorphicLayoutEffect, useLocalStorage, useLockedBody, useMap, useMediaQuery, useNetworkInformation, useOfflineDetector, useOnClickOutside, usePrevious, useReadLocalStorage, useScreen, useScript, useScroll, useSidebar, useSsr, useStep, useTernaryDarkMode, useThrottle, useTimeout, useToggle, useUnloadWarning, useUpdateEffect, useWindowSize };