signal-desktop/ts/hooks/useAnimated.tsx
2024-06-05 14:48:54 -07:00

95 lines
2.1 KiB
TypeScript

// 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<ModalConfigType>;
overlayStyles: SpringValues<ModalConfigType>;
} {
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 ||
currentState === ModalState.Opening
) {
return ModalState.Closing;
}
return currentState;
});
}, []);
return {
close,
isClosed,
overlayStyles,
modalStyles,
};
}