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…
	
	Add table
		
		Reference in a new issue