Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 28x 28x 28x 28x 26x 28x 28x 28x 9x 7x 7x 7x 28x 9x 6x 6x 6x 28x 5x 3x 2x 28x 13x 28x | import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { UseDisclosureOptions, UseDisclosureReturn } from "./types";
import { useCallbackRef } from "./utils";
/**
* Manage a boolean "open" state with `open` / `close` / `toggle` handlers — the
* ergonomic primitive for modals, drawers, popovers, dropdowns, and accordions.
*
* Returns a `[opened, handlers]` tuple (the Mantine shape). The handlers have
* **stable identities**, so passing them to memoized children or listing them in
* effect dependencies never causes extra work. Optional `onOpen` / `onClose`
* callbacks fire only on a *real* transition (not when already in that state).
*
* @param initialState - Whether it starts open. Defaults to `false`.
* @param options - Optional `{ onOpen, onClose }` transition callbacks.
* @returns `[opened, { open, close, toggle }]`.
*
* @example
* ```tsx
* function Example() {
* const [opened, { open, close, toggle }] = useDisclosure(false);
*
* return (
* <>
* <button onClick={open}>Open</button>
* <button onClick={toggle}>Toggle</button>
* {opened && (
* <div role="dialog">
* Modal content
* <button onClick={close}>Close</button>
* </div>
* )}
* </>
* );
* }
* ```
*
* @example
* ```tsx
* // React to transitions (analytics, focus management, …).
* const [opened, handlers] = useDisclosure(false, {
* onOpen: () => trackEvent("drawer_opened"),
* onClose: () => trackEvent("drawer_closed"),
* });
* ```
*
* @remarks
* `onOpen` / `onClose` are dispatched from the event handler after the state
* change is requested — never from inside a `setState` updater — so they are
* safe under React StrictMode / concurrent rendering and never double-fire.
*/
export function useDisclosure(
initialState = false,
options: UseDisclosureOptions = {}
): UseDisclosureReturn {
const { onOpen, onClose } = options;
const [opened, setOpened] = useState(initialState);
// Mirror the committed state so the handlers can read the latest value
// synchronously (and decide whether a transition actually happens) without
// running side effects inside a setState updater.
const openedRef = useRef(opened);
useEffect(() => {
openedRef.current = opened;
}, [opened]);
const handleOpen = useCallbackRef(onOpen);
const handleClose = useCallbackRef(onClose);
const open = useCallback(() => {
if (!openedRef.current) {
openedRef.current = true;
setOpened(true);
handleOpen();
}
}, [handleOpen]);
const close = useCallback(() => {
if (openedRef.current) {
openedRef.current = false;
setOpened(false);
handleClose();
}
}, [handleClose]);
const toggle = useCallback(() => {
if (openedRef.current) {
close();
} else {
open();
}
}, [open, close]);
const handlers = useMemo(
() => ({ open, close, toggle }),
[open, close, toggle]
);
return [opened, handlers] as const;
}
|