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