summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-03-28 13:12:55 +0100
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-05-28 10:33:24 +0200
commit69f2b7bb1b0af94155ca2ebe58e2fb9616a9484a (patch)
tree6fd36337884a87afa44814e7bd87b383009349cb
parent4ab68b8218f25994684bed94d701dbcd898ccdc4 (diff)
downloadmullvadvpn-69f2b7bb1b0af94155ca2ebe58e2fb9616a9484a.tar.xz
mullvadvpn-69f2b7bb1b0af94155ca2ebe58e2fb9616a9484a.zip
Update animation component to allow passing multiple animations
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx91
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/animations.ts13
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/fade.ts31
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/wipe.ts31
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/AnimatePresentVertical.tsx34
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.tsx29
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation-declaration.ts10
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation.ts10
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/index.ts2
12 files changed, 208 insertions, 46 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 d0edee746e..ffcc462385 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,14 +1,83 @@
-import { AnimatePresentVertical, AnimatePresentVerticalProps } from './components';
+import React from 'react';
+import styled, { css } from 'styled-components';
-export type AnimateProps = {
- type: 'present-vertical';
-} & AnimatePresentVerticalProps;
+import { TransientProps } from '../../types';
+import { useAnimations } from './hooks';
-export function Animate({ type, ...props }: AnimateProps) {
- switch (type) {
- case 'present-vertical':
- return <AnimatePresentVertical {...props} />;
- default:
- return type satisfies never;
- }
+export type Animation = FadeAnimation | WipeAnimation;
+
+export type FadeAnimation = {
+ type: 'fade';
+};
+
+export type WipeAnimation = {
+ type: 'wipe';
+ direction: 'vertical';
+};
+
+export type AnimateProps = React.HTMLAttributes<HTMLDivElement> & {
+ present?: boolean;
+ duration?: React.CSSProperties['animationDuration'];
+ timingFunction?: React.CSSProperties['animationTimingFunction'];
+ direction?: React.CSSProperties['animationDirection'];
+ iterationCount?: React.CSSProperties['animationIterationCount'];
+
+ animations: Animation[];
+ children?: React.ReactNode;
+};
+
+const StyledDiv = styled.div<TransientProps<AnimateProps>>`
+ ${({
+ $animations,
+ $duration = '0.25s',
+ $timingFunction = 'ease',
+ $direction = 'normal',
+ $iterationCount = '1',
+ }) => {
+ const animations = useAnimations($animations);
+ return css`
+ &&[data-present-animation='true'] {
+ display: none;
+
+ &&[data-show='true'] {
+ display: block;
+ }
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ --duration: ${$duration};
+ --timing-function: ${$timingFunction};
+ --direction: ${$direction};
+ --iteration-count: ${$iterationCount};
+
+ interpolate-size: allow-keywords;
+ transition-behavior: allow-discrete;
+
+ overflow: clip;
+ ${animations}
+ }
+ `;
+ }}
+`;
+
+export function Animate({
+ present,
+ animations,
+ duration,
+ timingFunction,
+ direction,
+ iterationCount,
+ ...props
+}: AnimateProps) {
+ return (
+ <StyledDiv
+ data-show={present}
+ data-present-animation={present === undefined ? false : true}
+ $animations={animations}
+ $duration={duration}
+ $timingFunction={timingFunction}
+ $direction={direction}
+ $iterationCount={iterationCount}
+ {...props}
+ />
+ );
}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/animations.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/animations.ts
new file mode 100644
index 0000000000..bec40a1adf
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/animations.ts
@@ -0,0 +1,13 @@
+import { fadeIn, fadeOut } from './fade';
+import { wipeDownIn, wipeVerticalOut } from './wipe';
+
+export const animations = {
+ fade: {
+ in: fadeIn,
+ out: fadeOut,
+ },
+ wipeDown: {
+ in: wipeDownIn,
+ out: wipeVerticalOut,
+ },
+} as const;
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/fade.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/fade.ts
new file mode 100644
index 0000000000..f67b6e1dd5
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/fade.ts
@@ -0,0 +1,31 @@
+import { css } from 'styled-components';
+
+import { createAnimation } from '../utils';
+
+export const fadeIn = createAnimation(
+ 'animation-fade-in',
+ css`
+ from {
+ display: none;
+ opacity: 0;
+ }
+ to {
+ display: block;
+ opacity: 1;
+ }
+ `,
+);
+
+export const fadeOut = createAnimation(
+ 'animation-fade-out',
+ css`
+ from {
+ display: block;
+ opacity: 1;
+ }
+ to {
+ display: none;
+ opacity: 0;
+ }
+ `,
+);
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/index.ts
new file mode 100644
index 0000000000..9616d4a908
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/index.ts
@@ -0,0 +1 @@
+export * from './animations';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/wipe.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/wipe.ts
new file mode 100644
index 0000000000..3bd6ecc6d9
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/wipe.ts
@@ -0,0 +1,31 @@
+import { css } from 'styled-components';
+
+import { createAnimation } from '../utils';
+
+export const wipeDownIn = createAnimation(
+ 'animation-wipe-down-in',
+ css`
+ from {
+ display: none;
+ height: 0;
+ }
+ to {
+ display: block;
+ height: min-content;
+ }
+ `,
+);
+
+export const wipeVerticalOut = createAnimation(
+ 'animation-wipe-vertical-out',
+ css`
+ from {
+ display: block;
+ height: min-content;
+ }
+ to {
+ display: none;
+ height: 0;
+ }
+ `,
+);
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/AnimatePresentVertical.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/AnimatePresentVertical.tsx
deleted file mode 100644
index 31536c93f9..0000000000
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/AnimatePresentVertical.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-export interface AnimatePresentVerticalProps extends React.HTMLAttributes<HTMLDivElement> {
- present?: boolean;
- children?: React.ReactNode;
-}
-
-const StyledDiv = styled.div`
- --display-start: none;
- --height-start: 0;
- --display-end: block;
- --height-end: min-content;
-
- overflow: clip;
- transition-property: display, height;
- transition-duration: 0.25s;
- transition-timing-function: ease;
- interpolate-size: allow-keywords;
- transition-behavior: allow-discrete;
- display: var(--display-start);
- height: var(--height-start);
- &&[data-present='true'] {
- display: var(--display-end);
- height: var(--height-end);
- @starting-style {
- height: var(--height-start);
- }
- }
-`;
-
-export function AnimatePresentVertical({ present, ...props }: AnimatePresentVerticalProps) {
- return <StyledDiv data-present={present} {...props} />;
-}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/index.ts
deleted file mode 100644
index 6bea1c015a..0000000000
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AnimatePresentVertical';
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
new file mode 100644
index 0000000000..8811e6a063
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useAnimations';
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.tsx
new file mode 100644
index 0000000000..1b44da8a30
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.tsx
@@ -0,0 +1,29 @@
+import { css, RuleSet } from 'styled-components';
+
+import { Animation } from '../Animate';
+import { animations } from '../animations';
+import { createAnimationDeclaration } from '../utils';
+
+export const useAnimations = (values: Animation[]) => {
+ const inAnimations: Array<{ name: string; rule: RuleSet }> = [];
+ const outAnimations: Array<{ name: string; rule: RuleSet }> = [];
+
+ values.forEach((animation) => {
+ if (animation.type === 'fade') {
+ inAnimations.push(animations.fade.in);
+ outAnimations.push(animations.fade.out);
+ } else if (animation.type === 'wipe' && animation.direction === 'vertical') {
+ inAnimations.push(animations.wipeDown.in);
+ outAnimations.push(animations.wipeDown.out);
+ }
+ });
+
+ return css`
+ ${inAnimations.map((animation) => animation.rule)}
+ ${outAnimations.map((animation) => animation.rule)}
+ ${createAnimationDeclaration(outAnimations)}
+ &&[data-show='true'] {
+ ${createAnimationDeclaration(inAnimations)}
+ }
+ `;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation-declaration.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation-declaration.ts
new file mode 100644
index 0000000000..e1268e6b76
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation-declaration.ts
@@ -0,0 +1,10 @@
+import { css } from 'styled-components';
+
+export const createAnimationDeclaration = (animations: Array<{ name: string }>) => css`
+ animation: ${animations
+ .map(
+ ({ name }) =>
+ `${name} var(--duration) var(--timing-function) var(--direction) var(--iteration-count)`,
+ )
+ .join(', ')};
+`;
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation.ts
new file mode 100644
index 0000000000..6f3905f5cd
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation.ts
@@ -0,0 +1,10 @@
+import { css, RuleSet } from 'styled-components';
+
+export const createAnimation = (name: string, frames: RuleSet) => ({
+ name,
+ rule: css`
+ @keyframes ${name} {
+ ${frames}
+ }
+ `,
+});
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/index.ts
new file mode 100644
index 0000000000..d3cf17a616
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './create-animation';
+export * from './create-animation-declaration';