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 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<CurtainsContextState>({
// Empty functions.
open() { return ""; },
close() {},
isClosed() { return false; },
});
/**
@ -98,6 +111,8 @@ export function Curtains({children}: React.PropsWithChildren<{}>)
{
// Curtains state.
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.
const open = useRef<OpenCurtainFunction>();
@ -115,32 +130,57 @@ export function Curtains({children}: React.PropsWithChildren<{}>)
return curtainUuid;
}, [curtains, setCurtains]);
// Initialize close curtain function.
const close = useRef<CloseCurtainFunction>();
close.current = useCallback((uuid: CurtainUuidType) => {
// Initialize remove curtain function.
const remove = useRef<CloseCurtainFunction>();
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<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.
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 (
<CurtainsContext.Provider value={contextState}>
@ -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<CurtainContextState>({
// 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 (
<CurtainContext.Provider value={contextState}>
<div className={"curtain"}>
<div className={classes("curtain", isClosed(uuid) ? "closed" : undefined)}>
{children}
</div>
</CurtainContext.Provider>

View file

@ -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