Adding animation for children of switch components#53938
Conversation
|
🚧 @mountiny has triggered a test build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
Kicu
left a comment
There was a problem hiding this comment.
Looks mostly ok. There is one thing with the translation keys and state that is a bit confusing. If you cannot rewrite it in any way, then at least drop a comment.
Otherwise lgtm 👍
You have a typo in name of the PR: childreans -> childrens
src/components/Acordition/index.tsx
Outdated
| import type {SharedValue} from 'react-native-reanimated'; | ||
| import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
|
|
||
| function AccordionItem({isExpanded, children, duration = 300}: {isExpanded: SharedValue<boolean>; children: ReactNode; duration?: number}) { |
There was a problem hiding this comment.
Please make sure the file and main export are named the same:
Component is called AccordionItem but the file is Accordion/index.ts (you have a type in the name).
src/components/Acordition/index.tsx
Outdated
| })); | ||
|
|
||
| return ( | ||
| <Animated.View style={[bodyStyle, {overflow: 'hidden'}]}> |
There was a problem hiding this comment.
| <Animated.View style={[bodyStyle, {overflow: 'hidden'}]}> | |
| <Animated.View style={[bodyStyle, styles.overflowHidden]}> |
where styles come from:
const styles = useThemeStyles();
src/components/Switch.tsx
Outdated
| disabledAction?: () => void; | ||
|
|
||
| /** | ||
| * onStateChange: Callback function triggered only after the external state of the switch has successfully changed. |
There was a problem hiding this comment.
| * onStateChange: Callback function triggered only after the external state of the switch has successfully changed. | |
| * Callback function triggered only after the external state of the switch has successfully changed. |
repeating the name is not needed, as the name of prop is right below this comment
There was a problem hiding this comment.
I feel like this comment is a bit inaccurate.
This callback is triggered inside Switch component after the external state has changed AND the animation has finished, right?
Because if it was only after state change, then the external component could handle this itself. So I'm guessing it is important that this runs after the line:
offsetX.set(withTiming([...])...
Does this make sense?
There was a problem hiding this comment.
Yes after the animation of moving the circle inside the switch, but it is this callback that actually triggers in our case the animation of rolling up and unrolling children
| if (!isImportMappingEnable) { | ||
| return; | ||
| } | ||
| setTranslationKey(getDisplayTypeTranslationKeys(config?.mappings?.[mappingName])); |
There was a problem hiding this comment.
This line will be confusing, since assigning translation keys to state is quite uncommon.
Perhaps we could add a short 1-line comment to at least say its done for animation purpose?
There was a problem hiding this comment.
The problem here is that changing the switch changes the content of the object:
config.mappings
which resulted in changing the content of the translation itself, that's why I changed the assignment here to only non-empty values, I was afraid of operating on the config logic itself so I think it's safe to leave a comment here
|
🚧 @mountiny has triggered a test build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! 🧪🧪 |
Kicu
left a comment
There was a problem hiding this comment.
LGTM 👍 the animation is tricky but I was not able to come up with a better solution
|
@dubielzyk-expensify @thesahindia One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
blazejkustra
left a comment
There was a problem hiding this comment.
Great work @sumo-slonik 🚀
| // We are storing translation keys in the local state for animation purposes. | ||
| // Otherwise, the values change to undefined immediately after clicking, before the closing animation finishes, | ||
| // resulting in a janky animation effect. | ||
|
|
src/components/Switch.tsx
Outdated
| offsetX.set(withTiming(isOn ? OFFSET_X.ON : OFFSET_X.OFF, {duration: 300})); | ||
| }, [isOn, offsetX]); | ||
| if (onStateChange) { | ||
| onStateChange(isOn); |
There was a problem hiding this comment.
Can't we use onToggle for this? 🤔
There was a problem hiding this comment.
Unfortunately it seems to me that it does not, because doing it in onToggle called the animation trigger at the wrong time, but I will make sure that in the final version of the code it did not become possible
There was a problem hiding this comment.
I was able to remove the use of this method and it is no longer needed.
src/components/Accordion/index.tsx
Outdated
| import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
|
|
||
| function Accordion({isExpanded, children, duration = 300}: {isExpanded: SharedValue<boolean>; children: ReactNode; duration?: number}) { |
There was a problem hiding this comment.
Let's extract props type to a separate type above, a good rule of thumb is to always do it for complex types like this
src/components/Accordion/index.tsx
Outdated
| onLayout={(e) => { | ||
| height.set(e.nativeEvent.layout.height); | ||
| }} | ||
| style={{position: 'absolute', left: 0, right: 0, top: 0}} |
There was a problem hiding this comment.
Let's use styling utilities here:
styles.pAbsolute, styles.t0 ...
|
@dubielzyk-expensify good catch, I'll fix it |
|
@dubielzyk-expensify Screen.Recording.2024-12-17.at.10.53.55.mov |
fe701f5 to
a23be73
Compare
|
@mountiny |
src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
Show resolved
Hide resolved
| if ( | ||
| action && | ||
| 'payload' in action && | ||
| action.payload && | ||
| 'name' in action.payload && | ||
| (isSideModalNavigator(action.payload.name) || action.payload.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR) | ||
| ) { |
There was a problem hiding this comment.
These navigation changes were consulted with me. We need to PUSH screens in FullScreenNavigator instead of NAVIGATE to ensure animations and history work properly
mountiny
left a comment
There was a problem hiding this comment.
Thanks! Asked @thesahindia for final check over and testing
|
@sumo-slonik, it's not working on the native Screen.Recording.2025-01-14.at.10.04.59.PM.mov |
|
@thesahindia I'm looking on it. |
|
@thesahindia Everything should be fine now, you can check and let me know what you think. |
|
@thesahindia should be ready again |
Reviewer Checklist
Screenshots/VideosAndroid: NativeScreen.Recording.2025-01-16.at.1.44.35.AM.movAndroid: mWeb ChromeScreen.Recording.2025-01-16.at.1.48.52.AM.moviOS: NativeScreen.Recording.2025-01-16.at.1.26.04.AM.moviOS: mWeb SafariScreen.Recording.2025-01-14.at.8.55.59.PM.movMacOS: Chrome / SafariScreen.Recording.2025-01-14.at.8.50.38.PM.movMacOS: DesktopScreen.Recording.2025-01-14.at.8.54.02.PM.mov |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.0.87-0 🚀
|
|
🚀 Deployed to production by https://github.com/Beamanator in version: 9.0.87-3 🚀
|
| } | ||
| return { | ||
| height: !isToggleTriggered.get() ? height.get() : derivedHeight.get(), | ||
| opacity: derivedOpacity.get(), |
There was a problem hiding this comment.
This PR introduced a regression: #79463
The Accordion component used absolute positioning for children without setting overflow: 'hidden' on the animated container. This caused collapsed (invisible) content to still intercept taps — e.g., tapping the empty area below a disabled Import toggle in Sage Intacct settings would open the "Displayed as" page.
Fixed in #83651 by adding overflow: 'hidden' when the accordion is collapsed.


Explanation of Change
These PR add animations of the appearance and disappearance of elements after the switch using reanimated.
Fixed Issues
$ #53759
PROPOSAL:
Tests
Offline tests
Offline tests are not needed.
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
android.native.mp4
Android: mWeb Chrome
web.android.mp4
iOS: Native
ios.native.mp4
iOS: mWeb Safari
web.ios.mp4
MacOS: Chrome / Safari
web.mp4
MacOS: Desktop
desktop.mp4