Skip to content

Commit

Permalink
Merge pull request #58004 from software-mansion-labs/side-pane/stage-2
Browse files Browse the repository at this point in the history
Side Pane Stage 2: Add baseline content for each major section
  • Loading branch information
francoisl authored Mar 7, 2025
2 parents 3ec26a9 + 9063a67 commit f16edc7
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 158 deletions.
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

0 comments on commit f16edc7

Please sign in to comment.