-
-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a ScreenStackItem component (#2433)
Currently there is a lot of logic to make `Screens` work properly in native-stack, including: - Making sure screens is enabled and mark it for native-stack - Making sure screens knows that it contains a large title - Rendering a `DebugContainer` for `LogBox` - Making sure the `DebugContainer` contains appropriate logic and wrapper - Rendering a nested stack to be able to show header in modals This diff consolidates this logic to a new component `ScreenStackItem`. This makes it possible to move the above logic to the `react-native-screens` package by moving this component. Moving the logic will make it easier to fix bugs in screens. Extracted from react-navigation/react-navigation@fbc8635 --------- Co-authored-by: Kacper Kafara <[email protected]>
- Loading branch information
Showing
6 changed files
with
227 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule react-navigation
updated
43 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as React from 'react'; | ||
import { Platform, type ViewProps } from 'react-native'; | ||
// @ts-expect-error importing private component | ||
// eslint-disable-next-line import/namespace, import/default, import/no-named-as-default, import/no-named-as-default-member | ||
import AppContainer from 'react-native/Libraries/ReactNative/AppContainer'; | ||
import ScreenContentWrapper from './ScreenContentWrapper'; | ||
import { StackPresentationTypes } from '../types'; | ||
|
||
type ContainerProps = ViewProps & { | ||
stackPresentation: StackPresentationTypes; | ||
children: React.ReactNode; | ||
}; | ||
|
||
/** | ||
* This view must *not* be flattened. | ||
* See https://github.com/software-mansion/react-native-screens/pull/1825 | ||
* for detailed explanation. | ||
*/ | ||
let DebugContainer: React.ComponentType<ContainerProps> = (props) => { | ||
return <ScreenContentWrapper {...props} />; | ||
} | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
// eslint-disable-next-line react/display-name | ||
DebugContainer = (props: ContainerProps) => { | ||
const { stackPresentation, ...rest } = props; | ||
|
||
if ( | ||
Platform.OS === 'ios' && | ||
stackPresentation !== 'push' && | ||
stackPresentation !== 'formSheet' | ||
) { | ||
// This is necessary for LogBox | ||
return ( | ||
<AppContainer> | ||
<ScreenContentWrapper {...rest} /> | ||
</AppContainer> | ||
); | ||
} | ||
|
||
return <ScreenContentWrapper {...rest} />; | ||
}; | ||
|
||
DebugContainer.displayName = 'DebugContainer'; | ||
} | ||
|
||
export default DebugContainer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import * as React from 'react'; | ||
import { type ViewProps } from 'react-native'; | ||
import ScreenContentWrapper from './ScreenContentWrapper'; | ||
|
||
export default function DebugContainer(props: ViewProps) { | ||
return <ScreenContentWrapper {...props} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import * as React from 'react'; | ||
import { | ||
Platform, | ||
type StyleProp, | ||
StyleSheet, | ||
type ViewStyle, | ||
View, | ||
} from 'react-native'; | ||
import warnOnce from 'warn-once'; | ||
|
||
import DebugContainer from './DebugContainer'; | ||
import { ScreenProps, ScreenStackHeaderConfigProps } from '../types'; | ||
import { ScreenStackHeaderConfig } from './ScreenStackHeaderConfig'; | ||
import Screen from './Screen'; | ||
import ScreenStack from './ScreenStack'; | ||
|
||
type Props = Omit< | ||
ScreenProps, | ||
'enabled' | 'isNativeStack' | 'hasLargeHeader' | ||
> & { | ||
headerConfig?: ScreenStackHeaderConfigProps; | ||
contentStyle?: StyleProp<ViewStyle>; | ||
}; | ||
|
||
function ScreenStackItem({ | ||
children, | ||
headerConfig, | ||
activityState, | ||
stackPresentation, | ||
contentStyle, | ||
...rest | ||
}: Props, ref: React.ForwardedRef<View>) { | ||
const isHeaderInModal = | ||
Platform.OS === 'android' | ||
? false | ||
: stackPresentation !== 'push' && headerConfig?.hidden === false; | ||
|
||
const headerHiddenPreviousRef = React.useRef(headerConfig?.hidden); | ||
|
||
React.useEffect(() => { | ||
warnOnce( | ||
Platform.OS !== 'android' && | ||
stackPresentation !== 'push' && | ||
headerHiddenPreviousRef.current !== headerConfig?.hidden, | ||
`Dynamically changing header's visibility in modals will result in remounting the screen and losing all local state.` | ||
); | ||
|
||
headerHiddenPreviousRef.current = headerConfig?.hidden; | ||
}, [headerConfig?.hidden, stackPresentation]); | ||
|
||
const content = ( | ||
<> | ||
<DebugContainer | ||
style={[ | ||
stackPresentation === 'formSheet' | ||
? Platform.OS === 'ios' | ||
? styles.absolute | ||
: null | ||
: styles.container, | ||
contentStyle, | ||
]} | ||
stackPresentation={stackPresentation ?? 'push'} | ||
> | ||
{children} | ||
</DebugContainer> | ||
{/** | ||
* `HeaderConfig` needs to be the direct child of `Screen` without any intermediate `View` | ||
* We don't render it conditionally based on visibility to make it possible to dynamically render a custom `header` | ||
* Otherwise dynamically rendering a custom `header` leaves the native header visible | ||
* | ||
* https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig | ||
* | ||
* HeaderConfig must not be first child of a Screen. | ||
* See https://github.com/software-mansion/react-native-screens/pull/1825 | ||
* for detailed explanation. | ||
*/} | ||
<ScreenStackHeaderConfig {...headerConfig} /> | ||
</> | ||
); | ||
|
||
return ( | ||
<Screen | ||
ref={ref} | ||
enabled | ||
isNativeStack | ||
activityState={activityState} | ||
stackPresentation={stackPresentation} | ||
hasLargeHeader={headerConfig?.largeTitle ?? false} | ||
{...rest} | ||
> | ||
{isHeaderInModal ? ( | ||
<ScreenStack style={styles.container}> | ||
<Screen | ||
enabled | ||
isNativeStack | ||
activityState={activityState} | ||
hasLargeHeader={headerConfig?.largeTitle ?? false} | ||
style={StyleSheet.absoluteFill} | ||
> | ||
{content} | ||
</Screen> | ||
</ScreenStack> | ||
) : ( | ||
content | ||
)} | ||
</Screen> | ||
); | ||
} | ||
|
||
export default React.forwardRef(ScreenStackItem); | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
}, | ||
absolute: { | ||
position: 'absolute', | ||
top: 0, | ||
start: 0, | ||
end: 0, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters