@trail-ui/react
Version:
74 lines (63 loc) • 1.88 kB
text/typescript
import { useCallbackRef } from '@trail-ui/hooks';
import { chain } from '@react-aria/utils';
import { useControlledState } from '@react-stately/utils';
import { useCallback, useId } from 'react';
export interface UseDisclosureProps {
isOpen?: boolean;
defaultOpen?: boolean;
onClose?(): void;
onOpen?(): void;
onChange?(isOpen: boolean | undefined): void;
id?: string;
}
export function useDisclosure(props: UseDisclosureProps = {}) {
const {
id: idProp,
defaultOpen,
isOpen: isOpenProp,
onClose: onCloseProp,
onOpen: onOpenProp,
onChange = () => {},
} = props;
const onOpenPropCallbackRef = useCallbackRef(onOpenProp);
const onClosePropCallbackRef = useCallbackRef(onCloseProp);
const [isOpen, setIsOpen] = useControlledState(isOpenProp, defaultOpen || false, onChange);
const reactId = useId();
const id = idProp || reactId;
const isControlled = isOpenProp !== undefined;
const onClose = useCallback(() => {
if (!isControlled) {
setIsOpen(false);
}
onClosePropCallbackRef?.();
}, [isControlled, onClosePropCallbackRef, setIsOpen]);
const onOpen = useCallback(() => {
if (!isControlled) {
setIsOpen(true);
}
onOpenPropCallbackRef?.();
}, [isControlled, onOpenPropCallbackRef, setIsOpen]);
const onOpenChange = useCallback(() => {
const action = isOpen ? onClose : onOpen;
action();
}, [isOpen, onOpen, onClose]);
return {
isOpen: !!isOpen,
onOpen,
onClose,
onOpenChange,
isControlled,
getButtonProps: (props: any = {}) => ({
...props,
'aria-expanded': isOpen,
'aria-controls': id,
onClick: chain(props.onClick, onOpenChange),
}),
getDisclosureProps: (props: any = {}) => ({
...props,
hidden: !isOpen,
id,
}),
};
}
export type UseDisclosureReturn = ReturnType<typeof useDisclosure>;