import type { ReactElement, ReactNode } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useOnUnmount } from "../useOnUnmount"; import { DEFAULT_HOVER_MODE_DEACTIVATION_TIME, DEFAULT_HOVER_MODE_VISIBLE_IN_TIME, } from "./constants"; import type { HoverModeContext } from "./useHoverModeContext"; import { HoverModeContextProvider } from "./useHoverModeContext"; /** @remarks \@since 2.8.0 */ export interface HoverModeConfiguration { /** * Boolean if the hover mode functionality should be disabled. * * @defaultValue `false` */ disabled?: boolean; /** * The amount of time (in ms) the user must hover an element before the hover * mode is enabled and the visibility is set to `true`. * * @defaultValue {@link DEFAULT_HOVER_MODE_VISIBLE_IN_TIME} */ defaultVisibleInTime?: number; /** * The amount of time (in ms) the user must not hover any element connected to * the hover mode. * * @defaultValue {@link DEFAULT_HOVER_MODE_DEACTIVATION_TIME} */ deactivateTime?: number; } /** @remarks \@since 2.8.0 */ export interface HoverModeProviderProps extends HoverModeConfiguration { children: ReactNode; } /** * This component should normally be mounted near the root of your app to enable * hover mode for child components. However, it can also be used at other levels * if hover mode functionality should not carry over between two different parts * of the screen. * * @example * Separating Hover Mode * ```tsx * export default function Example(): ReactElement { * return ( * <> * * * * * * * * ); * } * ``` * * @remarks \@since 2.8.0 */ export function HoverModeProvider({ children, disabled = false, defaultVisibleInTime = DEFAULT_HOVER_MODE_VISIBLE_IN_TIME, deactivateTime = DEFAULT_HOVER_MODE_DEACTIVATION_TIME, }: HoverModeProviderProps): ReactElement { const [visibleInTime, setVisibleInTime] = useState(defaultVisibleInTime); const timeoutRef = useRef(); const enableHoverMode = useCallback(() => { if (disabled) { return; } window.clearTimeout(timeoutRef.current); setVisibleInTime(0); }, [disabled]); const disableHoverMode = useCallback(() => { window.clearTimeout(timeoutRef.current); setVisibleInTime(defaultVisibleInTime); }, [defaultVisibleInTime]); const startDisableTimer = useCallback(() => { window.clearTimeout(timeoutRef.current); timeoutRef.current = window.setTimeout(() => { setVisibleInTime(defaultVisibleInTime); }, deactivateTime); }, [defaultVisibleInTime, deactivateTime]); useEffect(() => { if (disabled) { window.clearTimeout(timeoutRef.current); setVisibleInTime(defaultVisibleInTime); } }, [disabled, defaultVisibleInTime]); useOnUnmount(() => { window.clearTimeout(timeoutRef.current); }); const context = useMemo( () => ({ visibleInTime, enableHoverMode, disableHoverMode, startDisableTimer, }), [disableHoverMode, enableHoverMode, startDisableTimer, visibleInTime] ); return ( {children} ); }