From 343bbfe785b9f5abf2fccc9f2cdb198505a01dec Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Sun, 14 Jul 2024 16:50:25 +0200 Subject: [PATCH] Add fade out animation to closing curtains. --- src/Components/Curtains/Curtains.tsx | 69 +++++++++++++++++++++++----- src/styles/components/_curtains.less | 11 +++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/Components/Curtains/Curtains.tsx b/src/Components/Curtains/Curtains.tsx index fb9eaf0..fb3f4b7 100644 --- a/src/Components/Curtains/Curtains.tsx +++ b/src/Components/Curtains/Curtains.tsx @@ -1,6 +1,7 @@ import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react"; import ReactDOM from "react-dom"; import {v4 as uuidv4} from "uuid"; +import {classes} from "../../Utils"; /** * Curtain UUID type. @@ -17,6 +18,11 @@ export type OpenCurtainFunction = (content: React.ReactNode) => CurtainUuidType; */ export type CloseCurtainFunction = (uuid: CurtainUuidType) => void; +/** + * The function that checks if a curtain is closed (while transitioning out) or not. + */ +export type IsCurtainClosedFunction = (uuid: CurtainUuidType) => boolean; + /** * Interface of curtains state. */ @@ -34,12 +40,19 @@ export interface CurtainsContextState * @param uuid UUID of the curtain to close. */ close: CloseCurtainFunction; + + /** + * Check if the given curtain is closed or not. + * @param uuid UUID of the curtain to check. + */ + isClosed: IsCurtainClosedFunction; } const CurtainsContext = React.createContext({ // Empty functions. open() { return ""; }, close() {}, + isClosed() { return false; }, }); /** @@ -98,6 +111,8 @@ export function Curtains({children}: React.PropsWithChildren<{}>) { // Curtains state. const [curtains, setCurtains] = useState>({}); + // Keeping track of closed curtains that are still on (while transitioning out). + const [closedCurtains, setClosedCurtains] = useState>({}); // Initialize open curtain function. const open = useRef(); @@ -115,32 +130,57 @@ export function Curtains({children}: React.PropsWithChildren<{}>) return curtainUuid; }, [curtains, setCurtains]); - // Initialize close curtain function. - const close = useRef(); - close.current = useCallback((uuid: CurtainUuidType) => { + // Initialize remove curtain function. + const remove = useRef(); + remove.current = useCallback((uuid: CurtainUuidType) => { // Copy the curtains list. const newCurtains = {...curtains}; // Remove the given curtain from the list. delete newCurtains[uuid]; + delete closedCurtains[uuid]; // Set the new curtains list. setCurtains(newCurtains); - }, [curtains, setCurtains]); + setClosedCurtains(closedCurtains); + }, [curtains, setCurtains, closedCurtains, setClosedCurtains]); + + // Initialize close curtain function with animation. + const close = useRef(); + close.current = useCallback((uuid) => { + // Add the given curtain UUID to the list of closed curtains. + setClosedCurtains({ + ...closedCurtains, + [uuid]: true, + }); + + // Remove the curtain 1s later. + window.setTimeout(() => { + // Remove the curtain. + remove.current(uuid); + }, 1000); + }, [remove, closedCurtains, setClosedCurtains]); + + // Initialize isClosed curtain function. + const isClosed = useRef(); + isClosed.current = useCallback((uuid) => (!!closedCurtains?.[uuid]), [closedCurtains]); // Initialize context state from action functions. const contextState = useMemo(() => ({ open: (content: React.ReactNode) => open.current(content), close: (uuid: CurtainUuidType) => close.current(uuid), - }), [open, close]); + isClosed: (uuid: CurtainUuidType) => isClosed.current(uuid), + }), [open, close, isClosed]); // Show dimmed main content. useEffect(() => { - if (Object.entries(curtains).length > 0 && !document.body.classList.contains("dimmed")) - // We should dim content and it's currently not. + if ( + Object.keys(curtains).filter((curtainId) => !closedCurtains[curtainId]).length > 0 + && !document.body.classList.contains("dimmed") + ) // We should dim content if there is at least one open curtain. document.body.classList.add("dimmed"); else // We shouldn't dim content. document.body.classList.remove("dimmed"); - }, [curtains]); + }, [curtains, closedCurtains]); return ( @@ -180,6 +220,11 @@ export interface CurtainContextState * Close the curtain. */ close: () => void; + + /** + * True if the curtain is closed (while transitioning out). + */ + closed: boolean; } /** @@ -189,6 +234,7 @@ const CurtainContext = React.createContext({ // Empty values. uuid: "", close: () => {}, + closed: false, }); /** @@ -210,7 +256,7 @@ function CurtainInstance({uuid, children}: React.PropsWithChildren<{ }>) { // Get close curtain function. - const {close} = useCurtains(); + const {close, isClosed} = useCurtains(); // Initialize close curtain function. const closeCurtain = useRef<() => void>(); @@ -223,11 +269,12 @@ function CurtainInstance({uuid, children}: React.PropsWithChildren<{ const contextState = useMemo(() => ({ uuid: uuid, close: () => closeCurtain.current(), - }), [uuid, closeCurtain]); + closed: isClosed(uuid), + }), [uuid, closeCurtain, isClosed]); return ( -
+
{children}
diff --git a/src/styles/components/_curtains.less b/src/styles/components/_curtains.less index cfcd4e1..cc37f71 100644 --- a/src/styles/components/_curtains.less +++ b/src/styles/components/_curtains.less @@ -14,6 +14,17 @@ body > .curtain transform-origin: center; z-index: 1000; // On top of main content. + + &.closed + { // Added when the curtain is closing and will soon be removed from DOM. + transition: transform 0.4s ease-out, filter 0.4s ease-out, opacity 0.4s ease-out; + + transform: scale(1.2); + filter: blur(0.5em); + opacity: 0; + + pointer-events: none; + } } body.dimmed