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 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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue