popout transitions

This commit is contained in:
Jelle van Snik 2023-02-07 18:19:31 +01:00
parent 5d5a727663
commit 5e433266ee
2 changed files with 45 additions and 59 deletions

View File

@ -1,12 +1,14 @@
import { ReactNode, useRef } from "react"; import { ReactNode } from "react";
import { CSSTransition } from "react-transition-group"; import {
import { CSSTransitionClassNames } from "react-transition-group/CSSTransition"; Transition as HeadlessTransition,
TransitionClasses,
} from "@headlessui/react";
type TransitionAnimations = "slide-down" | "slide-up" | "fade" | "fade-inverse"; type TransitionAnimations = "slide-down" | "slide-up" | "fade";
interface Props { interface Props {
show: boolean; show: boolean;
durationClass?: string; durationClass?: "duration-200" | string; // default is specified so tailwind doesnt remove the class in prod builds
animation: TransitionAnimations; animation: TransitionAnimations;
className?: string; className?: string;
children?: ReactNode; children?: ReactNode;
@ -15,44 +17,37 @@ interface Props {
function getClasses( function getClasses(
animation: TransitionAnimations, animation: TransitionAnimations,
duration: number duration: number
): CSSTransitionClassNames { ): TransitionClasses {
if (animation === "slide-down") { if (animation === "slide-down") {
return { return {
exit: `transition-[transform,opacity] translate-y-0 duration-${duration} opacity-100`, leave: `transition-[transform,opacity] duration-${duration}`,
exitActive: "!-translate-y-4 !opacity-0", leaveFrom: "opacity-100 translate-y-0",
exitDone: "hidden", leaveTo: "-translate-y-4 opacity-0",
enter: `transition-[transform,opacity] -translate-y-4 duration-${duration} opacity-0`, enter: `transition-[transform,opacity] duration-${duration}`,
enterActive: "!translate-y-0 !opacity-100", enterFrom: "opacity-0 -translate-y-4",
enterTo: "translate-y-0 opacity-100",
}; };
} }
if (animation === "slide-up") { if (animation === "slide-up") {
return { return {
exit: `transition-[transform,opacity] translate-y-0 duration-${duration} opacity-100`, leave: `transition-[transform,opacity] duration-${duration}`,
exitActive: "!translate-y-4 !opacity-0", leaveFrom: "opacity-100 translate-y-0",
exitDone: "hidden", leaveTo: "translate-y-4 opacity-0",
enter: `transition-[transform,opacity] translate-y-4 duration-${duration} opacity-0`, enter: `transition-[transform,opacity] duration-${duration}`,
enterActive: "!translate-y-0 !opacity-100", enterFrom: "opacity-0 translate-y-4",
enterTo: "translate-y-0 opacity-100",
}; };
} }
if (animation === "fade") { if (animation === "fade") {
return { return {
exit: `transition-[transform,opacity] duration-${duration} opacity-100`, leave: `transition-[transform,opacity] duration-${duration}`,
exitActive: "!opacity-0", leaveFrom: "opacity-100",
exitDone: "hidden", leaveTo: "opacity-0",
enter: `transition-[transform,opacity] duration-${duration} opacity-0`, enter: `transition-[transform,opacity] duration-${duration}`,
enterActive: "!opacity-100", enterFrom: "opacity-0",
}; enterTo: "opacity-100",
}
if (animation === "fade-inverse") {
return {
enter: `transition-[transform,opacity] duration-${duration} opacity-100`,
enterActive: "!opacity-0",
exit: `transition-[transform,opacity] duration-${duration} opacity-0`,
exitActive: "!opacity-100",
enterDone: "hidden",
}; };
} }
@ -60,25 +55,16 @@ function getClasses(
} }
export function Transition(props: Props) { export function Transition(props: Props) {
const ref = useRef<HTMLDivElement>(null);
const duration = props.durationClass const duration = props.durationClass
? parseInt(props.durationClass.split("-")[1], 10) ? parseInt(props.durationClass.split("-")[1], 10)
: 200; : 200;
const classes = getClasses(props.animation, duration); const classes = getClasses(props.animation, duration);
return ( return (
<CSSTransition <div className={props.className}>
nodeRef={ref} <HeadlessTransition show={props.show} {...classes}>
in={props.show}
timeout={duration}
classNames={classes}
>
<div
ref={ref}
className={[props.className ?? "", classes.enter ?? ""].join(" ")}
>
{props.children} {props.children}
</div> </HeadlessTransition>
</CSSTransition> </div>
); );
} }

View File

@ -1,3 +1,4 @@
import { Transition } from "@/components/Transition";
import { EpisodeSelectionPopout } from "@/video/components/popouts/EpisodeSelectionPopout"; import { EpisodeSelectionPopout } from "@/video/components/popouts/EpisodeSelectionPopout";
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls"; import { useControls } from "@/video/state/logic/controls";
@ -6,7 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import "./Popouts.css"; import "./Popouts.css";
function ShowPopout(props: { popoutId: string }) { function ShowPopout(props: { popoutId: string | null }) {
// only updates popout id when a new one is set, so transitions look good // only updates popout id when a new one is set, so transitions look good
const [popoutId, setPopoutId] = useState<string | null>(props.popoutId); const [popoutId, setPopoutId] = useState<string | null>(props.popoutId);
useEffect(() => { useEffect(() => {
@ -20,7 +21,6 @@ function ShowPopout(props: { popoutId: string }) {
// TODO use new design for popouts // TODO use new design for popouts
// TODO improve anti offscreen math // TODO improve anti offscreen math
// TODO in and out transition
// TODO attach router history to popout state, so you can use back button to remove popout // TODO attach router history to popout state, so you can use back button to remove popout
export function PopoutProviderAction() { export function PopoutProviderAction() {
const descriptor = useVideoPlayerDescriptor(); const descriptor = useVideoPlayerDescriptor();
@ -51,20 +51,20 @@ export function PopoutProviderAction() {
: "30px"; : "30px";
}, [videoInterface]); }, [videoInterface]);
if (!videoInterface.popout) return null;
return ( return (
<div className="popout-wrapper pointer-events-auto absolute inset-0"> <Transition show={!!videoInterface.popout} animation="fade">
<div onClick={handleClick} className="absolute inset-0" /> <div className="popout-wrapper pointer-events-auto absolute inset-0">
<div <div onClick={handleClick} className="absolute inset-0" />
className="grid-template-rows-[auto,minmax(0px,1fr)] absolute z-10 grid h-[500px] w-72 rounded-lg bg-denim-300" <div
style={{ className="grid-template-rows-[auto,minmax(0px,1fr)] absolute z-10 grid h-[500px] w-72 rounded-lg bg-denim-300"
right: distanceFromRight, style={{
bottom: distanceFromBottom, right: distanceFromRight,
}} bottom: distanceFromBottom,
> }}
<ShowPopout popoutId={videoInterface.popout ?? ""} /> >
<ShowPopout popoutId={videoInterface.popout} />
</div>
</div> </div>
</div> </Transition>
); );
} }