Skip to content

Side Pane Stage 2: Add baseline content for each major section #58004

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

Merged
merged 24 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
18e58a3
Fix Help RHP does not close when clicking outside RHP or with ESC key
blazejkustra Mar 6, 2025
d4841c1
Fix eslint
blazejkustra Mar 6, 2025
f0a5312
Merge branch 'main' of github.com:Expensify/App into fix-side-pane/es…
blazejkustra Mar 6, 2025
a2ab1f5
Fix tests
blazejkustra Mar 6, 2025
2dc3af5
SidePane navigation fixes
blazejkustra Mar 6, 2025
f9579e4
Move SidePane over BottomBar
blazejkustra Mar 6, 2025
3c0f398
Remove ScreenWrapper from the sidepane
blazejkustra Mar 6, 2025
615d0ff
Merge branch 'fix-side-pane/escape-rhp' into side-pane/stage-2
blazejkustra Mar 7, 2025
20e024c
Remove sidePane patch
blazejkustra Mar 6, 2025
23edfed
Add documentation for substituteRouteParameters function in SidePaneU…
blazejkustra Mar 7, 2025
ebc5cbb
Change onyx name for nvp
blazejkustra Mar 7, 2025
e892e91
Show HelpButton on staging by default
blazejkustra Mar 7, 2025
793979d
Centralize helpButton logic in useSidePane
blazejkustra Mar 7, 2025
55b0f06
Remove unused FadeOut animation from HelpButton component
blazejkustra Mar 7, 2025
48c07ec
Create content mapping and logic to display appropriate content based…
blazejkustra Mar 7, 2025
d1e32aa
Add diagnostic data on dev and staging
blazejkustra Mar 7, 2025
e97ff0f
Remove unnecessary styles for stack navigator
blazejkustra Mar 7, 2025
a4aea2b
Get state from useRootNavigationState
blazejkustra Mar 7, 2025
0890ba3
Always display diagnostic data on dev/staging
blazejkustra Mar 7, 2025
f031128
Fix missing bottomTab on search screen
blazejkustra Mar 7, 2025
d343468
Fix desktop header gap
blazejkustra Mar 7, 2025
ddfd033
Create RootNavigatorExtraContent
blazejkustra Mar 7, 2025
181d6c7
Remove animation, change useSidePane button logic
blazejkustra Mar 7, 2025
9063a67
Fix navigation in very very big edgecase
blazejkustra Mar 7, 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
92 changes: 0 additions & 92 deletions patches/@react-navigation+core+6.4.11+004+side-pane.patch

This file was deleted.

2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ const ONYXKEYS = {
TRAVEL_PROVISIONING: 'travelProvisioning',

/** Stores the information about the state of side panel */
NVP_SIDE_PANE: 'nvp_sidePaneExpanded',
NVP_SIDE_PANE: 'nvp_sidePane',

/** Collection Keys */
COLLECTION: {
Expand Down
22 changes: 22 additions & 0 deletions src/components/Navigation/RootNavigatorExtraContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {ParamListBase} from '@react-navigation/native';
import React from 'react';
import SidePane from '@components/SidePane';
import type {PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types';
import TopLevelBottomTabBar from './TopLevelBottomTabBar';

type RootNavigatorExtraContentProps = {
state: PlatformStackNavigationState<ParamListBase>;
};

function RootNavigatorExtraContent({state}: RootNavigatorExtraContentProps) {
return (
<>
<TopLevelBottomTabBar state={state} />
<SidePane />
</>
);
}

RootNavigatorExtraContent.displayName = 'RootNavigatorExtraContent';

export default RootNavigatorExtraContent;
6 changes: 2 additions & 4 deletions src/components/Navigation/TopLevelBottomTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {InteractionManager, View} from 'react-native';
import {FullScreenBlockingViewContext} from '@components/FullScreenBlockingViewContextProvider';
import BottomTabBar from '@components/Navigation/BottomTabBar';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSidePane from '@hooks/useSidePane';
import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets';
import useThemeStyles from '@hooks/useThemeStyles';
import type {PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types';
Expand All @@ -31,11 +30,10 @@ function TopLevelBottomTabBar({state}: TopLevelBottomTabBarProps) {
const [isAfterClosingTransition, setIsAfterClosingTransition] = useState(false);
const cancelAfterInteractions = useRef<ReturnType<typeof InteractionManager.runAfterInteractions> | undefined>();
const {isBlockingViewVisible} = useContext(FullScreenBlockingViewContext);
const {shouldHideTopLevelBottomBar} = useSidePane();

// That means it's visible and it's not covered by the overlay.
const isBottomTabVisibleDirectly = getIsBottomTabVisibleDirectly(state) && !shouldHideTopLevelBottomBar;
const isScreenWithBottomTabFocused = getIsScreenWithBottomTabFocused(state) && !shouldHideTopLevelBottomBar;
const isBottomTabVisibleDirectly = getIsBottomTabVisibleDirectly(state);
const isScreenWithBottomTabFocused = getIsScreenWithBottomTabFocused(state);
const selectedTab = getSelectedTab(state);

const shouldDisplayBottomBar = shouldUseNarrowLayout ? isScreenWithBottomTabFocused : isBottomTabVisibleDirectly;
Expand Down
11 changes: 4 additions & 7 deletions src/components/SidePane/HelpButton.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
import Tooltip from '@components/Tooltip';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSidePane from '@hooks/useSidePane';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {triggerSidePane} from '@libs/actions/SidePane';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type HelpButtonProps = {
style?: StyleProp<ViewStyle>;
Expand All @@ -21,19 +19,18 @@ function HelpButton({style}: HelpButtonProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const [sidePane] = useOnyx(ONYXKEYS.NVP_SIDE_PANE);
const [language] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE);
const {isExtraLargeScreenWidth} = useResponsiveLayout();
const {sidePane, shouldHideHelpButton} = useSidePane();

if (!sidePane || language !== CONST.LOCALES.EN) {
if (shouldHideHelpButton) {
return null;
}

return (
<Tooltip text={translate('common.help')}>
<PressableWithoutFeedback
accessibilityLabel={translate('common.help')}
style={[styles.flexRow, styles.touchableButtonImage, styles.pr2, style]}
style={[styles.flexRow, styles.touchableButtonImage, styles.mr2, style]}
onPress={() => triggerSidePane(isExtraLargeScreenWidth ? !sidePane?.open : !sidePane?.openNarrowScreen, {shouldUpdateNarrowLayout: !isExtraLargeScreenWidth})}
>
<Icon
Expand Down
58 changes: 58 additions & 0 deletions src/components/SidePane/SidePaneOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import Animated, {Easing, Keyframe} from 'react-native-reanimated';
import {PressableWithoutFeedback} from '@components/Pressable';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type SidePaneOverlayProps = {
/** Whether the side pane is displayed inside of RHP */
isInNarrowPaneModal: boolean;

/** Callback fired when pressing the backdrop */
onBackdropPress: () => void;
};

const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0);

function SidePaneOverlay({isInNarrowPaneModal, onBackdropPress}: SidePaneOverlayProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const CustomFadeIn = new Keyframe({
from: {opacity: 0},
to: {
opacity: 0.72,
// @ts-expect-error Types mismatch in reanimated, should to be fixed in 3.17
easing,
},
}).duration(CONST.MODAL.ANIMATION_TIMING.DEFAULT_IN);

const CustomFadeOut = new Keyframe({
from: {opacity: 0.72},
to: {
opacity: 0,
// @ts-expect-error Types mismatch in reanimated, should to be fixed in 3.17
easing,
},
}).duration(CONST.MODAL.ANIMATION_TIMING.DEFAULT_OUT);

return (
<Animated.View
style={styles.sidePaneOverlay(isInNarrowPaneModal)}
entering={isInNarrowPaneModal ? undefined : CustomFadeIn}
exiting={isInNarrowPaneModal ? undefined : CustomFadeOut}
>
<PressableWithoutFeedback
accessible
accessibilityLabel={translate('modal.backdropLabel')}
onPress={onBackdropPress}
style={styles.flex1}
/>
</Animated.View>
);
}

SidePaneOverlay.displayName = 'SidePaneOverlay';

export default SidePaneOverlay;
130 changes: 127 additions & 3 deletions src/components/SidePane/getHelpContent.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,139 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type {ReactNode} from 'react';
import React from 'react';
import {View} from 'react-native';
import Text from '@components/Text';
import type {ThemeStyles} from '@styles/index';

const getHelpContent = (styles: ThemeStyles, route: string) => {
type HelpContent = {
/** The content to display for this route */
content?: (styles: ThemeStyles) => ReactNode;

/** Any children routes that this route has */
children?: Record<string, HelpContent>;

/** Whether this route is an exact match or displays parent content */
isExact?: boolean;
};

const helpContentMap: Record<string, HelpContent> = {
r: {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Chat Reports</Text>
<Text style={styles.textNormal}>... general chat reports help ...</Text>
</>
),
children: {
':reportID': {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Chat Report</Text>
<Text style={styles.textNormal}>... general chat report help ...</Text>
</>
),
},
},
},
search: {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Searching Reports</Text>
<Text style={styles.textNormal}>... general search help ...</Text>
</>
),
},
settings: {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Settings</Text>
<Text style={styles.textNormal}>... general settings help ...</Text>
</>
),
children: {
workspaces: {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Workspaces</Text>
<Text style={styles.textNormal}>... general workspaces help ...</Text>
</>
),
children: {
':policyID': {
content: (styles: ThemeStyles) => (
<>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Workspace Settings</Text>
<Text style={styles.textNormal}>... general workspace settings help ...</Text>
</>
),
},
},
},
},
},
};

type DiagnosticDataProps = {
styles: ThemeStyles;
route: string;
isExactMatch?: boolean;
children?: ReactNode;
};

function DiagnosticData({styles, route, children, isExactMatch}: DiagnosticDataProps) {
const diagnosticTitle = isExactMatch ? 'Help content found for route:' : 'Missing help content for route:';

return (
<View style={styles.ph5}>
<Text style={[styles.textHeadlineH1, styles.mb4]}>Missing page for route</Text>
<Text style={[styles.textHeadlineH1, styles.mb4]}>{diagnosticTitle}</Text>
<Text style={styles.textNormal}>{route}</Text>
{!!children && (
<>
<View style={[styles.sectionDividerLine, styles.mv5]} />
{children}
</>
)}
</View>
);
};
}

function getHelpContent(styles: ThemeStyles, route: string, isProduction: boolean): ReactNode {
const [firstPart, ...routeParts] = route.substring(1).split('/');
let currentNode: HelpContent = helpContentMap[firstPart];
let isExactMatch = true;

for (const part of routeParts) {
if (currentNode?.children?.[part]) {
currentNode = currentNode.children[part];
isExactMatch = true;
} else {
isExactMatch = false;
break;
}
}

if (currentNode?.content) {
if (isProduction) {
return <View style={styles.ph5}>{currentNode.content(styles)}</View>;
}

return (
<DiagnosticData
styles={styles}
route={route}
isExactMatch={isExactMatch}
>
{currentNode.content(styles)}
</DiagnosticData>
);
}

return (
<DiagnosticData
styles={styles}
route={route}
/>
);
}

export default getHelpContent;
Loading