// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { useState, useCallback } from 'react'; import type { SpringValues } from '@react-spring/web'; import { useChain, useSpring, useSpringRef } from '@react-spring/web'; export type ModalConfigType = { opacity: number; transform?: string; marginTop?: string; }; enum ModalState { Opening = 'Opening', Open = 'Open', Closing = 'Closing', Closed = 'Closed', } export function useAnimated( onClose: () => unknown, { getFrom, getTo, }: { getFrom: (isOpen: boolean) => ModalConfigType; getTo: (isOpen: boolean) => ModalConfigType; } ): { close: () => unknown; isClosed: boolean; modalStyles: SpringValues; overlayStyles: SpringValues; } { const [state, setState] = useState(ModalState.Opening); const shouldShowModal = state === ModalState.Open || state === ModalState.Opening; const isClosed = state === ModalState.Closed; const modalRef = useSpringRef(); const modalStyles = useSpring({ from: getFrom(shouldShowModal), to: getTo(shouldShowModal), onRest: () => { if (state === ModalState.Closing) { setState(ModalState.Closed); onClose(); } else if (state === ModalState.Opening) { setState(ModalState.Open); } }, config: { clamp: true, friction: 20, mass: 0.5, tension: 350, }, ref: modalRef, }); const overlayRef = useSpringRef(); const overlayStyles = useSpring({ from: { opacity: 0 }, to: { opacity: shouldShowModal ? 1 : 0 }, config: { clamp: true, friction: 22, tension: 360, }, ref: overlayRef, }); useChain(shouldShowModal ? [overlayRef, modalRef] : [modalRef, overlayRef]); const close = useCallback(() => { setState(currentState => { if (currentState === ModalState.Open) { return ModalState.Closing; } return currentState; }); }, []); return { close, isClosed, overlayStyles, modalStyles, }; }