Centered layout

This commit is contained in:
Eric Bailey
2024-12-01 16:02:38 -06:00
parent 5f4a0f2881
commit 8cd6763811
+162 -28
View File
@@ -1,12 +1,17 @@
import React, {useContext, useMemo} from 'react'
import {View, ViewStyle} from 'react-native'
import {StyleSheet, View, ViewProps, ViewStyle} from 'react-native'
import {StyleProp} from 'react-native'
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
} from 'react-native-keyboard-controller'
import Animated, {AnimatedScrollViewProps} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {isWeb} from '#/platform/detection'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a} from '#/alf'
import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf'
// Every screen should have a Layout component wrapping it.
// This component provides a default padding for the top of the screen.
@@ -18,19 +23,21 @@ const LayoutContext = React.createContext({
withinScrollView: false,
})
export type ScreenProps = React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
}
/**
* Every screen should have a Layout.Screen component wrapping it.
* This component provides a default padding for the top of the screen
* and height/minHeight
*/
let Screen = ({
export const Screen = React.memo(function Screen({
disableTopPadding = false,
style,
...props
}: React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
}): React.ReactNode => {
}: ScreenProps) {
const {top} = useSafeAreaInsets()
const context = useMemo(
() => ({
@@ -52,13 +59,11 @@ let Screen = ({
/>
</LayoutContext.Provider>
)
}
Screen = React.memo(Screen)
export {Screen}
})
let Header = (
export const Header = React.memo(function Header(
props: React.ComponentProps<typeof ViewHeader>,
): React.ReactNode => {
) {
const {withinScrollView} = useContext(LayoutContext)
if (!withinScrollView) {
return (
@@ -69,18 +74,19 @@ let Header = (
} else {
return <ViewHeader showOnDesktop showBorder {...props} />
}
}
Header = React.memo(Header)
export {Header}
})
let Content = ({
export type ContentProps = AnimatedScrollViewProps & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}
export const Content = React.memo(function Content({
children,
style,
contentContainerStyle,
...props
}: React.ComponentProps<typeof ScrollView> & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}): React.ReactNode => {
}: ContentProps) {
const context = useContext(LayoutContext)
const newContext = useMemo(
() => ({...context, withinScrollView: true}),
@@ -88,13 +94,141 @@ let Content = ({
)
return (
<LayoutContext.Provider value={newContext}>
<ScrollView
style={[a.flex_1, style]}
contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]}
{...props}
/>
<Animated.ScrollView
id="content"
style={[
styles.scrollViewCommonStyles,
style,
styles.scrollViewOverrideStyles,
]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
{...props}>
{isWeb ? (
// @ts-ignore web only -esb
<Center>{children}</Center>
) : (
children
)}
</Animated.ScrollView>
</LayoutContext.Provider>
)
})
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
children: React.ReactNode
contentContainerStyle?: StyleProp<ViewStyle>
}
Content = React.memo(Content)
export {Content}
export const KeyboardAwareContent = React.forwardRef(function LayoutScrollView(
{children, style, contentContainerStyle, ...props}: KeyboardAwareContentProps,
_ref: React.Ref<typeof KeyboardAwareScrollView>,
) {
return (
<KeyboardAwareScrollView
style={[
styles.scrollViewCommonStyles,
style,
styles.scrollViewOverrideStyles,
]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
keyboardShouldPersistTaps="handled"
{...props}>
{isWeb ? <Center>{children}</Center> : children}
</KeyboardAwareScrollView>
)
})
export const Center = React.forwardRef(function LayoutContent(
{children, style, ...props}: ViewProps,
ref: React.Ref<View>,
) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return (
<>
{gtMobile && (
<View
style={[
a.fixed,
a.inset_0,
a.border_l,
a.border_r,
t.atoms.border_contrast_low,
{
width: 602,
left: '50%',
transform: [
{
translateX: '-50%',
},
],
},
]}
/>
)}
<View
ref={ref}
style={[
a.util_screen_outer,
a.w_full,
a.mx_auto,
gtMobile && {
maxWidth: 600,
},
style,
]}
{...props}>
{children}
</View>
</>
)
})
export function Gutter({
children,
top,
bottom,
style,
}: ViewStyleProp & {
children: React.ReactNode
top?: boolean
bottom?: boolean
}) {
const {gtMobile} = useBreakpoints()
return (
<View
style={[
a.px_lg,
top && a.pt_lg,
bottom && a.pb_lg,
gtMobile && [a.px_xl, top && a.pt_xl, bottom && a.pb_xl],
style,
]}>
{children}
</View>
)
}
const styles = StyleSheet.create({
scrollViewCommonStyles: {
width: '100%',
},
scrollViewContentContainer: {
paddingBottom: 100,
},
/**
* Applied last to ensure they override any locally-defined styles. Use sparingly.
*/
scrollViewOverrideStyles: {
/*
* Ensures fixed items within `ScrollView` are fixed relative to window.
*/
transform: 'unset',
},
})