diff options
| author | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-11-04 13:34:24 +0100 |
|---|---|---|
| committer | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-11-04 13:34:24 +0100 |
| commit | d0f4e5fa0d48fe09293fdc61200ba7bf99759741 (patch) | |
| tree | edf0c1286d726b86769eaafda1c5d347beba1896 | |
| parent | 426c3d6ad4f57dbb503b7b7fbddc43c334d9c7ca (diff) | |
| parent | 09886c5fb0035b3613d53f5059f96ab0415ddd47 (diff) | |
| download | mullvadvpn-d0f4e5fa0d48fe09293fdc61200ba7bf99759741.tar.xz mullvadvpn-d0f4e5fa0d48fe09293fdc61200ba7bf99759741.zip | |
Merge branch 'accordion-animation'
17 files changed, 25 insertions, 367 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/accordion/components/AccordionContent.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/accordion/components/AccordionContent.tsx index beeb3ff238..99005180a0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/accordion/components/AccordionContent.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/accordion/components/AccordionContent.tsx @@ -1,26 +1,41 @@ +import { AnimatePresence, motion } from 'motion/react'; +import React from 'react'; import styled from 'styled-components'; -import { Animate } from '../../animate'; import { useAccordionContext } from '../AccordionContext'; export type AccordionContentProps = { children?: React.ReactNode; }; -const StyledAccordionContent = styled.div` +const StyledAccordionContent = styled(motion.div)` width: 100%; + overflow: hidden; `; +const variants = { + collapsed: { height: 0, opacity: 0 }, + expanded: { height: 'auto', opacity: 1 }, +}; + export function AccordionContent({ children }: AccordionContentProps) { const { contentId, triggerId, expanded } = useAccordionContext(); return ( - <Animate - present={expanded} - animations={[{ type: 'wipe', direction: 'vertical' }]} - duration="0.35s"> - <StyledAccordionContent id={contentId} aria-labelledby={triggerId} role="region"> - {children} - </StyledAccordionContent> - </Animate> + <AnimatePresence initial={false}> + {expanded && ( + <StyledAccordionContent + id={contentId} + aria-labelledby={triggerId} + layout + role="region" + variants={variants} + initial="collapsed" + animate="expanded" + exit="collapsed" + transition={{ duration: 0.25, ease: 'easeInOut' }}> + {children} + </StyledAccordionContent> + )} + </AnimatePresence> ); } 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 deleted file mode 100644 index 1c806b9a8d..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/Animate.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import styled, { css, RuleSet } from 'styled-components'; - -import { TransientProps } from '../../types'; -import { AnimateProvider, useAnimateContext } from './AnimateContext'; -import { useAnimate, useAnimations, useHandleAnimationEnd } from './hooks'; -import { Animation } from './types'; - -type AnimateBaseProps = { - initial?: boolean; - present?: boolean; - duration?: React.CSSProperties['animationDuration']; - timingFunction?: React.CSSProperties['animationTimingFunction']; - direction?: React.CSSProperties['animationDirection']; - iterationCount?: React.CSSProperties['animationIterationCount']; - - animations: Animation[]; - children?: React.ReactNode; -}; - -export type AnimateProps = AnimateBaseProps & - Omit<React.HTMLAttributes<HTMLDivElement>, keyof AnimateBaseProps>; - -const StyledDiv = styled.div< - TransientProps<Omit<AnimateBaseProps, 'animations' | 'present'>> & { - $animations: RuleSet; - } ->` - ${({ - $animations, - $duration = '0.25s', - $timingFunction = 'ease', - $direction = 'normal', - $iterationCount = '1', - }) => { - return css` - // If the user prefers reduced motion, visibility still needs - // to be toggled, otherwise this is handled by animations - display: none; - - &&[data-present='true'] { - display: block; - } - - @media (prefers-reduced-motion: no-preference) { - &&[data-animate='true'] { - --duration: ${$duration}; - --timing-function: ${$timingFunction}; - --direction: ${$direction}; - --iteration-count: ${$iterationCount}; - - interpolate-size: allow-keywords; - transition-behavior: allow-discrete; - - overflow: clip; - ${$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({ animations, initial, present = true, children, ...props }: AnimateProps) { - return ( - <AnimateProvider animations={animations} initial={initial} present={present}> - <AnimateImpl {...props}>{children}</AnimateImpl> - </AnimateProvider> - ); -} - -export type AnimateImplProps = Omit<AnimateProps, 'animations' | 'present'>; - -function AnimateImpl({ - duration, - timingFunction, - direction, - iterationCount, - onAnimationEnd, - ...props -}: AnimateImplProps) { - const { animatePresent } = useAnimateContext(); - const animations = useAnimations(); - const animate = useAnimate(); - const handleAnimationEnd = useHandleAnimationEnd(); - - return ( - <StyledDiv - data-animate={animate} - data-present={animatePresent} - onAnimationEnd={handleAnimationEnd} - $animations={animations} - $duration={duration} - $timingFunction={timingFunction} - $direction={direction} - $iterationCount={iterationCount} - {...props} - /> - ); -} diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/AnimateContext.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/AnimateContext.tsx deleted file mode 100644 index d168f07a2e..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/AnimateContext.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState } from 'react'; - -import { Animation } from './types'; - -interface AnimateContextProps { - animations: Animation[]; - animate: boolean; - animatePresent: boolean; - present: boolean; - initial?: boolean; - setAnimate: React.Dispatch<React.SetStateAction<boolean>>; - setAnimatePresent: React.Dispatch<React.SetStateAction<boolean>>; -} - -const AnimateContext = React.createContext<AnimateContextProps | undefined>(undefined); - -export const useAnimateContext = (): AnimateContextProps => { - const context = React.useContext(AnimateContext); - if (!context) { - throw new Error('useButtonContext must be used within a ButtonProvider'); - } - return context; -}; - -interface AnimateProviderProps { - animations: Animation[]; - present: boolean; - initial?: boolean; - children: React.ReactNode; -} - -export const AnimateProvider = ({ - animations, - present, - initial, - children, -}: AnimateProviderProps) => { - const [animate, setAnimate] = React.useState<boolean>((initial && present) || false); - const [animatePresent, setAnimatePresent] = useState<boolean>(present); - - return ( - <AnimateContext.Provider - value={{ - animate, - animatePresent, - animations, - initial, - present, - setAnimate, - setAnimatePresent, - }}> - {children} - </AnimateContext.Provider> - ); -}; 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 deleted file mode 100644 index bec40a1adf..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/animations.ts +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index f67b6e1dd5..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/fade.ts +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 9616d4a908..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/index.ts +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index bc0fff3807..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/animations/wipe.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { css } from 'styled-components'; - -import { createAnimation } from '../utils'; - -export const wipeDownIn = createAnimation( - 'animation-wipe-down-in', - css` - from { - display: none; - max-height: 0; - } - to { - display: block; - max-height: min-content; - } - `, -); - -export const wipeVerticalOut = createAnimation( - 'animation-wipe-vertical-out', - css` - from { - display: block; - max-height: min-content; - } - to { - display: none; - max-height: 0; - } - `, -); 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 deleted file mode 100644 index 8d6128d192..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './useAnimate'; -export * from './useAnimations'; -export * from './useHandleAnimationEnd'; -export * from './usePreviousValue'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimate.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimate.ts deleted file mode 100644 index f67405c72d..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimate.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect } from 'react'; - -import { useAnimateContext } from '../AnimateContext'; -import { usePreviousValue } from './usePreviousValue'; - -export const useAnimate = () => { - const { animate, present, setAnimate, setAnimatePresent } = useAnimateContext(); - const previousPresent = usePreviousValue(present); - - useEffect(() => { - if (present !== previousPresent) { - setAnimate(true); - setAnimatePresent(present); - } - }, [present, previousPresent, setAnimate, setAnimatePresent]); - - return animate; -}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts deleted file mode 100644 index 232bcd99b5..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useAnimations.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { css, RuleSet } from 'styled-components'; - -import { useAnimateContext } from '../AnimateContext'; -import { animations } from '../animations'; -import { createAnimationDeclaration } from '../utils'; - -export const useAnimations = () => { - const { animations: values } = useAnimateContext(); - - 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-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 deleted file mode 100644 index 53389437bc..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/useHandleAnimationEnd.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useCallback, useRef } from 'react'; - -import { useAnimateContext } from '../AnimateContext'; - -export const useHandleAnimationEnd = () => { - const { animations, present, setAnimate, setAnimatePresent } = useAnimateContext(); - const animationsCount = animations.length; - const animationsFinishedCount = useRef(0); - - const handleAnimationEnd = useCallback(() => { - const nextAnimationsFinishedCount = animationsFinishedCount.current + 1; - - if (nextAnimationsFinishedCount === animationsCount) { - animationsFinishedCount.current = 0; - setAnimate(false); - setAnimatePresent(present); - } else { - animationsFinishedCount.current = nextAnimationsFinishedCount; - } - }, [animationsCount, animationsFinishedCount, present, setAnimate, setAnimatePresent]); - - return handleAnimationEnd; -}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/usePreviousValue.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/usePreviousValue.ts deleted file mode 100644 index 1b835a7823..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/hooks/usePreviousValue.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useEffect, useState } from 'react'; - -export const usePreviousValue = <T>(value: T) => { - const [previousValue, setPreviousValue] = useState(value); - - useEffect(() => { - setPreviousValue(value); - }, [setPreviousValue, value]); - - return previousValue; -}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/index.ts deleted file mode 100644 index c84772d940..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Animate'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/types.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/types.ts deleted file mode 100644 index 6b51da2889..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type Animation = FadeAnimation | WipeAnimation; - -export type FadeAnimation = { - type: 'fade'; -}; - -export type WipeAnimation = { - type: 'wipe'; - direction: 'vertical'; -}; 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 deleted file mode 100644 index e1268e6b76..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation-declaration.ts +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 6f3905f5cd..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/create-animation.ts +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index d3cf17a616..0000000000 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/animate/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './create-animation'; -export * from './create-animation-declaration'; |
