Animate between IconX and IconReload (#13949)

Closes #13866 

An animation of 150ms has been introduced to all icons that are rendered
inside `OverridableCheckbox`. Attaching a recording of the same.
[Loom
Recording](https://www.loom.com/share/65293c13171a4bf8934c577eecbca9a0)
This commit is contained in:
Balaji Krishnamurthy
2025-08-20 15:39:06 +05:30
committed by GitHub
parent 3a13d240b4
commit fc39f35ced
3 changed files with 67 additions and 9 deletions
@@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconReload, IconX } from 'twenty-ui/display';
import { Checkbox } from 'twenty-ui/input';
import { AnimatedRotate } from 'twenty-ui/utilities';
export type OverridableCheckboxType = 'default' | 'override' | 'no_cta';
@@ -60,10 +61,12 @@ export const OverridableCheckbox = ({
isDisabled={disabled}
>
{!disabled && (
<IconX
size={theme.icon.size.md}
color={theme.font.color.secondary}
/>
<AnimatedRotate>
<IconX
size={theme.icon.size.md}
color={theme.font.color.secondary}
/>
</AnimatedRotate>
)}
</StyledIconWrapper>
</StyledOverridableCheckboxContainerItem>
@@ -79,17 +82,25 @@ export const OverridableCheckbox = ({
onClick={disabled ? undefined : onChange}
isDisabled={disabled}
>
<IconReload
size={theme.icon.size.md}
color={theme.adaptiveColors.orange4}
/>
<AnimatedRotate animateOnHover>
<IconReload
size={theme.icon.size.md}
color={theme.adaptiveColors.orange4}
/>
</AnimatedRotate>
</StyledIconWrapper>
</StyledOverridableCheckboxContainerItem>
</>
)}
{type === 'no_cta' && (
<StyledOverridableCheckboxContainerItem>
<Checkbox checked={checked} disabled={disabled} onChange={onChange} />
<AnimatedRotate>
<Checkbox
checked={checked}
disabled={disabled}
onChange={onChange}
/>
</AnimatedRotate>
</StyledOverridableCheckboxContainerItem>
)}
</StyledOverridableCheckboxContainer>
@@ -0,0 +1,46 @@
import { useTheme } from '@emotion/react';
import { type AnimationDuration } from '@ui/theme';
import { type HTMLMotionProps, motion } from 'framer-motion';
type AnimatedRotateProps = Omit<
HTMLMotionProps<'div'>,
'initial' | 'animate' | 'transition' | 'exit'
> & {
duration?: AnimationDuration;
animateOnHover?: boolean;
};
export const AnimatedRotate = ({
children,
duration = 'fast',
animateOnHover,
}: AnimatedRotateProps): JSX.Element => {
const theme = useTheme();
const initial = { opacity: 0, rotate: -90 };
const animate = { opacity: 1, rotate: 0 };
const exit = { opacity: 0, rotate: 90 };
const transition = {
duration: theme.animation.duration[duration],
};
return (
<motion.div
initial={initial}
animate={animate}
transition={transition}
exit={exit}
whileHover={
animateOnHover
? {
rotate: 45,
transition: {
duration: theme.animation.duration.fast,
},
}
: {}
}
>
{children}
</motion.div>
);
};
@@ -12,6 +12,7 @@ export { AnimatedContainer } from './animation/components/AnimatedContainer';
export { AnimatedEaseIn } from './animation/components/AnimatedEaseIn';
export { AnimatedEaseInOut } from './animation/components/AnimatedEaseInOut';
export { AnimatedFadeOut } from './animation/components/AnimatedFadeOut';
export { AnimatedRotate } from './animation/components/AnimatedRotate';
export { AnimatedTextWord } from './animation/components/AnimatedTextWord';
export { AnimatedTranslation } from './animation/components/AnimatedTranslation';
export { stringToHslColor } from './color/utils/stringToHslColor';