🆙 Updates thanks to Life

react-bootstrap → migrate to shadcn/ui + Tailwind (or HeroUI)
Legacy, ~250KB bundle, dated API, inconsistent with CMS stack

react-transition-group → use framer-motion (already installed!)
De-facto deprecated, duplicate animation lib
This commit is contained in:
DuckieTM
2026-05-03 16:05:23 +02:00
parent 506a29c9a0
commit 99aceefb9e
4 changed files with 76 additions and 166 deletions
-2
View File
@@ -14,13 +14,11 @@
"@emoji-mart/data": "^1.2.1", "@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",
"@tanstack/react-virtual": "3.13.24", "@tanstack/react-virtual": "3.13.24",
"@types/react-transition-group": "^4.4.12",
"dompurify": "^3.4.1", "dompurify": "^3.4.1",
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"emoji-toolkit": "10.0.0", "emoji-toolkit": "10.0.0",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
"react": "^19.2.5", "react": "^19.2.5",
"react-bootstrap": "^2.10.10",
"react-dom": "^19.2.5", "react-dom": "^19.2.5",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-slider": "^2.0.6", "react-slider": "^2.0.6",
+17 -33
View File
@@ -1,6 +1,6 @@
import { FC, ReactNode, useEffect, useState } from 'react'; import { AnimatePresence, motion, Variants } from 'framer-motion';
import { Transition } from 'react-transition-group'; import { FC, ReactNode } from 'react';
import { getTransitionAnimationStyle } from './TransitionAnimationStyles'; import { getTransitionVariants } from './TransitionAnimationStyles';
interface TransitionAnimationProps interface TransitionAnimationProps
{ {
@@ -15,38 +15,22 @@ export const TransitionAnimation: FC<TransitionAnimationProps> = props =>
{ {
const { type = null, inProp = false, timeout = 300, className = null, children = null } = props; const { type = null, inProp = false, timeout = 300, className = null, children = null } = props;
const [ isChildrenVisible, setChildrenVisible ] = useState(false); const variants: Variants = getTransitionVariants(type);
const duration = timeout / 1000;
useEffect(() =>
{
let timeoutData: ReturnType<typeof setTimeout> = null;
if(inProp)
{
setChildrenVisible(true);
}
else
{
timeoutData = setTimeout(() =>
{
setChildrenVisible(false);
clearTimeout(timeout);
}, timeout);
}
return () =>
{
if(timeoutData) clearTimeout(timeoutData);
};
}, [ inProp, timeout ]);
return ( return (
<Transition in={ inProp } timeout={ timeout }> <AnimatePresence initial={ false }>
{ state => ( { inProp && (
<div className={ (className ?? '') + ' animate__animated' } style={ { ...getTransitionAnimationStyle(type, state, timeout) } }> <motion.div
{ isChildrenVisible && children } className={ className ?? '' }
</div> variants={ variants }
initial="hidden"
animate="visible"
exit="exit"
transition={ { duration } }>
{ children }
</motion.div>
) } ) }
</Transition> </AnimatePresence>
); );
}; };
@@ -1,136 +1,66 @@
import { CSSProperties } from 'react'; import { Variants } from 'framer-motion';
import { TransitionStatus } from 'react-transition-group';
import { ENTERING, EXITING } from 'react-transition-group/Transition';
import { TransitionAnimationTypes } from './TransitionAnimationTypes'; import { TransitionAnimationTypes } from './TransitionAnimationTypes';
export function getTransitionAnimationStyle(type: string, transition: TransitionStatus, timeout: number = 300): Partial<CSSProperties> export function getTransitionVariants(type: string): Variants
{ {
switch(type) switch(type)
{ {
case TransitionAnimationTypes.BOUNCE: case TransitionAnimationTypes.BOUNCE:
switch(transition) return {
{ hidden: { opacity: 0, scale: 0.3 },
default: visible: { opacity: 1, scale: 1, transition: { type: 'spring', stiffness: 260, damping: 12 } },
return {}; exit: { opacity: 0, scale: 0.3 }
case ENTERING: };
return {
animationName: 'bounceIn',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'bounceOut',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.SLIDE_LEFT: case TransitionAnimationTypes.SLIDE_LEFT:
switch(transition) return {
{ hidden: { opacity: 0, x: '-100%' },
default: visible: { opacity: 1, x: 0 },
return {}; exit: { opacity: 0, x: '-100%' }
case ENTERING: };
return {
animationName: 'slideInLeft',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'slideOutLeft',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.SLIDE_RIGHT: case TransitionAnimationTypes.SLIDE_RIGHT:
switch(transition) return {
{ hidden: { opacity: 0, x: '100%' },
default: visible: { opacity: 1, x: 0 },
return {}; exit: { opacity: 0, x: '100%' }
case ENTERING: };
return {
animationName: 'slideInRight',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'slideOutRight',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.FLIP_X: case TransitionAnimationTypes.FLIP_X:
switch(transition) return {
{ hidden: { opacity: 0, rotateX: 90 },
default: visible: { opacity: 1, rotateX: 0 },
return {}; exit: { opacity: 0, rotateX: 90 }
case ENTERING: };
return {
animationName: 'flipInX',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'flipOutX',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.FADE_UP: case TransitionAnimationTypes.FADE_UP:
switch(transition) return {
{ hidden: { opacity: 0, y: 20 },
default: visible: { opacity: 1, y: 0 },
return {}; exit: { opacity: 0, y: 20 }
case ENTERING: };
return {
animationName: 'fadeInUp',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'fadeOutDown',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.FADE_IN: case TransitionAnimationTypes.FADE_IN:
switch(transition) return {
{ hidden: { opacity: 0 },
default: visible: { opacity: 1 },
return {}; exit: { opacity: 0 }
case ENTERING: };
return {
animationName: 'fadeIn',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'fadeOut',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.FADE_DOWN: case TransitionAnimationTypes.FADE_DOWN:
switch(transition) return {
{ hidden: { opacity: 0, y: -20 },
default: visible: { opacity: 1, y: 0 },
return {}; exit: { opacity: 0, y: -20 }
case ENTERING: };
return {
animationName: 'fadeInDown',
animationDuration: `${ timeout }ms`
};
case EXITING:
return {
animationName: 'fadeOutUp',
animationDuration: `${ timeout }ms`
};
}
case TransitionAnimationTypes.HEAD_SHAKE: case TransitionAnimationTypes.HEAD_SHAKE:
switch(transition) return {
{ hidden: { x: 0 },
default: visible: {
return {}; x: [ 0, -6, 5, -3, 2, 0 ],
case ENTERING: transition: { duration: 0.5 }
return { },
animationName: 'headShake', exit: { x: 0 }
animationDuration: `${ timeout }ms` };
};
}
} }
return null; return {
hidden: {},
visible: {},
exit: {}
};
} }
+8 -10
View File
@@ -1,5 +1,4 @@
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { LocalizeFormattedNumber, LocalizeShortNumber } from '../../../api'; import { LocalizeFormattedNumber, LocalizeShortNumber } from '../../../api';
import { Flex, LayoutCurrencyIcon, Text } from '../../../common'; import { Flex, LayoutCurrencyIcon, Text } from '../../../common';
@@ -17,7 +16,7 @@ export const CurrencyView: FC<CurrencyViewProps> = props =>
const element = useMemo(() => const element = useMemo(() =>
{ {
return ( return (
<Flex justifyContent="end" pointer gap={ 1 } className={`nitro-purse-button rounded allcurrencypurse nitro-purse-button currency-${type}`}> <Flex justifyContent="end" pointer gap={ 1 } className={ `nitro-purse-button rounded allcurrencypurse nitro-purse-button currency-${ type }` }>
<Text truncate textEnd variant="white" grow>{ short ? LocalizeShortNumber(amount) : LocalizeFormattedNumber(amount) }</Text> <Text truncate textEnd variant="white" grow>{ short ? LocalizeShortNumber(amount) : LocalizeFormattedNumber(amount) }</Text>
<LayoutCurrencyIcon type={ type } /> <LayoutCurrencyIcon type={ type } />
</Flex>); </Flex>);
@@ -26,14 +25,13 @@ export const CurrencyView: FC<CurrencyViewProps> = props =>
if(!short) return element; if(!short) return element;
return ( return (
<OverlayTrigger <div className="group relative">
placement="left"
overlay={
<Tooltip id={ `tooltip-${ type }` }>
{ LocalizeFormattedNumber(amount) }
</Tooltip>
}>
{ element } { element }
</OverlayTrigger> <div
role="tooltip"
className="pointer-events-none absolute right-full top-1/2 z-50 mr-2 -translate-y-1/2 whitespace-nowrap rounded bg-black/80 px-2 py-1 text-xs text-white opacity-0 shadow transition-opacity duration-150 group-hover:opacity-100">
{ LocalizeFormattedNumber(amount) }
</div>
</div>
); );
} }