[Chat] Split view layout for web (#10258)

This commit is contained in:
Samuel Newman
2026-05-15 20:46:53 +01:00
committed by GitHub
parent 5b7bc56a12
commit 0eb382da03
35 changed files with 849 additions and 588 deletions
+14 -6
View File
@@ -7,6 +7,7 @@ import {useNavigation} from '@react-navigation/native'
import {HITSLOP_30} from '#/lib/constants'
import {type NavigationProp} from '#/lib/routes/types'
import {useSetDrawerOpen} from '#/state/shell'
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
import {
atoms as a,
platform,
@@ -46,6 +47,7 @@ export function Outer({
const {gtMobile} = useBreakpoints()
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
const {centerColumnOffset} = useLayoutBreakpoints()
const {isWithinSplitView} = useIsWithinSplitView()
return (
<View
@@ -64,12 +66,13 @@ export function Outer({
}),
t.atoms.border_contrast_low,
gtMobile && [a.mx_auto, {maxWidth: 600}],
!isWithinOffsetView && {
transform: [
{translateX: centerColumnOffset ? CENTER_COLUMN_OFFSET : 0},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
!isWithinOffsetView &&
!isWithinSplitView && {
transform: [
{translateX: centerColumnOffset ? CENTER_COLUMN_OFFSET : 0},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
]}>
{children}
</View>
@@ -108,6 +111,7 @@ export function Slot({children}: {children?: React.ReactNode}) {
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const {isWithinRightPanel} = useIsWithinSplitView()
const onPressBack = useCallback(
(evt: GestureResponderEvent) => {
@@ -122,6 +126,10 @@ export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
[onPress, navigation],
)
if (isWithinRightPanel) {
return null
}
return (
<Slot>
<Button
+37 -64
View File
@@ -1,15 +1,10 @@
import {forwardRef, memo, useContext, useMemo} from 'react'
import {
type StyleProp,
StyleSheet,
View,
type ViewProps,
type ViewStyle,
} from 'react-native'
import {
KeyboardAwareScrollView,
type KeyboardAwareScrollViewProps,
} from 'react-native-keyboard-controller'
import Animated, {
type AnimatedScrollViewProps,
useAnimatedProps,
@@ -18,6 +13,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useEnableMinimalShellModeForScreen} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout'
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
import {
atoms as a,
useBreakpoints,
@@ -49,14 +45,20 @@ export const Screen = memo(function Screen({
...props
}: ScreenProps) {
const {top} = useSafeAreaInsets()
const {isWithinSplitView} = useIsWithinSplitView()
useEnableMinimalShellModeForScreen({enabled: minimalShell})
return (
<>
{IS_WEB && <WebCenterBorders />}
{IS_WEB && !isWithinSplitView && <WebCenterBorders />}
<View
style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]}
style={[
a.util_screen_outer,
{paddingTop: noInsetTop ? 0 : top},
isWithinSplitView && {maxHeight: '100%'},
style,
]}
{...props}
/>
</>
@@ -85,6 +87,7 @@ export const Content = memo(
) {
const t = useTheme()
const {footerHeight} = useShellLayout()
const {isWithinSplitView} = useIsWithinSplitView()
const animatedProps = useAnimatedProps(() => {
return {
scrollIndicatorInsets: {
@@ -103,11 +106,18 @@ export const Content = memo(
indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'}
// sets the scroll inset to the height of the footer
animatedProps={animatedProps}
style={[scrollViewStyles.common, style]}
contentContainerStyle={[
scrollViewStyles.contentContainer,
contentContainerStyle,
style={[
a.w_full,
isWithinSplitView &&
web({
flex: 1,
overflowY: 'scroll',
scrollbarWidth: 'thin',
scrollbarColor: `${t.palette.contrast_100} transparent`,
}),
style,
]}
contentContainerStyle={[contentContainerStyle]}
{...props}>
{IS_WEB ? (
<Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}>
@@ -122,45 +132,6 @@ export const Content = memo(
}),
)
const scrollViewStyles = StyleSheet.create({
common: {
width: '100%',
},
contentContainer: {
paddingBottom: 100,
},
})
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
children: React.ReactNode
contentContainerStyle?: StyleProp<ViewStyle>
}
/**
* Default scroll view for simple pages.
*
* BE SURE TO TEST THIS WHEN USING, it's untested as of writing this comment.
*/
export const KeyboardAwareContent = memo(function LayoutKeyboardAwareContent({
children,
style,
contentContainerStyle,
...props
}: KeyboardAwareContentProps) {
return (
<KeyboardAwareScrollView
style={[scrollViewStyles.common, style]}
contentContainerStyle={[
scrollViewStyles.contentContainer,
contentContainerStyle,
]}
keyboardShouldPersistTaps="handled"
{...props}>
{IS_WEB ? <Center>{children}</Center> : children}
</KeyboardAwareScrollView>
)
})
/**
* Utility component to center content within the screen
*/
@@ -174,28 +145,30 @@ export const Center = memo(function LayoutCenter({
const {gtMobile} = useBreakpoints()
const {centerColumnOffset} = useLayoutBreakpoints()
const {isWithinDialog} = useDialogContext()
const {isWithinSplitView} = useIsWithinSplitView()
const ctx = useMemo(() => ({isWithinOffsetView: true}), [])
return (
<View
style={[
a.w_full,
a.mx_auto,
!isWithinSplitView && a.mx_auto,
gtMobile && {
maxWidth: 600,
},
!isWithinOffsetView && {
transform: [
{
translateX:
centerColumnOffset &&
!ignoreTabletLayoutOffset &&
!isWithinDialog
? CENTER_COLUMN_OFFSET
: 0,
},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
!isWithinOffsetView &&
!isWithinSplitView && {
transform: [
{
translateX:
centerColumnOffset &&
!ignoreTabletLayoutOffset &&
!isWithinDialog
? CENTER_COLUMN_OFFSET
: 0,
},
{translateX: web(SCROLLBAR_OFFSET) ?? 0},
],
},
style,
]}
{...props}>