Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 236 lines 5.9 kB view raw
1import {forwardRef, memo, useContext, useMemo} from 'react' 2import { 3 type StyleProp, 4 StyleSheet, 5 View, 6 type ViewProps, 7 type ViewStyle, 8} from 'react-native' 9import { 10 KeyboardAwareScrollView, 11 type KeyboardAwareScrollViewProps, 12} from 'react-native-keyboard-controller' 13import Animated, { 14 type AnimatedScrollViewProps, 15 useAnimatedProps, 16} from 'react-native-reanimated' 17import {useSafeAreaInsets} from 'react-native-safe-area-context' 18 19import {useEnableMinimalShellModeForScreen} from '#/state/shell' 20import {useShellLayout} from '#/state/shell/shell-layout' 21import { 22 atoms as a, 23 useBreakpoints, 24 useLayoutBreakpoints, 25 useTheme, 26 web, 27} from '#/alf' 28import {useDialogContext} from '#/components/Dialog' 29import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const' 30import {ScrollbarOffsetContext} from '#/components/Layout/context' 31import {IS_WEB} from '#/env' 32 33export * from '#/components/Layout/const' 34export * as Header from '#/components/Layout/Header' 35 36export type ScreenProps = React.ComponentProps<typeof View> & { 37 style?: StyleProp<ViewStyle> 38 noInsetTop?: boolean 39 minimalShell?: boolean 40} 41 42/** 43 * Outermost component of every screen 44 */ 45export const Screen = memo(function Screen({ 46 style, 47 noInsetTop, 48 minimalShell = false, 49 ...props 50}: ScreenProps) { 51 const {top} = useSafeAreaInsets() 52 53 useEnableMinimalShellModeForScreen({enabled: minimalShell}) 54 55 return ( 56 <> 57 {IS_WEB && <WebCenterBorders />} 58 <View 59 style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]} 60 {...props} 61 /> 62 </> 63 ) 64}) 65 66export type ContentProps = AnimatedScrollViewProps & { 67 style?: StyleProp<ViewStyle> 68 contentContainerStyle?: StyleProp<ViewStyle> 69 ignoreTabletLayoutOffset?: boolean 70} 71 72/** 73 * Default scroll view for simple pages 74 */ 75export const Content = memo( 76 forwardRef<Animated.ScrollView, ContentProps>(function Content( 77 { 78 children, 79 style, 80 contentContainerStyle, 81 ignoreTabletLayoutOffset, 82 ...props 83 }, 84 ref, 85 ) { 86 const t = useTheme() 87 const {footerHeight} = useShellLayout() 88 const animatedProps = useAnimatedProps(() => { 89 return { 90 scrollIndicatorInsets: { 91 bottom: footerHeight.get(), 92 top: 0, 93 right: 1, 94 }, 95 } satisfies AnimatedScrollViewProps 96 }) 97 98 return ( 99 <Animated.ScrollView 100 ref={ref} 101 id="content" 102 automaticallyAdjustsScrollIndicatorInsets={false} 103 indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'} 104 // sets the scroll inset to the height of the footer 105 animatedProps={animatedProps} 106 style={[scrollViewStyles.common, style]} 107 contentContainerStyle={[ 108 scrollViewStyles.contentContainer, 109 contentContainerStyle, 110 ]} 111 {...props}> 112 {IS_WEB ? ( 113 <Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}> 114 {/* @ts-expect-error web only -esb */} 115 {children} 116 </Center> 117 ) : ( 118 children 119 )} 120 </Animated.ScrollView> 121 ) 122 }), 123) 124 125const scrollViewStyles = StyleSheet.create({ 126 common: { 127 width: '100%', 128 }, 129 contentContainer: { 130 paddingBottom: 100, 131 }, 132}) 133 134export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & { 135 children: React.ReactNode 136 contentContainerStyle?: StyleProp<ViewStyle> 137} 138 139/** 140 * Default scroll view for simple pages. 141 * 142 * BE SURE TO TEST THIS WHEN USING, it's untested as of writing this comment. 143 */ 144export const KeyboardAwareContent = memo(function LayoutKeyboardAwareContent({ 145 children, 146 style, 147 contentContainerStyle, 148 ...props 149}: KeyboardAwareContentProps) { 150 return ( 151 <KeyboardAwareScrollView 152 style={[scrollViewStyles.common, style]} 153 contentContainerStyle={[ 154 scrollViewStyles.contentContainer, 155 contentContainerStyle, 156 ]} 157 keyboardShouldPersistTaps="handled" 158 {...props}> 159 {IS_WEB ? <Center>{children}</Center> : children} 160 </KeyboardAwareScrollView> 161 ) 162}) 163 164/** 165 * Utility component to center content within the screen 166 */ 167export const Center = memo(function LayoutCenter({ 168 children, 169 style, 170 ignoreTabletLayoutOffset, 171 ...props 172}: ViewProps & {ignoreTabletLayoutOffset?: boolean}) { 173 const {isWithinOffsetView} = useContext(ScrollbarOffsetContext) 174 const {gtMobile} = useBreakpoints() 175 const {centerColumnOffset} = useLayoutBreakpoints() 176 const {isWithinDialog} = useDialogContext() 177 const ctx = useMemo(() => ({isWithinOffsetView: true}), []) 178 return ( 179 <View 180 style={[ 181 a.w_full, 182 a.mx_auto, 183 gtMobile && { 184 maxWidth: 600, 185 }, 186 !isWithinOffsetView && { 187 transform: [ 188 { 189 translateX: 190 centerColumnOffset && 191 !ignoreTabletLayoutOffset && 192 !isWithinDialog 193 ? CENTER_COLUMN_OFFSET 194 : 0, 195 }, 196 {translateX: web(SCROLLBAR_OFFSET) ?? 0}, 197 ], 198 }, 199 style, 200 ]} 201 {...props}> 202 <ScrollbarOffsetContext.Provider value={ctx}> 203 {children} 204 </ScrollbarOffsetContext.Provider> 205 </View> 206 ) 207}) 208 209/** 210 * Only used within `Layout.Screen`, not for reuse 211 */ 212const WebCenterBorders = memo(function LayoutWebCenterBorders() { 213 const t = useTheme() 214 const {gtMobile} = useBreakpoints() 215 const {centerColumnOffset} = useLayoutBreakpoints() 216 return gtMobile ? ( 217 <View 218 style={[ 219 a.fixed, 220 a.inset_0, 221 a.border_l, 222 a.border_r, 223 t.atoms.border_contrast_low, 224 web({ 225 width: 602, 226 left: '50%', 227 transform: [ 228 {translateX: '-50%'}, 229 {translateX: centerColumnOffset ? CENTER_COLUMN_OFFSET : 0}, 230 ...a.scrollbar_offset.transform, 231 ], 232 }), 233 ]} 234 /> 235 ) : null 236})