From 36689e24c21b474692bb7ecd0b901c8afbbe9a20 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Wed, 15 Apr 2020 22:37:03 +0200 Subject: [PATCH] feat: add openByDefault option to drawer --- example/src/Screens/MasterDetail.tsx | 127 ++++++++++++++++++ example/src/index.tsx | 9 +- .../src/navigators/createDrawerNavigator.tsx | 2 + packages/drawer/src/views/Drawer.tsx | 63 +++++++-- packages/drawer/src/views/DrawerView.tsx | 22 +-- packages/routers/src/DrawerRouter.tsx | 32 +++-- 6 files changed, 222 insertions(+), 33 deletions(-) create mode 100644 example/src/Screens/MasterDetail.tsx diff --git a/example/src/Screens/MasterDetail.tsx b/example/src/Screens/MasterDetail.tsx new file mode 100644 index 00000000..1dce5d52 --- /dev/null +++ b/example/src/Screens/MasterDetail.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import { Dimensions, ScaledSize } from 'react-native'; +import { Appbar } from 'react-native-paper'; +import { ParamListBase } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { + createDrawerNavigator, + DrawerNavigationProp, + DrawerContent, +} from '@react-navigation/drawer'; +import Article from '../Shared/Article'; +import Albums from '../Shared/Albums'; +import NewsFeed from '../Shared/NewsFeed'; + +type DrawerParams = { + Article: undefined; + NewsFeed: undefined; + Album: undefined; +}; + +type DrawerNavigation = DrawerNavigationProp; + +const useIsLargeScreen = () => { + const [dimensions, setDimensions] = React.useState(Dimensions.get('window')); + + React.useEffect(() => { + const onDimensionsChange = ({ window }: { window: ScaledSize }) => { + setDimensions(window); + }; + + Dimensions.addEventListener('change', onDimensionsChange); + + return () => Dimensions.removeEventListener('change', onDimensionsChange); + }, []); + + return dimensions.width > 414; +}; + +const Header = ({ + onGoBack, + title, +}: { + onGoBack: () => void; + title: string; +}) => { + const isLargeScreen = useIsLargeScreen(); + + return ( + + {isLargeScreen ? null : } + + + ); +}; + +const ArticleScreen = ({ navigation }: { navigation: DrawerNavigation }) => { + return ( + <> +
navigation.toggleDrawer()} /> +
+ + ); +}; + +const NewsFeedScreen = ({ navigation }: { navigation: DrawerNavigation }) => { + return ( + <> +
navigation.toggleDrawer()} /> + + + ); +}; + +const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => { + return ( + <> +
navigation.toggleDrawer()} /> + + + ); +}; + +const Drawer = createDrawerNavigator(); + +type Props = Partial> & { + navigation: StackNavigationProp; +}; + +export default function DrawerScreen({ navigation, ...rest }: Props) { + navigation.setOptions({ + headerShown: false, + gestureEnabled: false, + }); + + const isLargeScreen = useIsLargeScreen(); + + return ( + ( + <> + + navigation.goBack()} /> + + + + + )} + {...rest} + > + + + + + ); +} diff --git a/example/src/index.tsx b/example/src/index.tsx index df4b53e1..ecdef630 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -43,6 +43,7 @@ import { } from '@react-navigation/stack'; import LinkingPrefixes from './LinkingPrefixes'; +import SettingsItem from './Shared/SettingsItem'; import SimpleStack from './Screens/SimpleStack'; import ModalPresentationStack from './Screens/ModalPresentationStack'; import StackTransparent from './Screens/StackTransparent'; @@ -53,7 +54,7 @@ import MaterialBottomTabs from './Screens/MaterialBottomTabs'; import DynamicTabs from './Screens/DynamicTabs'; import AuthFlow from './Screens/AuthFlow'; import CompatAPI from './Screens/CompatAPI'; -import SettingsItem from './Shared/SettingsItem'; +import MasterDetail from './Screens/MasterDetail'; YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']); @@ -97,6 +98,10 @@ const SCREENS = { title: 'Dynamic Tabs', component: DynamicTabs, }, + MasterDetail: { + title: 'Master Detail', + component: MasterDetail, + }, AuthFlow: { title: 'Auth Flow', component: AuthFlow, @@ -214,7 +219,7 @@ export default function App() { return null; } - const isLargeScreen = dimensions.width > 900; + const isLargeScreen = dimensions.width > 834; return ( diff --git a/packages/drawer/src/navigators/createDrawerNavigator.tsx b/packages/drawer/src/navigators/createDrawerNavigator.tsx index a4bfc5a8..4d081b27 100644 --- a/packages/drawer/src/navigators/createDrawerNavigator.tsx +++ b/packages/drawer/src/navigators/createDrawerNavigator.tsx @@ -21,6 +21,7 @@ type Props = DefaultNavigatorOptions & function DrawerNavigator({ initialRouteName, + openByDefault, backBehavior, children, screenOptions, @@ -33,6 +34,7 @@ function DrawerNavigator({ DrawerNavigationEventMap >(DrawerRouter, { initialRouteName, + openByDefault, backBehavior, children, screenOptions, diff --git a/packages/drawer/src/views/Drawer.tsx b/packages/drawer/src/views/Drawer.tsx index 2a800eeb..6db4e36c 100644 --- a/packages/drawer/src/views/Drawer.tsx +++ b/packages/drawer/src/views/Drawer.tsx @@ -96,6 +96,7 @@ type Props = { renderDrawerContent: Renderer; renderSceneContent: Renderer; gestureHandlerProps?: React.ComponentProps; + dimensions: { width: number; height: number }; }; export default class DrawerView extends React.Component { @@ -196,6 +197,22 @@ export default class DrawerView extends React.Component { } }; + private getDrawerWidth = (): number => { + const { drawerStyle, dimensions } = this.props; + const { width } = StyleSheet.flatten(drawerStyle); + + if (typeof width === 'string' && width.endsWith('%')) { + // Try to calculate width if a percentage is given + const percentage = Number(width.replace(/%$/, '')); + + if (Number.isFinite(percentage)) { + return dimensions.width * (percentage / 100); + } + } + + return typeof width === 'number' ? width : 0; + }; + private clock = new Clock(); private interactionHandle: number | undefined; @@ -207,16 +224,27 @@ export default class DrawerView extends React.Component { private nextIsOpen = new Value(UNSET); private isSwiping = new Value(FALSE); + private initialDrawerWidth = this.getDrawerWidth(); + private gestureState = new Value(GestureState.UNDETERMINED); private touchX = new Value(0); private velocityX = new Value(0); private gestureX = new Value(0); private offsetX = new Value(0); - private position = new Value(0); + private position = new Value( + this.props.open + ? this.initialDrawerWidth * + (this.props.drawerPosition === 'right' + ? DIRECTION_RIGHT + : DIRECTION_LEFT) + : 0 + ); - private containerWidth = new Value(0); - private drawerWidth = new Value(0); - private drawerOpacity = new Value(0); + private containerWidth = new Value(this.props.dimensions.width); + private drawerWidth = new Value(this.initialDrawerWidth); + private drawerOpacity = new Value( + this.initialDrawerWidth || this.props.drawerType === 'permanent' ? 1 : 0 + ); private drawerPosition = new Value( this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT ); @@ -560,9 +588,15 @@ export default class DrawerView extends React.Component { const isOpen = drawerType === 'permanent' ? true : open; const isRight = drawerPosition === 'right'; - const contentTranslateX = drawerType === 'front' ? 0 : this.translateX; + const contentTranslateX = + drawerType === 'front' || drawerType === 'permanent' + ? 0 + : this.translateX; + const drawerTranslateX = - drawerType === 'back' + drawerType === 'permanent' + ? 0 + : drawerType === 'back' ? I18nManager.isRTL ? multiply( sub(this.containerWidth, this.drawerWidth), @@ -612,9 +646,7 @@ export default class DrawerView extends React.Component { @@ -641,6 +673,11 @@ export default class DrawerView extends React.Component { ) } + {drawerType === 'permanent' ? null : ( { onLayout={this.handleDrawerLayout} style={[ styles.container, + { + transform: [{ translateX: drawerTranslateX }], + opacity: this.drawerOpacity, + }, drawerType === 'permanent' ? // Without this, the `left`/`right` values don't get reset isRight @@ -666,10 +707,6 @@ export default class DrawerView extends React.Component { : { left: 0 } : [ styles.nonPermanent, - { - transform: [{ translateX: drawerTranslateX }], - opacity: this.drawerOpacity, - }, isRight ? { right: offset } : { left: offset }, { zIndex: drawerType === 'back' ? -1 : 0 }, ], diff --git a/packages/drawer/src/views/DrawerView.tsx b/packages/drawer/src/views/DrawerView.tsx index 743ccf11..3b49a51e 100644 --- a/packages/drawer/src/views/DrawerView.tsx +++ b/packages/drawer/src/views/DrawerView.tsx @@ -89,11 +89,9 @@ export default function DrawerView({ sceneContainerStyle, }: Props) { const [loaded, setLoaded] = React.useState([state.index]); - const [drawerWidth, setDrawerWidth] = React.useState(() => { - const { height = 0, width = 0 } = Dimensions.get('window'); - - return getDefaultDrawerWidth({ height, width }); - }); + const [dimensions, setDimensions] = React.useState(() => + Dimensions.get('window') + ); const drawerGestureRef = React.useRef(null); @@ -141,13 +139,13 @@ export default function DrawerView({ }, [handleDrawerClose, isDrawerOpen, navigation, state.key]); React.useEffect(() => { - const updateWidth = ({ window }: { window: ScaledSize }) => { - setDrawerWidth(getDefaultDrawerWidth(window)); + const updateDimensions = ({ window }: { window: ScaledSize }) => { + setDimensions(window); }; - Dimensions.addEventListener('change', updateWidth); + Dimensions.addEventListener('change', updateDimensions); - return () => Dimensions.removeEventListener('change', updateWidth); + return () => Dimensions.removeEventListener('change', updateDimensions); }, []); if (!loaded.includes(state.index)) { @@ -225,7 +223,10 @@ export default function DrawerView({ sceneContainerStyle, ]} drawerStyle={[ - { width: drawerWidth, backgroundColor: colors.card }, + { + width: getDefaultDrawerWidth(dimensions), + backgroundColor: colors.card, + }, drawerType === 'permanent' && (drawerPosition === 'left' ? { @@ -247,6 +248,7 @@ export default function DrawerView({ renderSceneContent={renderContent} keyboardDismissMode={keyboardDismissMode} drawerPostion={drawerPosition} + dimensions={dimensions} /> diff --git a/packages/routers/src/DrawerRouter.tsx b/packages/routers/src/DrawerRouter.tsx index e0aefc2b..84f22502 100644 --- a/packages/routers/src/DrawerRouter.tsx +++ b/packages/routers/src/DrawerRouter.tsx @@ -21,7 +21,9 @@ export type DrawerActionType = target?: string; }; -export type DrawerRouterOptions = TabRouterOptions; +export type DrawerRouterOptions = TabRouterOptions & { + openByDefault?: boolean; +}; export type DrawerNavigationState = Omit< TabNavigationState, @@ -95,10 +97,14 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => { }; }; -export default function DrawerRouter( - options: DrawerRouterOptions -): Router { - const router = (TabRouter(options) as unknown) as Router< +export default function DrawerRouter({ + openByDefault, + ...rest +}: DrawerRouterOptions): Router< + DrawerNavigationState, + DrawerActionType | CommonNavigationAction +> { + const router = (TabRouter(rest) as unknown) as Router< DrawerNavigationState, TabActionType | CommonNavigationAction >; @@ -109,7 +115,11 @@ export default function DrawerRouter( type: 'drawer', getInitialState({ routeNames, routeParamList }) { - const state = router.getInitialState({ routeNames, routeParamList }); + let state = router.getInitialState({ routeNames, routeParamList }); + + if (openByDefault) { + state = openDrawer(state); + } return { ...state, @@ -162,8 +172,14 @@ export default function DrawerRouter( return openDrawer(state); case 'GO_BACK': - if (isDrawerOpen(state)) { - return closeDrawer(state); + if (openByDefault) { + if (!isDrawerOpen(state)) { + return openDrawer(state); + } + } else { + if (isDrawerOpen(state)) { + return closeDrawer(state); + } } return router.getStateForAction(state, action, options);