Add fade out animation to closing curtains.
This commit is contained in:
parent
a42385bfc1
commit
343bbfe785
2 changed files with 69 additions and 11 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue