Add fade out animation to closing curtains.

This commit is contained in:
Madeorsk 2024-07-14 16:50:25 +02:00
parent a42385bfc1
commit 343bbfe785
Signed by: Madeorsk
SSH key fingerprint: SHA256:J9G0ofIOLKf7kyS2IfrMqtMaPdfsk1W02+oGueZzDDU
2 changed files with 69 additions and 11 deletions

View file

@ -1,6 +1,7 @@
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react"; import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {classes} from "../../Utils";
/** /**
* Curtain UUID type. * Curtain UUID type.
@ -17,6 +18,11 @@ export type OpenCurtainFunction = (content: React.ReactNode) => CurtainUuidType;
*/ */
export type CloseCurtainFunction = (uuid: CurtainUuidType) => void; 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. * Interface of curtains state.
*/ */
@ -34,12 +40,19 @@ export interface CurtainsContextState
* @param uuid UUID of the curtain to close. * @param uuid UUID of the curtain to close.
*/ */
close: CloseCurtainFunction; 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<CurtainsContextState>({ const CurtainsContext = React.createContext<CurtainsContextState>({
// Empty functions. // Empty functions.
open() { return ""; }, open() { return ""; },
close() {}, close() {},
isClosed() { return false; },
}); });
/** /**
@ -98,6 +111,8 @@ export function Curtains({children}: React.PropsWithChildren<{}>)
{ {
// Curtains state. // Curtains state.
const [curtains, setCurtains] = useState<Record<CurtainUuidType, React.ReactNode>>({}); const [curtains, setCurtains] = useState<Record<CurtainUuidType, React.ReactNode>>({});
// Keeping track of closed curtains that are still on (while transitioning out).
const [closedCurtains, setClosedCurtains] = useState<Record<CurtainUuidType, boolean>>({});
// Initialize open curtain function. // Initialize open curtain function.
const open = useRef<OpenCurtainFunction>(); const open = useRef<OpenCurtainFunction>();
@ -115,32 +130,57 @@ export function Curtains({children}: React.PropsWithChildren<{}>)
return curtainUuid; return curtainUuid;
}, [curtains, setCurtains]); }, [curtains, setCurtains]);
// Initialize close curtain function. // Initialize remove curtain function.
const close = useRef<CloseCurtainFunction>(); const remove = useRef<CloseCurtainFunction>();
close.current = useCallback((uuid: CurtainUuidType) => { remove.current = useCallback((uuid: CurtainUuidType) => {
// Copy the curtains list. // Copy the curtains list.
const newCurtains = {...curtains}; const newCurtains = {...curtains};
// Remove the given curtain from the list. // Remove the given curtain from the list.
delete newCurtains[uuid]; delete newCurtains[uuid];
delete closedCurtains[uuid];
// Set the new curtains list. // Set the new curtains list.
setCurtains(newCurtains); setCurtains(newCurtains);
}, [curtains, setCurtains]); setClosedCurtains(closedCurtains);
}, [curtains, setCurtains, closedCurtains, setClosedCurtains]);
// Initialize close curtain function with animation.
const close = useRef<CloseCurtainFunction>();
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<IsCurtainClosedFunction>();
isClosed.current = useCallback((uuid) => (!!closedCurtains?.[uuid]), [closedCurtains]);
// Initialize context state from action functions. // Initialize context state from action functions.
const contextState = useMemo(() => ({ const contextState = useMemo(() => ({
open: (content: React.ReactNode) => open.current(content), open: (content: React.ReactNode) => open.current(content),
close: (uuid: CurtainUuidType) => close.current(uuid), close: (uuid: CurtainUuidType) => close.current(uuid),
}), [open, close]); isClosed: (uuid: CurtainUuidType) => isClosed.current(uuid),
}), [open, close, isClosed]);
// Show dimmed main content. // Show dimmed main content.
useEffect(() => { useEffect(() => {
if (Object.entries(curtains).length > 0 && !document.body.classList.contains("dimmed")) if (
// We should dim content and it's currently not. 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"); document.body.classList.add("dimmed");
else else
// We shouldn't dim content. // We shouldn't dim content.
document.body.classList.remove("dimmed"); document.body.classList.remove("dimmed");
}, [curtains]); }, [curtains, closedCurtains]);
return ( return (
<CurtainsContext.Provider value={contextState}> <CurtainsContext.Provider value={contextState}>
@ -180,6 +220,11 @@ export interface CurtainContextState
* Close the curtain. * Close the curtain.
*/ */
close: () => void; close: () => void;
/**
* True if the curtain is closed (while transitioning out).
*/
closed: boolean;
} }
/** /**
@ -189,6 +234,7 @@ const CurtainContext = React.createContext<CurtainContextState>({
// Empty values. // Empty values.
uuid: "", uuid: "",
close: () => {}, close: () => {},
closed: false,
}); });
/** /**
@ -210,7 +256,7 @@ function CurtainInstance({uuid, children}: React.PropsWithChildren<{
}>) }>)
{ {
// Get close curtain function. // Get close curtain function.
const {close} = useCurtains(); const {close, isClosed} = useCurtains();
// Initialize close curtain function. // Initialize close curtain function.
const closeCurtain = useRef<() => void>(); const closeCurtain = useRef<() => void>();
@ -223,11 +269,12 @@ function CurtainInstance({uuid, children}: React.PropsWithChildren<{
const contextState = useMemo(() => ({ const contextState = useMemo(() => ({
uuid: uuid, uuid: uuid,
close: () => closeCurtain.current(), close: () => closeCurtain.current(),
}), [uuid, closeCurtain]); closed: isClosed(uuid),
}), [uuid, closeCurtain, isClosed]);
return ( return (
<CurtainContext.Provider value={contextState}> <CurtainContext.Provider value={contextState}>
<div className={"curtain"}> <div className={classes("curtain", isClosed(uuid) ? "closed" : undefined)}>
{children} {children}
</div> </div>
</CurtainContext.Provider> </CurtainContext.Provider>

View file

@ -14,6 +14,17 @@ body > .curtain
transform-origin: center; transform-origin: center;
z-index: 1000; // On top of main content. 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 body.dimmed