summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx94
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts3
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts (renamed from desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.tsx)2
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useHandleAnimationEnd.ts19
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useIsInitialRender.ts11
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useShow.ts14
6 files changed, 122 insertions, 21 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx
index ffcc462385..00475d6cf3 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx
@@ -1,8 +1,10 @@
import React from 'react';
-import styled, { css } from 'styled-components';
+import styled, { css, RuleSet } from 'styled-components';
import { TransientProps } from '../../types';
-import { useAnimations } from './hooks';
+import { useMounted } from '../../utility-hooks';
+import { AnimateProvider } from './AnimateContext';
+import { useAnimations, useHandleAnimationEnd, useShow } from './hooks';
export type Animation = FadeAnimation | WipeAnimation;
@@ -15,7 +17,8 @@ export type WipeAnimation = {
direction: 'vertical';
};
-export type AnimateProps = React.HTMLAttributes<HTMLDivElement> & {
+type AnimateBaseProps = {
+ initial?: boolean;
present?: boolean;
duration?: React.CSSProperties['animationDuration'];
timingFunction?: React.CSSProperties['animationTimingFunction'];
@@ -26,7 +29,14 @@ export type AnimateProps = React.HTMLAttributes<HTMLDivElement> & {
children?: React.ReactNode;
};
-const StyledDiv = styled.div<TransientProps<AnimateProps>>`
+export type AnimateProps = AnimateBaseProps &
+ Omit<React.HTMLAttributes<HTMLDivElement>, keyof AnimateBaseProps>;
+
+const StyledDiv = styled.div<
+ TransientProps<Omit<AnimateBaseProps, 'animations'>> & {
+ $animations: RuleSet;
+ }
+>`
${({
$animations,
$duration = '0.25s',
@@ -34,44 +44,88 @@ const StyledDiv = styled.div<TransientProps<AnimateProps>>`
$direction = 'normal',
$iterationCount = '1',
}) => {
- const animations = useAnimations($animations);
return css`
- &&[data-present-animation='true'] {
+ // If the user prefers reduced motion, visibility still needs
+ // to be toggled, otherwise this is handled by animations
+ &&[data-is-present-animation='true'] {
display: none;
- &&[data-show='true'] {
+ &&[data-present='true'] {
display: block;
}
}
@media (prefers-reduced-motion: no-preference) {
- --duration: ${$duration};
- --timing-function: ${$timingFunction};
- --direction: ${$direction};
- --iteration-count: ${$iterationCount};
+ &&[data-animate='true'] {
+ --duration: ${$duration};
+ --timing-function: ${$timingFunction};
+ --direction: ${$direction};
+ --iteration-count: ${$iterationCount};
- interpolate-size: allow-keywords;
- transition-behavior: allow-discrete;
+ interpolate-size: allow-keywords;
+ transition-behavior: allow-discrete;
- overflow: clip;
- ${animations}
+ overflow: clip;
+ ${$animations}
+ }
}
`;
}}
`;
-export function Animate({
- present,
- animations,
+/**
+ * Animate that applies animation to a wrapper around it's children.
+ *
+ * @param initial - Whether animation should trigger on mount.
+ * @param present - Whether element is present, i.e rendered or not.
+ * @param animations - List of animations to apply.
+ */
+export function Animate({ initial, present, children, ...props }: AnimateProps) {
+ return (
+ <AnimateProvider initial={initial} present={present}>
+ <AnimateImpl {...props} present={present} initial={initial}>
+ {children}
+ </AnimateImpl>
+ </AnimateProvider>
+ );
+}
+
+function AnimateImpl({
+ initial = true,
+ present: presentProp,
+ animations: animationsProp,
duration,
timingFunction,
direction,
iterationCount,
+ onAnimationEnd,
...props
}: AnimateProps) {
+ const animations = useAnimations(animationsProp);
+ const show = useShow();
+ const [initialPresent] = React.useState(presentProp);
+ const [presentChanged, setPresentChanged] = React.useState(false);
+ const [present, setPresent] = React.useState(presentProp);
+
+ React.useEffect(() => {
+ if (presentProp !== initialPresent && !presentChanged) {
+ setPresentChanged(true);
+ }
+ }, [initialPresent, presentProp, presentChanged]);
+
+ React.useEffect(() => {
+ setPresent(presentProp);
+ }, [presentProp]);
+
+ const handleAnimationEnd = useHandleAnimationEnd(onAnimationEnd);
+ const mounted = useMounted();
+ if (!show) return null;
+
return (
<StyledDiv
- data-show={present}
- data-present-animation={present === undefined ? false : true}
+ data-animate={initial || (mounted() && presentChanged)}
+ data-present={present}
+ data-is-present-animation={presentProp !== undefined}
+ onAnimationEnd={handleAnimationEnd}
$animations={animations}
$duration={duration}
$timingFunction={timingFunction}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts
index 8811e6a063..5ba626bda4 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts
@@ -1 +1,4 @@
export * from './useAnimations';
+export * from './useIsInitialRender';
+export * from './useShow';
+export * from './useHandleAnimationEnd';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts
index 1b44da8a30..c792d016a1 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts
@@ -22,7 +22,7 @@ export const useAnimations = (values: Animation[]) => {
${inAnimations.map((animation) => animation.rule)}
${outAnimations.map((animation) => animation.rule)}
${createAnimationDeclaration(outAnimations)}
- &&[data-show='true'] {
+ &&[data-present='true'] {
${createAnimationDeclaration(inAnimations)}
}
`;
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useHandleAnimationEnd.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useHandleAnimationEnd.ts
new file mode 100644
index 0000000000..2b4d83441c
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useHandleAnimationEnd.ts
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { AnimateProps } from '../Animate';
+import { useAnimateContext } from '../AnimateContext';
+
+export const useHandleAnimationEnd = (onAnimationEnd: AnimateProps['onAnimationEnd']) => {
+ const { present, show, setShow } = useAnimateContext();
+ return React.useCallback(
+ (e: React.AnimationEvent<HTMLDivElement>) => {
+ if (!present && show) {
+ setShow(false);
+ }
+ if (onAnimationEnd) {
+ onAnimationEnd(e);
+ }
+ },
+ [onAnimationEnd, present, setShow, show],
+ );
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useIsInitialRender.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useIsInitialRender.ts
new file mode 100644
index 0000000000..546eba8152
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useIsInitialRender.ts
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export const useIsInitialRender = () => {
+ const isInitialRender = React.useRef(true);
+
+ React.useEffect(() => {
+ isInitialRender.current = false;
+ }, []);
+
+ return React.useCallback(() => isInitialRender.current, [isInitialRender]);
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useShow.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useShow.ts
new file mode 100644
index 0000000000..2a452ffbb4
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useShow.ts
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import { useAnimateContext } from '../AnimateContext';
+
+export const useShow = () => {
+ const { present, show, setShow } = useAnimateContext();
+
+ React.useEffect(() => {
+ if (present && !show) {
+ setShow(true);
+ }
+ }, [present, setShow, show]);
+ return show;
+};