1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/packages/ui-components/animate/src/animate.tsx
Gabriel b1e737c334 chore: extract @k8slens/animate
Signed-off-by: Gabriel <gaccettola@mirantis.com>
2023-05-30 21:38:22 +02:00

111 lines
3.4 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./animate.scss";
import React, { useEffect, useState } from "react";
import type { StrictReactNode } from "@k8slens/utilities";
import { cssNames, noop } from "@k8slens/utilities";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { RequestAnimationFrame } from "./request-animation-frame.injectable";
import { requestAnimationFrameInjectable } from "./request-animation-frame.injectable";
import { defaultEnterDurationForAnimatedInjectable } from "./default-enter-duration.injectable";
import { defaultLeaveDurationForAnimatedInjectable } from "./default-leave-duration.injectable";
export type AnimateName = "opacity" | "slide-right" | "opacity-scale" | string;
export interface AnimateProps {
name?: AnimateName; // predefined names in css
enter?: boolean;
onEnter?: () => void;
onLeave?: () => void;
enterDuration?: number;
leaveDuration?: number;
children?: StrictReactNode;
}
interface Dependencies {
requestAnimationFrame: RequestAnimationFrame;
defaultEnterDuration: number;
defaultLeaveDuration: number;
}
const NonInjectedAnimate = (propsAndDeps: AnimateProps & Dependencies) => {
const { requestAnimationFrame, defaultEnterDuration, defaultLeaveDuration, ...props } = propsAndDeps;
const {
children,
enter = true,
enterDuration = defaultEnterDuration,
leaveDuration = defaultLeaveDuration,
name = "opacity",
onEnter: onEnterHandler = noop<[]>,
onLeave: onLeaveHandler = noop<[]>,
} = props;
const [isVisible, setIsVisible] = useState(false);
const [showClassNameEnter, setShowClassNameEnter] = useState(false);
const [showClassNameLeave, setShowClassNameLeave] = useState(false);
// eslint-disable-next-line xss/no-mixed-html
const contentElem = React.Children.only(children) as React.ReactElement<React.HTMLAttributes<any>>;
const classNames = cssNames("Animate", name, contentElem.props.className, {
enter: showClassNameEnter,
leave: showClassNameLeave,
});
useEffect(() => {
if (enter) {
setIsVisible(true);
requestAnimationFrame(() => {
setShowClassNameEnter(true);
onEnterHandler();
});
return noop;
} else if (isVisible) {
setShowClassNameLeave(true);
onLeaveHandler();
// Cleanup after duration
const handle = setTimeout(() => {
setIsVisible(false);
setShowClassNameEnter(false);
setShowClassNameLeave(false);
}, leaveDuration);
return () => clearTimeout(handle);
}
return noop;
}, [enter]);
if (!isVisible) {
return null;
}
const cssVarsForAnimation = {
"--enter-duration": `${enterDuration}ms`,
"--leave-duration": `${leaveDuration}ms`,
} as React.CSSProperties;
return React.cloneElement(contentElem, {
className: classNames,
children: contentElem.props.children,
style: {
...contentElem.props.style,
...cssVarsForAnimation,
},
});
};
export const Animate = withInjectables<Dependencies, AnimateProps>(NonInjectedAnimate, {
getProps: (di, props) => ({
...props,
requestAnimationFrame: di.inject(requestAnimationFrameInjectable),
defaultEnterDuration: di.inject(defaultEnterDurationForAnimatedInjectable),
defaultLeaveDuration: di.inject(defaultLeaveDurationForAnimatedInjectable),
}),
});