nextjs-app-hooks
Version:
A library of custom React hooks for simplified state management and streamlined component logic.
1,954 lines (1,939 loc) • 91.8 kB
text/typescript
import { RefObject } from 'react';
/**
* Hook to determine if browser APIs are safely available
*
* @returns {boolean} - Returns true if running in the browser, false if on the server
*
* @example
* ```tsx
* 'use client';
*
* import { useIsBrowser } from 'nextjs-app-hooks';
*
* export default function MyComponent() {
* const isBrowser = useIsBrowser();
*
* useEffect(() => {
* if (isBrowser) {
* // Safe to use browser APIs like localStorage, window, etc.
* localStorage.setItem('visited', 'true');
* }
* }, [isBrowser]);
*
* return (
* <div>
* {isBrowser ? 'Browser APIs are available' : 'Running on the server'}
* </div>
* );
* }
* ```
*/
declare function useIsBrowser(): boolean;
/**
* Hook to detect if the code is running on the server or client
*
* @returns {boolean} - Returns true if running on the server, false if running on the client
*
* @example
* ```tsx
* 'use client';
*
* import { useIsServer } from 'nextjs-app-hooks';
*
* export default function MyComponent() {
* const isServer = useIsServer();
*
* return (
* <div>
* {isServer ? 'Rendering on server' : 'Rendering on client'}
* </div>
* );
* }
* ```
*/
declare function useIsServer(): boolean;
/**
* Battery status information
*/
interface BatteryStatus {
/**
* Whether the battery is currently charging
*/
charging: boolean;
/**
* Estimated time in seconds until the battery is fully charged
* Returns Infinity if the battery is discharging
*/
chargingTime: number;
/**
* Estimated time in seconds until the battery is fully discharged
* Returns Infinity if the battery is charging
*/
dischargingTime: number;
/**
* Current battery level, from 0.0 to 1.0
*/
level: number;
/**
* Whether the device is connected to power
* This can be true even when charging is false (e.g., battery is full)
*/
connected: boolean;
}
/**
* Hook state for battery information
*/
interface BatteryHookState {
/**
* Whether the Battery Status API is supported by the browser
*/
isSupported: boolean;
/**
* Whether the battery information is currently loading
*/
isLoading: boolean;
/**
* Any error that occurred while accessing the battery
*/
error: Error | null;
/**
* The current battery status information
* Will be null if API is not supported, loading, or an error occurred
*/
battery: BatteryStatus | null;
}
/**
* A hook to access and monitor the device's battery status
*
* @returns {BatteryHookState} The current battery state and status information
*/
declare function useBattery(): BatteryHookState;
/**
* Hook options for configuring the click outside detection
*/
interface UseClickOutsideOptions {
/**
* Whether the hook is enabled - can be used to conditionally enable/disable
* @default true
*/
enabled?: boolean;
/**
* Additional elements to ignore when detecting clicks outside
* Useful for linked components (e.g., a button that toggles a dropdown)
*/
ignoreRefs?: RefObject<HTMLElement>[];
/**
* Mouse events to listen for
* @default ["mousedown"]
*/
mouseEvents?: Array<"mousedown" | "mouseup" | "click">;
/**
* Whether to listen for touch events (for mobile devices)
* @default true
*/
listenForTouchEvents?: boolean;
/**
* Whether to ignore scrollbar clicks
* @default true
*/
ignoreScrollbar?: boolean;
}
/**
* A hook that detects clicks outside of a specified element
*
* @param onClickOutside - Callback function to be called when a click outside is detected
* @param options - Configuration options for the hook
* @returns RefObject to be attached to the element we want to detect clicks outside of
*
* @example
* ```tsx
* const Modal = ({ isOpen, onClose }) => {
* const modalRef = useClickOutside(() => {
* if (isOpen) onClose();
* });
*
* if (!isOpen) return null;
*
* return (
* <div className="modal-overlay">
* <div ref={modalRef} className="modal-content">
* Modal content here
* </div>
* </div>
* );
* };
* ```
*/
declare function useClickOutside<T extends HTMLElement = HTMLElement>(onClickOutside: (event: MouseEvent | TouchEvent) => void, options?: UseClickOutsideOptions): RefObject<T | null>;
/**
* An extended version of useClickOutside that maintains an internal isOpen state
*
* @param initialState - Initial open state
* @param options - Configuration options for the hook
* @returns [ref, isOpen, setIsOpen] - The element ref, current state, and state setter
*
* @example
* ```tsx
* const Dropdown = () => {
* const [dropdownRef, isOpen, setIsOpen] = useClickOutsideState(false);
*
* return (
* <div>
* <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
* {isOpen && (
* <div ref={dropdownRef} className="dropdown-menu">
* Dropdown content
* </div>
* )}
* </div>
* );
* };
* ```
*/
declare function useClickOutsideState<T extends HTMLElement = HTMLElement>(initialState?: boolean, options?: UseClickOutsideOptions): [RefObject<T>, boolean, React.Dispatch<React.SetStateAction<boolean>>];
/**
* Options for configuring the clipboard hook
*/
interface UseClipboardOptions {
/**
* Duration in milliseconds to show success/error status before resetting
* @default 2000
*/
resetDelay?: number;
/**
* Whether to automatically reset the status after copy
* @default true
*/
autoReset?: boolean;
/**
* Initial text value to be copied
* @default ""
*/
initialValue?: string;
/**
* Whether to use only the modern Clipboard API (no fallbacks)
* @default false
*/
modernOnly?: boolean;
/**
* Callback function to execute on successful copy
*/
onCopySuccess?: (value: string) => void;
/**
* Callback function to execute on copy failure
*/
onCopyError?: (error: Error) => void;
}
/**
* Status of the clipboard operation
*/
type ClipboardStatus = "idle" | "copied" | "error";
/**
* Return type for the useClipboard hook
*/
interface UseClipboardReturn {
/**
* Function to copy text to clipboard
* @param text - The text to copy (optional if initialValue is set)
*/
copyToClipboard: (text?: string) => Promise<boolean>;
/**
* Current value that was copied or will be copied
*/
value: string;
/**
* Current status of clipboard operation
*/
status: ClipboardStatus;
/**
* Whether the copy operation was successful
*/
isSuccess: boolean;
/**
* Whether the copy operation failed
*/
isError: boolean;
/**
* Function to manually reset status to idle
*/
reset: () => void;
/**
* Error object if copy operation failed
*/
error: Error | null;
/**
* Whether clipboard API is supported in current environment
*/
isSupported: boolean;
}
/**
* A hook for copying text to the clipboard with status tracking
*
* @param options - Configuration options for the hook
* @returns Object with clipboard functions and state
*
* @example
* ```tsx
* 'use client';
*
* import { useClipboard } from 'nextjs-app-hooks';
*
* export default function CopyButton() {
* const { copyToClipboard, isSuccess, status } = useClipboard();
*
* return (
* <button
* onClick={() => copyToClipboard("Text to copy")}
* className={isSuccess ? "bg-green-500" : "bg-blue-500"}
* >
* {status === 'idle' && 'Copy to clipboard'}
* {status === 'copied' && 'Copied!'}
* {status === 'error' && 'Failed to copy'}
* </button>
* );
* }
* ```
*
* @example
* ```tsx
* // With initial value
* const { copyToClipboard, value, isSuccess } = useClipboard({
* initialValue: "https://example.com",
* resetDelay: 3000
* });
*
* return (
* <div>
* <div>Link: {value}</div>
* <button onClick={() => copyToClipboard()}>
* {isSuccess ? 'Copied!' : 'Copy Link'}
* </button>
* </div>
* );
* ```
*/
declare function useClipboard(options?: UseClipboardOptions): UseClipboardReturn;
/**
* Options for configuring the debounce hook
*/
interface UseDebounceOptions {
/**
* Delay in milliseconds before the debounced function is called
* @default 500
*/
delay?: number;
/**
* Whether to call the function on the leading edge of the timeout
* If true, function is called immediately, then wait for delay before it can be called again
* If false, function is called after the delay when inputs stop changing
* @default false
*/
leading?: boolean;
/**
* Maximum time in milliseconds allowed to elapse between calls
* If specified, the function will be called at least once every maxWait ms
* @default undefined
*/
maxWait?: number;
/**
* Whether to track the pending status of the debounced function
* @default false
*/
trackPending?: boolean;
}
/**
* A hook for creating a debounced function
*
* @param fn - The function to debounce
* @param options - Configuration options for debouncing
* @returns An array containing the debounced function, cancel function, pending status, and flush function
*
* @example
* ```tsx
* 'use client';
*
* import { useDebounce } from 'nextjs-app-hooks';
*
* export default function SearchInput() {
* const [searchTerm, setSearchTerm] = useState('');
* const [results, setResults] = useState([]);
*
* const searchAPI = async (term) => {
* const response = await fetch(`/api/search?q=${term}`);
* const data = await response.json();
* setResults(data);
* };
*
* const [debouncedSearch, cancelSearch, isPending] = useDebounce(searchAPI, {
* delay: 300,
* trackPending: true
* });
*
* const handleInputChange = (e) => {
* const value = e.target.value;
* setSearchTerm(value);
* debouncedSearch(value);
* };
*
* return (
* <div>
* <input
* type="text"
* value={searchTerm}
* onChange={handleInputChange}
* placeholder="Search..."
* />
* {isPending && <span>Searching...</span>}
* <ul>
* {results.map(result => (
* <li key={result.id}>{result.name}</li>
* ))}
* </ul>
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With leading edge execution
* const [debouncedHandleResize] = useDebounce(() => {
* // Update layout dimensions
* setDimensions({
* width: window.innerWidth,
* height: window.innerHeight
* });
* }, { delay: 200, leading: true });
*
* useEffect(() => {
* window.addEventListener('resize', debouncedHandleResize);
* return () => window.removeEventListener('resize', debouncedHandleResize);
* }, [debouncedHandleResize]);
* ```
*/
declare function useDebounce<T extends (...args: any[]) => any>(fn: T, options?: UseDebounceOptions): [
(...args: Parameters<T>) => void,
() => void,
boolean,
() => ReturnType<T> | undefined
];
/**
* Extended geolocation options
*/
interface UseGeolocationOptions extends Omit<PositionOptions, "timeout"> {
/**
* Whether to watch position continuously or just get it once
* @default false
*/
watchPosition?: boolean;
/**
* Timeout for position request in milliseconds
* @default 10000 (10 seconds)
*/
timeout?: number;
/**
* Whether to automatically request permission and position on mount
* @default true
*/
autoRequest?: boolean;
/**
* Callback for successful position updates
*/
onSuccess?: (position: GeolocationPosition) => void;
/**
* Callback for position errors
*/
onError?: (error: GeolocationPositionError | Error) => void;
/**
* Permission change callback
*/
onPermissionChange?: (state: GeolocationPermissionState) => void;
}
/**
* Simplified geolocation position
*/
interface GeolocationPosition {
/**
* Latitude in decimal degrees
*/
latitude: number;
/**
* Longitude in decimal degrees
*/
longitude: number;
/**
* Accuracy of latitude and longitude in meters
*/
accuracy: number;
/**
* Altitude in meters above the WGS84 ellipsoid
*/
altitude: number | null;
/**
* Accuracy of altitude in meters
*/
altitudeAccuracy: number | null;
/**
* Direction of travel in degrees clockwise from true north
*/
heading: number | null;
/**
* Current velocity in meters per second
*/
speed: number | null;
/**
* Timestamp when position was acquired
*/
timestamp: number;
}
/**
* Geolocation permission states
*/
type GeolocationPermissionState = "granted" | "denied" | "prompt" | "unknown";
/**
* Return type for the useGeolocation hook
*/
interface UseGeolocationReturn {
/**
* Current position data, null if not available
*/
position: GeolocationPosition | null;
/**
* Error object if geolocation failed
*/
error: GeolocationPositionError | Error | null;
/**
* Whether the geolocation request is in progress
*/
isLoading: boolean;
/**
* Whether position watching is active
*/
isWatching: boolean;
/**
* Current permission state for geolocation
*/
permissionState: GeolocationPermissionState;
/**
* Function to request current position
*/
getPosition: () => Promise<GeolocationPosition>;
/**
* Function to start watching position updates
*/
watchPosition: () => void;
/**
* Function to stop watching position updates
*/
stopWatching: () => void;
/**
* Whether browser supports geolocation API
*/
isSupported: boolean;
}
/**
* A hook for accessing and tracking geolocation data
*
* @param options - Configuration options for geolocation requests
* @returns Object with position data, status, and control functions
*
* @example
* ```tsx
* 'use client';
*
* import { useGeolocation } from 'nextjs-app-hooks';
*
* export default function LocationComponent() {
* const {
* position,
* error,
* isLoading,
* getPosition,
* isSupported
* } = useGeolocation({ enableHighAccuracy: true });
*
* if (!isSupported) {
* return <p>Geolocation is not supported in your browser.</p>;
* }
*
* return (
* <div>
* <button onClick={getPosition} disabled={isLoading}>
* {isLoading ? 'Getting location...' : 'Get My Location'}
* </button>
*
* {position && (
* <div>
* <p>Latitude: {position.latitude}</p>
* <p>Longitude: {position.longitude}</p>
* <p>Accuracy: {position.accuracy} meters</p>
* </div>
* )}
*
* {error && <p>Error: {error.message}</p>}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // Continuous position watching
* const { position, isWatching, watchPosition, stopWatching } = useGeolocation({
* watchPosition: true,
* enableHighAccuracy: true,
* timeout: 15000
* });
*
* return (
* <div>
* <div>
* {isWatching ? (
* <button onClick={stopWatching}>Stop tracking</button>
* ) : (
* <button onClick={watchPosition}>Start tracking</button>
* )}
* </div>
*
* {position && (
* <div>
* <p>Current position:</p>
* <p>Lat: {position.latitude.toFixed(6)}, Lng: {position.longitude.toFixed(6)}</p>
* {position.speed !== null && <p>Speed: {position.speed} m/s</p>}
* </div>
* )}
* </div>
* );
* ```
*/
declare function useGeolocation(options?: UseGeolocationOptions): UseGeolocationReturn;
/**
* Options for configuring the hover hook
*/
interface UseHoverOptions {
/**
* Delay in milliseconds before hover state activates
* @default 0
*/
enterDelay?: number;
/**
* Delay in milliseconds before hover state deactivates
* @default 0
*/
leaveDelay?: number;
/**
* Whether the hover detection is enabled
* @default true
*/
enabled?: boolean;
/**
* Whether to handle touch events as hover events
* @default false
*/
supportTouch?: boolean;
/**
* Callback when hover begins
*/
onHoverStart?: (event: MouseEvent | TouchEvent) => void;
/**
* Callback when hover ends
*/
onHoverEnd?: (event: MouseEvent | TouchEvent) => void;
/**
* Callback when disabled/enabled state changes
*/
onEnabledChange?: (enabled: boolean) => void;
}
/**
* Return type for the useHover hook
*/
interface UseHoverReturn<T extends HTMLElement = HTMLDivElement> {
/**
* Ref to attach to the element to track hover state
*/
hoverRef: RefObject<T | null>;
/**
* Whether the element is currently being hovered
*/
isHovered: boolean;
/**
* Function to enable hover detection
*/
enable: () => void;
/**
* Function to disable hover detection
*/
disable: () => void;
/**
* Whether hover detection is currently enabled
*/
isEnabled: boolean;
}
/**
* A hook for tracking hover state of an element
*
* @param options - Configuration options for hover detection
* @returns Object with hover ref, state, and control functions
*
* @example
* ```tsx
* 'use client';
*
* import { useHover } from 'nextjs-app-hooks';
*
* export default function HoverCard() {
* const { hoverRef, isHovered } = useHover<HTMLDivElement>({
* enterDelay: 100,
* leaveDelay: 300
* });
*
* return (
* <div
* ref={hoverRef}
* className={`card ${isHovered ? 'card-hovered' : ''}`}
* >
* Hover over me!
* {isHovered && <div className="tooltip">Hello there!</div>}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With callbacks and manual control
* const { hoverRef, isHovered, disable, enable } = useHover({
* onHoverStart: () => console.log('Hover started'),
* onHoverEnd: () => console.log('Hover ended')
* });
*
* return (
* <>
* <button onClick={isHovered ? disable : enable}>
* {isHovered ? 'Disable' : 'Enable'} hover
* </button>
* <div ref={hoverRef} className="hover-element">
* {isHovered ? 'Hovered!' : 'Hover me'}
* </div>
* </>
* );
* ```
*/
declare function useHover<T extends HTMLElement = HTMLDivElement>(options?: UseHoverOptions): UseHoverReturn<T>;
/**
* Events that can reset the idle timer
*/
type IdleEvents = "mousemove" | "mousedown" | "resize" | "keydown" | "touchstart" | "wheel" | "visibilitychange" | "scroll";
/**
* Options for configuring the idle hook
*/
interface UseIdleOptions {
/**
* Idle timeout in milliseconds
* @default 60000 (1 minute)
*/
idleTime?: number;
/**
* Initial idle state
* @default false
*/
initialState?: boolean;
/**
* Events that reset the idle timer
* @default ["mousemove", "mousedown", "resize", "keydown", "touchstart", "wheel"]
*/
events?: IdleEvents[];
/**
* Minimum mouse movement in pixels to reset the idle timer
* @default 10
*/
minimumMovement?: number;
/**
* Whether to track idle state when the document is hidden (tab inactive)
* @default true
*/
trackInBackground?: boolean;
/**
* Callback when user becomes idle
*/
onIdle?: () => void;
/**
* Callback when user becomes active
*/
onActive?: () => void;
/**
* Whether the idle detection is enabled
* @default true
*/
enabled?: boolean;
/**
* Whether to use document visibility to immediately set to idle
* @default false
*/
idleOnVisibilityHidden?: boolean;
/**
* Whether to store and sync idle state in localStorage
* Useful for syncing across tabs
* @default false
*/
syncWithStorage?: boolean;
/**
* Key to use for localStorage syncing
* @default "user-idle-state"
*/
storageKey?: string;
/**
* Whether to emit a browser event when idle state changes
* Useful for cross-component communication
* @default false
*/
emitEvents?: boolean;
/**
* Event name for idle state change
* @default "user-idle-state-change"
*/
eventName?: string;
/**
* How frequently to check for idle time in milliseconds
* Lower values are more accurate but less performant
* @default 1000
*/
pollingInterval?: number;
}
/**
* Return type for the useIdle hook
*/
interface UseIdleReturn {
/**
* Whether the user is currently idle
*/
isIdle: boolean;
/**
* Time in milliseconds since the last activity
*/
idleTime: number;
/**
* Time in milliseconds until the user will be considered idle
*/
remainingTime: number;
/**
* Timestamp of last activity
*/
lastActive: number;
/**
* Percentage of idle timeout passed (0-100)
*/
idlePercentage: number;
/**
* Function to manually set the user to idle
*/
setIdle: () => void;
/**
* Function to manually set the user to active
*/
setActive: () => void;
/**
* Function to manually reset the idle timer
*/
reset: () => void;
/**
* Function to enable idle detection
*/
enable: () => void;
/**
* Function to disable idle detection
*/
disable: () => void;
/**
* Whether idle detection is currently enabled
*/
isEnabled: boolean;
}
/**
* A hook for tracking user idle state
*
* @param options - Configuration options for idle detection
* @returns Object with idle state and control functions
*
* @example
* ```tsx
* 'use client';
*
* import { useIdle } from 'nextjs-app-hooks';
*
* export default function IdleDetectionExample() {
* const { isIdle, reset } = useIdle({
* idleTime: 10000, // 10 seconds
* onIdle: () => console.log('User is idle'),
* onActive: () => console.log('User is active')
* });
*
* return (
* <div>
* <p>User is currently {isIdle ? 'idle' : 'active'}</p>
* {isIdle && (
* <button onClick={reset}>
* I'm still here!
* </button>
* )}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With custom events and tracking options
* const { isIdle, idlePercentage, remainingTime } = useIdle({
* idleTime: 5 * 60 * 1000, // 5 minutes
* events: ['mousemove', 'keydown', 'touchstart'],
* minimumMovement: 20,
* trackInBackground: false
* });
*
* return (
* <>
* <div className="idle-indicator">
* <div
* className="idle-progress"
* style={{ width: `${idlePercentage}%` }}
* />
* </div>
* <p>Session timeout in: {Math.ceil(remainingTime / 1000)}s</p>
* {isIdle && <SessionTimeoutModal />}
* </>
* );
* ```
*/
declare function useIdle(options?: UseIdleOptions): UseIdleReturn;
/**
* Options for configuring the intersection observer hook
*/
interface UseIntersectionObserverOptions {
/**
* The element that is used as the viewport for checking visibility
* If null, defaults to browser viewport
* @default null
*/
root?: Element | Document | null;
/**
* Margin around the root element
* Can have values similar to CSS margin property (e.g. "10px 20px 30px 40px")
* @default "0px"
*/
rootMargin?: string;
/**
* A threshold or array of thresholds between 0 and 1
* Each threshold indicates what percentage of the target's visibility
* triggers the observer callback
* @default 0
*/
threshold?: number | number[];
/**
* Callback function that is triggered when element enters the viewport
*/
onEnter?: (entry: IntersectionObserverEntry) => void;
/**
* Callback function that is triggered when element exits the viewport
*/
onExit?: (entry: IntersectionObserverEntry) => void;
/**
* Whether to trigger once and then disconnect the observer
* Useful for one-time animations or lazy loading
* @default false
*/
triggerOnce?: boolean;
/**
* Whether to skip creating the intersection observer
* Can be used to conditionally enable/disable the hook
* @default false
*/
skip?: boolean;
/**
* Initial value to use before the observer is attached
* @default false
*/
initialInView?: boolean;
/**
* Delay in milliseconds before observer is initialized
* Useful for cases where the element isn't immediately ready
* @default 0
*/
delay?: number;
/**
* Whether to track all intersection changes with detailed entries history
* @default false
*/
trackVisibilityChanges?: boolean;
/**
* Amount of entries to keep in history when tracking visibility changes
* @default 10
*/
historySize?: number;
/**
* Whether to use the "isIntersecting" property for intersection detection
* If false, will check if intersectionRatio > 0
* @default true
*/
useIsIntersecting?: boolean;
}
/**
* Return type for the useIntersectionObserver hook
*/
interface UseIntersectionObserverReturn<T extends Element> {
/**
* Ref to attach to the element to observe
*/
ref: RefObject<T>;
/**
* Whether the element is currently in view according to the threshold
*/
isInView: boolean;
/**
* Current intersection ratio (0 to 1)
*/
intersectionRatio: number;
/**
* The most recent IntersectionObserverEntry
*/
entry: IntersectionObserverEntry | null;
/**
* History of intersection entries if tracking is enabled
*/
entryHistory: IntersectionObserverEntry[];
/**
* Function to manually reset observation
*/
reset: () => void;
/**
* Function to disconnect the observer
*/
disconnect: () => void;
/**
* Function to reconnect the observer after disconnection
*/
observe: () => void;
/**
* Whether the observer is currently connected
*/
isConnected: boolean;
/**
* Time the element entered the viewport (most recent)
*/
enteredAt: number | null;
/**
* Time the element exited the viewport (most recent)
*/
exitedAt: number | null;
/**
* Duration in milliseconds the element has been in viewport
* Will be 0 if element is not in view
*/
inViewDuration: number;
}
/**
* A hook that uses the Intersection Observer API to track element visibility
*
* @param options - Configuration options for the intersection observer
* @returns Object with ref and intersection state
*
* @example
* ```tsx
* 'use client';
*
* import { useIntersectionObserver } from 'nextjs-app-hooks';
*
* export default function LazyImage() {
* const { ref, isInView } = useIntersectionObserver<HTMLDivElement>({
* threshold: 0.1,
* triggerOnce: true
* });
*
* return (
* <div ref={ref} className="image-container">
* {isInView ? (
* <img src="https://example.com/image.jpg" alt="Lazy loaded" />
* ) : (
* <div className="placeholder" />
* )}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With animation on scroll
* const { ref, isInView, intersectionRatio } = useIntersectionObserver({
* threshold: [0, 0.25, 0.5, 0.75, 1],
* rootMargin: "0px 0px -100px 0px" // Trigger 100px before element comes into view
* });
*
* return (
* <div
* ref={ref}
* className={`fade-in ${isInView ? 'visible' : 'hidden'}`}
* style={{ opacity: intersectionRatio }}
* >
* This content will fade in as it scrolls into view
* </div>
* );
* ```
*/
declare function useIntersectionObserver<T extends Element = HTMLDivElement>(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn<T>;
/**
* A hook that tracks whether the component has been rendered already
* It returns false on first render and true afterward
*
* @returns boolean - Whether the component has rendered before
*
* @example
* ```tsx
* 'use client';
*
* import { useHasRendered } from 'nextjs-app-hooks';
*
* export default function MyComponent() {
* const hasRendered = useHasRendered();
* const [data, setData] = useState(null);
*
* useEffect(() => {
* if (!hasRendered) {
* // Only fetch data on the first render
* fetchData().then(setData);
* }
* }, [hasRendered]);
*
* return (
* <div>
* {!hasRendered ? 'Loading...' : 'Data loaded'}
* {data && <DataDisplay data={data} />}
* </div>
* );
* }
* ```
*/
declare function useHasRendered(): boolean;
/**
* Options for configuring the body scroll lock
*/
interface UseLockBodyScrollOptions {
/**
* Whether to disable the scroll lock
* @default false
*/
disabled?: boolean;
/**
* The element to apply the overflow: hidden to (default is document.body)
* @default document.body
*/
targetElement?: HTMLElement | null;
/**
* Whether to preserve the scroll position when unlocking
* @default true
*/
preserveScrollPosition?: boolean;
/**
* Whether to reserve space for scrollbar to prevent layout shift
* @default true
*/
reserveScrollbarGap?: boolean;
}
/**
* Return type for the useLockBodyScroll hook
*/
interface UseLockBodyScrollReturn {
/**
* Whether the scroll is currently locked
*/
isLocked: boolean;
/**
* Function to lock the body scroll
*/
lock: () => void;
/**
* Function to unlock the body scroll
*/
unlock: () => void;
/**
* Function to toggle between locked and unlocked states
*/
toggle: () => void;
}
/**
* A hook that locks/unlocks body scrolling for modals, drawers, etc.
*
* @param initialLocked - Whether scrolling should be locked initially
* @param options - Configuration options for the hook
* @returns Object with lock state and control functions
*
* @example
* ```tsx
* // Simple usage with a modal
* function Modal({ isOpen, onClose, children }) {
* // Lock scrolling when modal is open
* useLockBodyScroll(isOpen);
*
* if (!isOpen) return null;
*
* return (
* <div className="modal-overlay">
* <div className="modal-content">
* {children}
* <button onClick={onClose}>Close</button>
* </div>
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With manual control
* function ScrollToggleComponent() {
* const { isLocked, toggle } = useLockBodyScroll(false);
*
* return (
* <button onClick={toggle}>
* {isLocked ? 'Enable Scrolling' : 'Disable Scrolling'}
* </button>
* );
* }
* ```
*/
declare function useLockBodyScroll(initialLocked?: boolean, options?: UseLockBodyScrollOptions): UseLockBodyScrollReturn;
/**
* Position coordinates for tracking press movement
*/
interface PressPosition {
x: number;
y: number;
}
/**
* Movement data including distance and direction
*/
interface PressMovement {
/**
* Distance moved from initial press position in pixels
*/
distance: number;
/**
* Horizontal movement from initial press position in pixels
*/
deltaX: number;
/**
* Vertical movement from initial press position in pixels
*/
deltaY: number;
/**
* Primary direction of movement ('up', 'down', 'left', 'right')
*/
direction: "up" | "down" | "left" | "right" | null;
}
/**
* Options for configuring the long press hook
*/
interface UseLongPressOptions {
/**
* Duration in milliseconds before a press is considered "long"
* @default 500
*/
delay?: number;
/**
* Whether to disable the long press detection
* @default false
*/
disabled?: boolean;
/**
* Whether to prevent default browser behavior on events
* @default true
*/
preventDefault?: boolean;
/**
* Whether to stop event propagation
* @default false
*/
stopPropagation?: boolean;
/**
* Whether to detect mouse events in addition to touch events
* @default true
*/
detectMouse?: boolean;
/**
* How many pixels the user can move while pressing before canceling the long press
* @default 10
*/
moveTolerance?: number;
/**
* Whether to enable vibration feedback when long press is detected (mobile only)
* @default false
*/
vibrate?: boolean;
/**
* Vibration pattern in milliseconds
* @default [100]
*/
vibrationPattern?: number[];
/**
* Whether to continue triggering while holding after initial long press
* @default false
*/
continuousLongPress?: boolean;
/**
* Interval in milliseconds for continuous long press events
* @default 100
*/
continuousLongPressInterval?: number;
/**
* Callback when the user starts pressing (before long press threshold)
*/
onPressStart?: (position: PressPosition) => void;
/**
* Callback when the user moves while pressing
*/
onPressMove?: (movement: PressMovement) => void;
/**
* Callback when the long press is triggered
*/
onLongPress?: (position: PressPosition, movement: PressMovement) => void;
/**
* Callback when the long press ends (user releases after long press)
*/
onLongPressEnd?: (position: PressPosition, movement: PressMovement) => void;
/**
* Callback when the press is canceled before reaching threshold
*/
onPressCancel?: () => void;
/**
* Callback when a normal click/tap is performed (press and release before threshold)
*/
onClick?: (e: React.MouseEvent | React.TouchEvent) => void;
}
/**
* Return type for the useLongPress hook
*/
interface UseLongPressReturn {
/**
* Whether the element is currently being pressed
*/
isPressed: boolean;
/**
* Whether the long press threshold has been reached
*/
isLongPressed: boolean;
/**
* Object of handler props to spread onto the target element
*/
handlers: {
onMouseDown: (e: React.MouseEvent) => void;
onMouseMove: (e: React.MouseEvent) => void;
onMouseUp: (e: React.MouseEvent) => void;
onMouseLeave: (e: React.MouseEvent) => void;
onTouchStart: (e: React.TouchEvent) => void;
onTouchMove: (e: React.TouchEvent) => void;
onTouchEnd: (e: React.TouchEvent) => void;
onContextMenu: (e: React.MouseEvent) => void;
onClick: (e: React.MouseEvent) => void;
};
/**
* Current position data during a press
*/
position: PressPosition | null;
/**
* Current movement data during a press
*/
movement: PressMovement | null;
/**
* Manually trigger the long press
*/
triggerLongPress: () => void;
/**
* Manually cancel the long press detection
*/
cancelLongPress: () => void;
/**
* Reset all states to default values
*/
reset: () => void;
}
/**
* A hook for detecting long press gestures with extensive configuration options
*
* @param callback - Function to call when long press is detected
* @param options - Configuration options for the hook
* @returns Object with handler functions and state information
*
* @example
* ```tsx
* // Basic usage
* function LongPressButton() {
* const onLongPress = () => alert('Long pressed!');
*
* const { handlers, isLongPressed } = useLongPress(onLongPress);
*
* return (
* <button
* {...handlers}
* className={isLongPressed ? 'active' : ''}
* >
* Press and hold me
* </button>
* );
* }
* ```
*
* @example
* ```tsx
* // Advanced usage with movement tracking
* function DragButton() {
* const [feedback, setFeedback] = useState('');
*
* const { handlers, isPressed, movement } = useLongPress(null, {
* moveTolerance: 50,
* onPressStart: () => setFeedback('Press started'),
* onPressMove: (data) => setFeedback(`Moving: ${data.direction} (${data.deltaX}, ${data.deltaY})`),
* onLongPress: () => setFeedback('Long press detected!'),
* continuousLongPress: true
* });
*
* return (
* <div>
* <button {...handlers} className={isPressed ? 'active' : ''}>
* Drag me around
* </button>
* <div>{feedback}</div>
* </div>
* );
* }
* ```
*/
declare function useLongPress(callback?: ((e: React.MouseEvent | React.TouchEvent) => void) | null, options?: UseLongPressOptions): UseLongPressReturn;
/**
* Predefined breakpoint values (in pixels) for responsive design
*/
declare const breakpoints: {
readonly xs: 0;
readonly sm: 576;
readonly md: 768;
readonly lg: 992;
readonly xl: 1200;
readonly xxl: 1400;
};
/**
* Breakpoint keys type
*/
type Breakpoint = keyof typeof breakpoints;
/**
* Media query features that can be used with the hook
*/
interface MediaQueryFeatures {
/**
* Min-width in pixels or as a breakpoint key
*/
minWidth?: number | Breakpoint;
/**
* Max-width in pixels or as a breakpoint key
*/
maxWidth?: number | Breakpoint;
/**
* Min-height in pixels
*/
minHeight?: number;
/**
* Max-height in pixels
*/
maxHeight?: number;
/**
* Device orientation
*/
orientation?: "portrait" | "landscape";
/**
* Preferred color scheme
*/
prefersColorScheme?: "dark" | "light";
/**
* Preferred reduced motion setting
*/
prefersReducedMotion?: boolean;
/**
* Display mode of the application
*/
displayMode?: "browser" | "standalone" | "minimal-ui" | "fullscreen";
/**
* Custom media query string (will be used directly)
*/
custom?: string;
}
/**
* Options for the useMediaQuery hook
*/
interface UseMediaQueryOptions {
/**
* Default value to use on the server or when disabled
* @default false
*/
defaultValue?: boolean;
/**
* Whether to disable the media query matching
* @default false
*/
disabled?: boolean;
/**
* Function to call when the media query match changes
*/
onChange?: (matches: boolean) => void;
/**
* Whether to initialize with the default value instead of immediately checking
* @default false
*/
initializeWithDefaultValue?: boolean;
/**
* Whether to convert the query to use em units instead of pixels
* @default false
*/
useEm?: boolean;
/**
* Base font size in pixels, used when converting to em units
* @default 16
*/
baseFontSize?: number;
}
/**
* Return type for the useMediaQuery hook
*/
interface UseMediaQueryReturn {
/**
* Whether the media query matches
*/
matches: boolean;
/**
* The media query string being used
*/
query: string;
/**
* Update the media query features
*/
updateQuery: (features: MediaQueryFeatures) => void;
}
/**
* A hook for responding to media queries with extensive configuration options
*
* @param features - Media query features or a direct media query string
* @param options - Configuration options for the hook
* @returns Object with current match state and utility functions
*
* @example
* ```tsx
* // Basic usage with breakpoints
* function ResponsiveComponent() {
* const isMobile = useMediaQuery({ maxWidth: 'sm' });
* const isTablet = useMediaQuery({ minWidth: 'md', maxWidth: 'lg' });
* const isDesktop = useMediaQuery({ minWidth: 'xl' });
*
* return (
* <div>
* {isMobile.matches && <MobileView />}
* {isTablet.matches && <TabletView />}
* {isDesktop.matches && <DesktopView />}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // Advanced usage with multiple features
* function ThemeAwareComponent() {
* const { matches: isDarkMode } = useMediaQuery({
* prefersColorScheme: 'dark',
* });
*
* const { matches: isReducedMotion } = useMediaQuery({
* prefersReducedMotion: true
* });
*
* return (
* <div className={isDarkMode ? 'dark-theme' : 'light-theme'}>
* <Animation disabled={isReducedMotion} />
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With custom query
* function PrintView() {
* const { matches: isPrinting } = useMediaQuery({
* custom: 'print'
* });
*
* return (
* <div>
* {isPrinting ? 'Optimized for printing' : 'Screen view'}
* </div>
* );
* }
* ```
*/
declare function useMediaQuery(features: MediaQueryFeatures | string, options?: UseMediaQueryOptions): UseMediaQueryReturn;
/**
* Hook that matches multiple media queries and returns their states
*
* @param queries - Object with named media query features
* @param options - Configuration options for the hook
* @returns Object with match states for each query
*
* @example
* ```tsx
* function ResponsiveLayout() {
* const { mobile, tablet, desktop } = useMediaQueries({
* mobile: { maxWidth: 'sm' },
* tablet: { minWidth: 'md', maxWidth: 'lg' },
* desktop: { minWidth: 'xl' }
* });
*
* return (
* <div className={`layout ${mobile ? 'mobile' : ''} ${tablet ? 'tablet' : ''} ${desktop ? 'desktop' : ''}`}>
* {mobile && <MobileMenu />}
* {(tablet || desktop) && <DesktopMenu />}
* <main>Content</main>
* </div>
* );
* }
* ```
*/
declare function useMediaQueries<T extends Record<string, MediaQueryFeatures | string>>(queries: T, options?: UseMediaQueryOptions): {
[K in keyof T]: boolean;
};
/**
* Convenience hook for responsive design using predefined breakpoints
*
* @param options - Configuration options for the hook
* @returns Object with boolean flags for different screen sizes
*
* @example
* ```tsx
* function App() {
* const { isMobile, isTablet, isDesktop, isLargeDesktop } = useResponsive();
*
* return (
* <div>
* <h1>Current viewport:</h1>
* {isMobile && <p>Mobile</p>}
* {isTablet && <p>Tablet</p>}
* {isDesktop && <p>Desktop</p>}
* {isLargeDesktop && <p>Large Desktop</p>}
* </div>
* );
* }
* ```
*/
declare function useResponsive(options?: UseMediaQueryOptions): {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
isLargeDesktop: boolean;
};
/**
* Hook to check if the screen is in dark mode
*
* @param options - Configuration options for the hook
* @returns Boolean indicating if dark mode is active
*
* @example
* ```tsx
* function ThemeAwareComponent() {
* const isDarkMode = useDarkMode();
*
* return (
* <div className={isDarkMode ? 'dark-theme' : 'light-theme'}>
* <p>Current theme: {isDarkMode ? 'Dark' : 'Light'}</p>
* </div>
* );
* }
* ```
*/
declare function useDarkMode(options?: UseMediaQueryOptions): boolean;
/**
* Hook to check if the user prefers reduced motion
*
* @param options - Configuration options for the hook
* @returns Boolean indicating if reduced motion is preferred
*
* @example
* ```tsx
* function AccessibleAnimation() {
* const prefersReducedMotion = usePrefersReducedMotion();
*
* return (
* <div>
* {prefersReducedMotion ? (
* <StaticContent />
* ) : (
* <AnimatedContent />
* )}
* </div>
* );
* }
* ```
*/
declare function usePrefersReducedMotion(options?: UseMediaQueryOptions): boolean;
/**
* Hook to check the current screen orientation
*
* @param options - Configuration options for the hook
* @returns Object with orientation information
*
* @example
* ```tsx
* function OrientationAwareComponent() {
* const { isPortrait, isLandscape } = useOrientation();
*
* return (
* <div>
* <p>Current orientation: {isPortrait ? 'Portrait' : 'Landscape'}</p>
* {isPortrait && <PortraitLayout />}
* {isLandscape && <LandscapeLayout />}
* </div>
* );
* }
* ```
*/
declare function useOrientation(options?: UseMediaQueryOptions): {
isPortrait: boolean;
isLandscape: boolean;
};
/**
* Mouse event names for tracking
*/
type MouseEventName = "mousedown" | "mouseup" | "mousemove" | "mouseenter" | "mouseleave" | "mouseover" | "mouseout" | "click" | "contextmenu" | "dblclick";
/**
* Mouse button states
*/
interface MouseButtons {
/**
* Left mouse button state
*/
left: boolean;
/**
* Right mouse button state
*/
right: boolean;
/**
* Middle mouse button state
*/
middle: boolean;
}
/**
* Mouse position coordinates
*/
interface MousePosition {
/**
* X coordinate
*/
x: number;
/**
* Y coordinate
*/
y: number;
/**
* Client X coordinate (relative to viewport)
*/
clientX: number;
/**
* Client Y coordinate (relative to viewport)
*/
clientY: number;
/**
* Page X coordinate (including scroll)
*/
pageX: number;
/**
* Page Y coordinate (including scroll)
*/
pageY: number;
/**
* Screen X coordinate (relative to screen)
*/
screenX: number;
/**
* Screen Y coordinate (relative to screen)
*/
screenY: number;
}
/**
* Mouse movement data
*/
interface MouseMovement {
/**
* Horizontal movement delta since last position
*/
deltaX: number;
/**
* Vertical movement delta since last position
*/
deltaY: number;
/**
* Direction of movement as degrees (0-360, 0 is right, 90 is down)
*/
direction: number;
/**
* Speed of movement in pixels per second
*/
speed: number;
/**
* Velocity vector [x, y] in pixels per second
*/
velocity: [number, number];
}
/**
* Mouse event data
*/
interface MouseEventData {
/**
* Current mouse position
*/
position: MousePosition;
/**
* Mouse button states
*/
buttons: MouseButtons;
/**
* Whether the mouse is inside the target element
*/
isInside: boolean;
/**
* Mouse movement data
*/
movement: MouseMovement;
/**
* Raw mouse event
*/
event: MouseEvent | null;
}
/**
* Options for the useMouse hook
*/
interface UseMouseOptions {
/**
* Target element to track mouse events on
* If not provided, document is used
*/
target?: React.RefObject<HTMLElement> | HTMLElement | null;
/**
* Whether to disable the hook
* @default false
*/
disabled?: boolean;
/**
* Mouse events to track
* @default ["mousemove", "mousedown", "mouseup", "mouseenter", "mouseleave"]
*/
eventTypes?: MouseEventName[];
/**
* Whether to use capture phase for event listeners
* @default false
*/
useCapture?: boolean;
/**
* Whether to prevent default behavior for mouse events
* @default false
*/
preventDefault?: boolean;
/**
* Whether to stop event propagation
* @default false
*/
stopPropagation?: boolean;
/**
* How long to keep tracking velocity after movement stops (ms)
* @default 50
*/
velocityDelay?: number;
/**
* Number of positions to keep in history for velocity calculations
* @default 5
*/
positionHistoryCount?: number;
/**
* Whether to track mouse position relative to the target element
* @default true
*/
trackRelativePosition?: boolean;
/**
* Callback when mouse enters target
*/
onEnter?: (data: MouseEventData) => void;
/**
* Callback when mouse leaves target
*/
onLeave?: (data: MouseEventData) => void;
/**
* Callback when mouse moves within target
*/
onMove?: (data: MouseEventData) => void;
/**
* Callback when mouse button is pressed within target
*/
onDown?: (data: MouseEventData, button: number) => void;
/**
* Callback when mouse button is released within target
*/
onUp?: (data: MouseEventData, button: number) => void;
/**
* Callback when mouse button is clicked within target
*/
onClick?: (data: MouseEventData, button: number) => void;
}
/**
* Return type for the useMouse hook
*/
interface UseMouseReturn extends MouseEventData {
/**
* Element that mouse events are being tracked on
*/
targetElement: HTMLElement | Document | null;
/**
* Relative position within the target element (0-1)
* Only available if trackRelativePosition is true
*/
relativePosition: {
x: number;
y: number;
} | null;
/**
* Reset the mouse state
*/
reset: () => void;
}
/**
* A hook for tracking mouse position, movement, and button states
*
* @param options - Configuration options for the hook
* @returns Object with mouse position, button states, and movement data
*
* @example
* ```tsx
* // Basic usage
* function MouseTracker() {
* const mouse = useMouse();
*
* return (
* <div>
* <p>Mouse position: {mouse.position.x}, {mouse.position.y}</p>
* <p>Left button: {mouse.buttons.left ? 'Pressed' : 'Released'}</p>
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // Tracking mouse within a specific element
* function CanvasInteraction() {
* const canvasRef = useRef(null);
* const mouse = useMouse({
* target: canvasRef,
* trackRelativePosition: true,
* onMov