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 17 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
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.get() * Number(isExpanded.get()), {
duration,
easing: Easing.inOut(Easing.quad),
}),
);

const derivedOpacity = useDerivedValue(() =>
withTiming(isExpanded.get() ? 1 : 0, {
duration,
easing: Easing.inOut(Easing.quad),
}),
);

const bodyStyle = useAnimatedStyle(() => ({
height: derivedHeight.get(),
opacity: derivedOpacity.get(),
}));

return (
<Animated.View style={[bodyStyle, style]}>
<View
onLayout={(e) => {
height.set(e.nativeEvent.layout.height);
}}
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 @@

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

Check failure on line 55 in src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check


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 @@
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 @@ -18,11 +20,17 @@
function QuickbooksDesktopClassesPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '-1';

Check failure on line 23 in src/pages/workspace/accounting/qbd/import/QuickbooksDesktopClassesPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
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 @@
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 @@
}
/>
</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 @@ -18,11 +20,17 @@
function QuickbooksDesktopCustomersPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '-1';

Check failure on line 23 in src/pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersPage.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

const qbdConfig = policy?.connections?.quickbooksDesktop?.config;
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 @@
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 @@
}
/>
</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 @@ -24,6 +26,12 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
const {vendors} = policy?.connections?.quickbooksOnline?.data ?? {};
const nonReimbursableBillDefaultVendorObject = vendors?.find((vendor) => vendor.id === qboConfig?.nonReimbursableBillDefaultVendor);

const isAccordionExpanded = useSharedValue(!!qboConfig?.autoCreateVendor);

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

const sections = [
{
title: qboConfig?.nonReimbursableExpensesExportDestination ? translate(`workspace.qbo.accounts.${qboConfig?.nonReimbursableExpensesExportDestination}`) : undefined,
Expand Down Expand Up @@ -93,7 +101,10 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
}
onCloseError={() => clearQBOErrorField(policyID, CONST.QUICKBOOKS_CONFIG.AUTO_CREATE_VENDOR)}
/>
{qboConfig?.autoCreateVendor && (
<Accordion
isExpanded={isAccordionExpanded}
style={styles.overflowHidden}
>
<OfflineWithFeedback pendingAction={PolicyUtils.settingsPendingAction([CONST.QUICKBOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR], qboConfig?.pendingFields)}>
<MenuItemWithTopDescription
title={nonReimbursableBillDefaultVendorObject?.name}
Expand All @@ -107,7 +118,7 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
shouldShowRightIcon
/>
</OfflineWithFeedback>
)}
</Accordion>
</>
)}
</ConnectionLayout>
Expand Down
Loading
Loading