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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6440,6 +6440,8 @@ const CONST = {
},

MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal',

DEFAULT_POLICY_ID: '-1',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
DEFAULT_POLICY_ID: '-1',

} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand Down
60 changes: 60 additions & 0 deletions src/components/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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>;
};

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

const derivedHeight = useDerivedValue(() =>
withTiming(height.value * Number(isExpanded.value), {

Check failure on line 28 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead

Check failure on line 28 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead
duration,
easing: Easing.inOut(Easing.quad),
}),
);

const derivedOpacity = useDerivedValue(() =>
withTiming(isExpanded.value ? 1 : 0, {

Check failure on line 35 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead
duration,
easing: Easing.inOut(Easing.quad),
}),
);

const bodyStyle = useAnimatedStyle(() => ({
height: derivedHeight.value,

Check failure on line 42 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead
opacity: derivedOpacity.value,

Check failure on line 43 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead
}));

return (
<Animated.View style={[bodyStyle, style]}>
<View
onLayout={(e) => {
height.value = e.nativeEvent.layout.height;

Check failure on line 50 in src/components/Accordion/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'value' is deprecated. Use the new `.get()` and `.set(value)` methods instead
}}
style={[styles.pAbsolute, styles.l0, styles.r0, styles.t0]}
>
{children}
</View>
</Animated.View>
);
}

export default Accordion;
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, {useMemo} from 'react';
import React, {useEffect, useMemo} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand Down Expand Up @@ -28,11 +30,16 @@

function SageIntacctAdvancedPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const policyID = policy?.id ?? '-1';

Check failure on line 33 in src/pages/workspace/accounting/intacct/advanced/SageIntacctAdvancedPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

const styles = useThemeStyles();

const {importEmployees, autoSync, sync, pendingFields, errorFields} = policy?.connections?.intacct?.config ?? {};
const {data, config} = policy?.connections?.intacct ?? {};
const isAccordionExpanded = useSharedValue(!!sync?.syncReimbursedReports);

useEffect(() => {
isAccordionExpanded.set(!!sync?.syncReimbursedReports);
}, [isAccordionExpanded, sync?.syncReimbursedReports]);

const toggleSections = useMemo(
() => [
Expand Down Expand Up @@ -70,7 +77,7 @@
updateSageIntacctSyncReimbursedReports(policyID, enabled);

if (enabled && !sync?.reimbursementAccountID) {
const reimbursementAccountID = data?.bankAccounts[0]?.id ?? '';

Check failure on line 80 in src/pages/workspace/accounting/intacct/advanced/SageIntacctAdvancedPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

updateSageIntacctSyncReimbursementAccountID(policyID, reimbursementAccountID);
}
},
Expand Down Expand Up @@ -113,20 +120,23 @@
/>
))}

{!!sync?.syncReimbursedReports && (
<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback
key={translate('workspace.sageIntacct.paymentAccount')}
pendingAction={settingsPendingAction([CONST.SAGE_INTACCT_CONFIG.REIMBURSEMENT_ACCOUNT_ID], pendingFields)}
>
<MenuItemWithTopDescription
title={getReimbursedAccountName(data?.bankAccounts ?? [], sync.reimbursementAccountID) ?? translate('workspace.sageIntacct.notConfigured')}
title={getReimbursedAccountName(data?.bankAccounts ?? [], sync?.reimbursementAccountID) ?? translate('workspace.sageIntacct.notConfigured')}
description={translate('workspace.sageIntacct.paymentAccount')}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PAYMENT_ACCOUNT.getRoute(policyID))}
brickRoadIndicator={areSettingsInErrorFields([CONST.SAGE_INTACCT_CONFIG.REIMBURSEMENT_ACCOUNT_ID], errorFields) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
</OfflineWithFeedback>
)}
</Accordion>
</ConnectionLayout>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Str} from 'expensify-common';
import React from 'react';
import React, {useEffect, useState} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand Down Expand Up @@ -50,11 +52,23 @@ function SageIntacctToggleMappingsPage({route}: SageIntacctToggleMappingsPagePro

const policy = usePolicy(route.params.policyID);
const mappingName: SageIntacctMappingName = route.params.mapping;
const policyID: string = policy?.id ?? '-1';

const policyID: string = policy?.id ?? CONST.DEFAULT_POLICY_ID;
Copy link
Contributor

@blazejkustra blazejkustra Dec 19, 2024

Choose a reason for hiding this comment

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

Please read through contributingGuides/STYLE.md (Default value for inexistent IDs). I think we shouldn't add DEFAULT_POLICY_ID and instead use no default id at all, what do you think? @sumo-slonik

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, let's avoid any defaulting values for string ids!
In this case maybe you can try to use route.params.policyID - this way you won't need to handle undefined cases, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems to me that the example in contributingGuides/STYLE.md indicates that we should have no value, but I wanted to keep the sense of the code. But I will do it as you suggest.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes please lets remove this

Copy link
Member

Choose a reason for hiding this comment

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

const config = policy?.connections?.intacct?.config;
const translationKeys = getDisplayTypeTranslationKeys(config?.mappings?.[mappingName]);
const isImportMappingEnable = config?.mappings?.[mappingName] !== CONST.SAGE_INTACCT_MAPPING_VALUE.NONE;
const isAccordionExpanded = useSharedValue(isImportMappingEnable);

// 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.
const [translationKeys, setTranslationKey] = useState<DisplayTypeTranslationKeys | undefined>(undefined);

useEffect(() => {
if (!isImportMappingEnable) {
return;
}
setTranslationKey(getDisplayTypeTranslationKeys(config?.mappings?.[mappingName]));
Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

}, [isImportMappingEnable, config?.mappings, mappingName]);

return (
<ConnectionLayout
displayName={SageIntacctToggleMappingsPage.displayName}
Expand All @@ -81,28 +95,27 @@ function SageIntacctToggleMappingsPage({route}: SageIntacctToggleMappingsPagePro
onToggle={(enabled) => {
const mappingValue = enabled ? CONST.SAGE_INTACCT_MAPPING_VALUE.TAG : CONST.SAGE_INTACCT_MAPPING_VALUE.NONE;
updateSageIntacctMappingValue(policyID, mappingName, mappingValue, config?.mappings?.[mappingName]);
isAccordionExpanded.set(enabled);
}}
pendingAction={settingsPendingAction([mappingName], config?.pendingFields)}
errors={ErrorUtils.getLatestErrorField(config ?? {}, mappingName)}
onCloseError={() => clearSageIntacctErrorField(policyID, mappingName)}
/>
{isImportMappingEnable && (
<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback pendingAction={settingsPendingAction([mappingName], config?.pendingFields)}>
<MenuItemWithTopDescription
title={translationKeys?.titleKey ? translate(translationKeys?.titleKey) : undefined}
description={translate('workspace.common.displayedAs')}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE.getRoute(policyID, mappingName))}
brickRoadIndicator={areSettingsInErrorFields([mappingName], config?.errorFields) ? 'error' : undefined}
hintText={translationKeys?.descriptionKey ? translate(translationKeys?.descriptionKey) : undefined}
/>
<Text
style={[styles.textLabelSupporting, styles.ph5]}
numberOfLines={2}
>
{translationKeys?.descriptionKey ? translate(translationKeys?.descriptionKey) : undefined}
</Text>
</OfflineWithFeedback>
)}
</Accordion>
</ConnectionLayout>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {CONST as COMMON_CONST} from 'expensify-common';
import React from 'react';
import React, {useEffect} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand All @@ -24,11 +26,17 @@
const {translate} = useLocalize();
const config = policy?.connections?.netsuite?.options?.config;
const autoSyncConfig = policy?.connections?.netsuite?.config;
const policyID = route.params.policyID ?? '-1';

Check failure on line 29 in src/pages/workspace/accounting/netsuite/advanced/NetSuiteAutoSyncPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

const accountingMethod = policy?.connections?.netsuite?.options?.config?.accountingMethod;
const pendingAction =
settingsPendingAction([CONST.NETSUITE_CONFIG.AUTO_SYNC], autoSyncConfig?.pendingFields) ?? settingsPendingAction([CONST.NETSUITE_CONFIG.ACCOUNTING_METHOD], config?.pendingFields);

const isAccordionExpanded = useSharedValue(!!autoSyncConfig?.autoSync?.enabled);

useEffect(() => {
isAccordionExpanded.set(!!autoSyncConfig?.autoSync?.enabled);
}, [isAccordionExpanded, autoSyncConfig?.autoSync?.enabled]);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand Down Expand Up @@ -58,7 +66,11 @@
pendingAction={pendingAction}
errors={ErrorUtils.getLatestErrorField(autoSyncConfig, CONST.NETSUITE_CONFIG.AUTO_SYNC)}
/>
{!!autoSyncConfig?.autoSync?.enabled && (

<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback pendingAction={pendingAction}>
<MenuItemWithTopDescription
title={
Expand All @@ -71,7 +83,7 @@
onPress={() => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_ACCOUNTING_METHOD.getRoute(policyID))}
/>
</OfflineWithFeedback>
)}
</Accordion>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, {useMemo} from 'react';
import React, {useEffect, useMemo} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand All @@ -20,7 +22,7 @@
function QuickbooksDesktopCompanyCardExpenseAccountPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '-1';

Check failure on line 25 in src/pages/workspace/accounting/qbd/export/QuickbooksDesktopCompanyCardExpenseAccountPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
const {vendors} = policy?.connections?.quickbooksDesktop?.data ?? {};
const nonReimbursableBillDefaultVendorObject = vendors?.find((vendor) => vendor.id === qbdConfig?.export?.nonReimbursableBillDefaultVendor);
Expand All @@ -34,6 +36,12 @@
return qbdReimbursableAccounts.find(({id}) => nonReimbursableAccount === id)?.name || qbdReimbursableAccounts.at(0)?.name || translate('workspace.qbd.notConfigured');
}, [policy?.connections?.quickbooksDesktop, nonReimbursable, translate, nonReimbursableAccount]);

const isAccordionExpanded = useSharedValue(!!qbdConfig?.shouldAutoCreateVendor);

useEffect(() => {
isAccordionExpanded.set(!!qbdConfig?.shouldAutoCreateVendor);
}, [isAccordionExpanded, qbdConfig?.shouldAutoCreateVendor]);

const sections = [
{
title: nonReimbursable ? translate(`workspace.qbd.accounts.${nonReimbursable}`) : undefined,
Expand Down Expand Up @@ -96,7 +104,11 @@
onToggle={(isOn) => QuickbooksDesktop.updateQuickbooksDesktopShouldAutoCreateVendor(policyID, isOn)}
onCloseError={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR)}
/>
{!!qbdConfig?.shouldAutoCreateVendor && (

<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback
pendingAction={PolicyUtils.settingsPendingAction(
[CONST.QUICKBOOKS_DESKTOP_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR, CONST.QUICKBOOKS_DESKTOP_CONFIG.SHOULD_AUTO_CREATE_VENDOR],
Expand All @@ -115,7 +127,7 @@
shouldShowRightIcon
/>
</OfflineWithFeedback>
)}
</Accordion>
</>
)}
</ConnectionLayout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import React, {useEffect} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand All @@ -23,6 +25,12 @@ function QuickbooksDesktopClassesPage({policy}: WithPolicyProps) {
const isSwitchOn = !!(qbdConfig?.mappings?.classes && qbdConfig.mappings.classes !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
const isReportFieldsSelected = qbdConfig?.mappings?.classes === CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD;

const isAccordionExpanded = useSharedValue(isSwitchOn);

useEffect(() => {
isAccordionExpanded.set(isSwitchOn);
}, [isAccordionExpanded, isSwitchOn]);

return (
<ConnectionLayout
displayName={QuickbooksDesktopClassesPage.displayName}
Expand Down Expand Up @@ -50,7 +58,10 @@ function QuickbooksDesktopClassesPage({policy}: WithPolicyProps) {
errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CLASSES)}
onCloseError={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CLASSES)}
/>
{isSwitchOn && (
<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CLASSES], qbdConfig?.pendingFields)}>
<MenuItemWithTopDescription
title={isReportFieldsSelected ? translate('workspace.common.reportFields') : translate('workspace.common.tags')}
Expand All @@ -65,7 +76,7 @@ function QuickbooksDesktopClassesPage({policy}: WithPolicyProps) {
}
/>
</OfflineWithFeedback>
)}
</Accordion>
</ConnectionLayout>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import React, {useEffect} from 'react';
import {useSharedValue} from 'react-native-reanimated';
import Accordion from '@components/Accordion';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand All @@ -23,6 +25,12 @@ function QuickbooksDesktopCustomersPage({policy}: WithPolicyProps) {
const isSwitchOn = !!(qbdConfig?.mappings?.customers && qbdConfig.mappings.customers !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
const isReportFieldsSelected = qbdConfig?.mappings?.customers === CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD;

const isAccordionExpanded = useSharedValue(isSwitchOn);

useEffect(() => {
isAccordionExpanded.set(isSwitchOn);
}, [isAccordionExpanded, isSwitchOn]);

return (
<ConnectionLayout
displayName={QuickbooksDesktopCustomersPage.displayName}
Expand Down Expand Up @@ -50,7 +58,10 @@ function QuickbooksDesktopCustomersPage({policy}: WithPolicyProps) {
errors={ErrorUtils.getLatestErrorField(qbdConfig, CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CUSTOMERS)}
onCloseError={() => clearQBDErrorField(policyID, CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CUSTOMERS)}
/>
{isSwitchOn && (
<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_DESKTOP_CONFIG.MAPPINGS.CUSTOMERS], qbdConfig?.pendingFields)}>
<MenuItemWithTopDescription
title={isReportFieldsSelected ? translate('workspace.common.reportFields') : translate('workspace.common.tags')}
Expand All @@ -65,7 +76,7 @@ function QuickbooksDesktopCustomersPage({policy}: WithPolicyProps) {
}
/>
</OfflineWithFeedback>
)}
</Accordion>
</ConnectionLayout>
);
}
Expand Down
Loading
Loading