Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding animation for children of switch components #53938

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dad3f55
smooth animation
sumo-slonik Dec 11, 2024
457b549
work in progress
sumo-slonik Dec 11, 2024
eaa7d47
clean code before PR
sumo-slonik Dec 11, 2024
d852938
animation works in all places
sumo-slonik Dec 12, 2024
2b2b451
refactor before PR
sumo-slonik Dec 12, 2024
c2fb131
changes after code review
sumo-slonik Dec 13, 2024
4473400
changes after Blazej review
sumo-slonik Dec 16, 2024
7443bc5
fix padding problem in toggle page
sumo-slonik Dec 17, 2024
6c8771d
Merge branch 'main' into feature/kuba_nowakowski/add_animation_for_sw…
sumo-slonik Dec 17, 2024
24cd3ca
fix eslint
sumo-slonik Dec 17, 2024
8b7525a
Merge branch 'main' into feature/kuba_nowakowski/add_animation_for_sw…
sumo-slonik Dec 18, 2024
23c3b57
work in progress
sumo-slonik Dec 19, 2024
408750f
adding accordion to all places, remove unnecessary callback in switch…
sumo-slonik Dec 20, 2024
be4015e
add easing to effect
sumo-slonik Dec 20, 2024
53fd209
fix liner reanimated API
sumo-slonik Jan 7, 2025
314dca1
remove unnecessary const
sumo-slonik Jan 7, 2025
d99e589
fix linter
sumo-slonik Jan 7, 2025
20401d1
in progress
sumo-slonik Jan 10, 2025
007eac8
finish adding accordion without refactor
sumo-slonik Jan 10, 2025
65f680d
first part of refactor
sumo-slonik Jan 10, 2025
09794bf
Fix animation on first appearance
sumo-slonik Jan 13, 2025
b9aed8e
fix animation using navigation
sumo-slonik Jan 13, 2025
a23be73
Revert changes different from the workflows page.
sumo-slonik Jan 13, 2025
67a6187
Merge branch 'main' into feature/kuba_nowakowski/add_animation_for_sw…
sumo-slonik Jan 14, 2025
a65661f
change suggested by Vit
sumo-slonik Jan 14, 2025
d7e1334
works almost perfect
sumo-slonik Jan 14, 2025
a174547
works perfect
sumo-slonik Jan 14, 2025
8c39088
ready for tests
sumo-slonik Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/components/Accordion/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type {ReactNode} from 'react';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import type {SharedValue} from 'react-native-reanimated';
import Animated, {Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
import useThemeStyles from '@hooks/useThemeStyles';

type AccordionProps = {
/** Giving information whether the component is open */
isExpanded: SharedValue<boolean>;

/** Element that is inside Accordion */
children: ReactNode;

/** Duration of expansion animation */
duration?: number;

/** Additional external style */
style?: StyleProp<ViewStyle>;

/** Was toggle triggered */
isToggleTriggered: SharedValue<boolean>;
};

function Accordion({isExpanded, children, duration = 300, isToggleTriggered, style}: AccordionProps) {
const height = useSharedValue(0);
const styles = useThemeStyles();

const derivedHeight = useDerivedValue(() => {
if (!isToggleTriggered.get()) {
return isExpanded.get() ? height.get() : 0;
}

return withTiming(height.get() * Number(isExpanded.get()), {
duration,
easing: Easing.inOut(Easing.quad),
});
});

const derivedOpacity = useDerivedValue(() => {
if (!isToggleTriggered.get()) {
return isExpanded.get() ? 1 : 0;
}

return withTiming(isExpanded.get() ? 1 : 0, {
duration,
easing: Easing.inOut(Easing.quad),
});
});

const animatedStyle = useAnimatedStyle(() => {
if (!isToggleTriggered.get() && !isExpanded.get()) {
return {
height: 0,
opacity: 0,
};
}
return {
height: !isToggleTriggered.get() ? height.get() : derivedHeight.get(),
opacity: derivedOpacity.get(),
};
});

return (
<Animated.View style={[animatedStyle, style]}>
<View
onLayout={(e) => {
height.set(e.nativeEvent.layout.height);
}}
style={[styles.pAbsolute, styles.l0, styles.r0, styles.t0]}
>
{children}
</View>
</Animated.View>
);
}

Accordion.displayName = 'Accordion';

export default Accordion;
78 changes: 78 additions & 0 deletions src/components/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type {ReactNode} from 'react';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import type {SharedValue} from 'react-native-reanimated';
import Animated, {Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';

type AccordionProps = {
/** Giving information whether the component is open */
isExpanded: SharedValue<boolean>;

/** Element that is inside Accordion */
children: ReactNode;

/** Duration of expansion animation */
duration?: number;

/** Additional external style */
style?: StyleProp<ViewStyle>;

/** Was toggle triggered */
isToggleTriggered: SharedValue<boolean>;
};

function Accordion({isExpanded, children, duration = 300, isToggleTriggered, style}: AccordionProps) {
const height = useSharedValue(0);

const derivedHeight = useDerivedValue(() => {
if (!isToggleTriggered.get()) {
return isExpanded.get() ? height.get() : 0;
}

return withTiming(height.get() * Number(isExpanded.get()), {
duration,
easing: Easing.inOut(Easing.quad),
});
});

const derivedOpacity = useDerivedValue(() => {
if (!isToggleTriggered.get()) {
return isExpanded.get() ? 1 : 0;
}

return withTiming(isExpanded.get() ? 1 : 0, {
duration,
easing: Easing.inOut(Easing.quad),
});
});

const animatedStyle = useAnimatedStyle(() => {
if (!isToggleTriggered.get() && !isExpanded.get()) {
return {
height: 0,
opacity: 0,
};
}

return {
height: !isToggleTriggered.get() ? undefined : derivedHeight.get(),
opacity: derivedOpacity.get(),
};
});

return (
<Animated.View style={[animatedStyle, style]}>
<View
onLayout={(e) => {
height.set(e.nativeEvent.layout.height);
}}
>
{children}
</View>
</Animated.View>
);
}
sumo-slonik marked this conversation as resolved.
Show resolved Hide resolved
Accordion.displayName = 'Accordion';

export default Accordion;
10 changes: 8 additions & 2 deletions src/libs/Navigation/linkTo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@ export default function linkTo(navigation: NavigationContainerRef<RootStackParam
}
}

if (action && 'payload' in action && action.payload && 'name' in action.payload && isSideModalNavigator(action.payload.name)) {
if (
action &&
'payload' in action &&
action.payload &&
'name' in action.payload &&
(isSideModalNavigator(action.payload.name) || action.payload.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR)
) {
Comment on lines +186 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These navigation changes were consulted with me. We need to PUSH screens in FullScreenNavigator instead of NAVIGATE to ensure animations and history work properly

// Information about the state may be in the params.
const currentFocusedRoute = findFocusedRoute(extrapolateStateFromParams(rootState));
const targetFocusedRoute = findFocusedRoute(stateFromPath);
Expand All @@ -201,7 +207,7 @@ export default function linkTo(navigation: NavigationContainerRef<RootStackParam
// There are situations where a route already exists on the current navigation stack
// But we want to push the same route instead of going back in the stack
// Which would break the user navigation history
if (!isActiveRoute && type === CONST.NAVIGATION.ACTION_TYPE.PUSH) {
if ((!isActiveRoute && type === CONST.NAVIGATION.ACTION_TYPE.PUSH) || action.payload.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR) {
minimalAction.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
}
root.dispatch(minimalAction);
Expand Down
27 changes: 24 additions & 3 deletions src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {ReactNode} from 'react';
import React, {useMemo} from 'react';
import React, {useEffect, useMemo} from 'react';
import {View} from 'react-native';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import Icon from '@components/Icon';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import RenderHTML from '@components/RenderHTML';
Expand Down Expand Up @@ -46,6 +48,9 @@ type ToggleSettingOptionRowProps = {
/** Used to apply styles to the Subtitle */
subtitleStyle?: StyleProp<TextStyle>;

/** Used to apply styles to the Accordion */
accordionStyle?: StyleProp<ViewStyle>;

/** Whether the option is enabled or not */
isActive: boolean;

Expand Down Expand Up @@ -81,6 +86,7 @@ function ToggleSettingOptionRow({
customTitle,
subtitle,
subtitleStyle,
accordionStyle,
switchAccessibilityLabel,
shouldPlaceSubtitleBelowSwitch,
shouldEscapeText = undefined,
Expand All @@ -98,6 +104,12 @@ function ToggleSettingOptionRow({
showLockIcon = false,
}: ToggleSettingOptionRowProps) {
const styles = useThemeStyles();
const isExpanded = useSharedValue(isActive);
const isToggleTriggered = useSharedValue(false);

useEffect(() => {
isExpanded.set(isActive);
}, [isExpanded, isActive]);

const subtitleHtml = useMemo(() => {
if (!subtitle || !shouldParseSubtitle || typeof subtitle !== 'string') {
Expand Down Expand Up @@ -171,14 +183,23 @@ function ToggleSettingOptionRow({
<Switch
disabledAction={disabledAction}
accessibilityLabel={switchAccessibilityLabel}
onToggle={onToggle}
onToggle={(isOn) => {
isToggleTriggered.set(true);
onToggle(isOn);
}}
isOn={isActive}
disabled={disabled}
showLockIcon={showLockIcon}
/>
</View>
{shouldPlaceSubtitleBelowSwitch && subtitle && subTitleView}
{isActive && subMenuItems}
<Accordion
isExpanded={isExpanded}
style={accordionStyle}
isToggleTriggered={isToggleTriggered}
>
{subMenuItems}
</Accordion>
</View>
</OfflineWithFeedback>
);
Expand Down
Loading