Bluesky app fork with some witchin' additions 馃挮
at readme-update 195 lines 5.5 kB view raw
1/** 2 * In the Web build, we center all content so that it mirrors the 3 * mobile experience (a single narrow column). We then place a UI 4 * shell around the content if you're in desktop. 5 * 6 * Because scrolling is handled by components deep in the hierarchy, 7 * we can't just wrap the top-level element with a max width. The 8 * centering has to be done at the ScrollView. 9 * 10 * These components wrap the RN ScrollView-based components to provide 11 * consistent layout. It also provides <CenteredView> for views that 12 * need to match layout but which aren't scrolled. 13 */ 14 15import React from 'react' 16import { 17 type FlatList, 18 type FlatListProps, 19 type ScrollViewProps, 20 StyleSheet, 21 View, 22 type ViewProps, 23} from 'react-native' 24import Animated from 'react-native-reanimated' 25 26import {usePalette} from '#/lib/hooks/usePalette' 27import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 28import {addStyle} from '#/lib/styles' 29import {useLayoutBreakpoints} from '#/alf' 30import {useDialogContext} from '#/components/Dialog' 31import {CENTER_COLUMN_OFFSET} from '#/components/Layout' 32 33interface AddedProps { 34 desktopFixedHeight?: boolean | number 35} 36 37/** 38 * @deprecated use `Layout` components 39 */ 40export const CenteredView = React.forwardRef(function CenteredView( 41 { 42 style, 43 topBorder, 44 ...props 45 }: React.PropsWithChildren< 46 ViewProps & {sideBorders?: boolean; topBorder?: boolean} 47 >, 48 ref: React.Ref<View>, 49) { 50 const pal = usePalette('default') 51 const {isMobile} = useWebMediaQueries() 52 const {centerColumnOffset} = useLayoutBreakpoints() 53 const {isWithinDialog} = useDialogContext() 54 if (!isMobile) { 55 style = addStyle(style, styles.container) 56 } 57 if (centerColumnOffset && !isWithinDialog) { 58 style = addStyle(style, styles.containerOffset) 59 } 60 if (topBorder) { 61 style = addStyle(style, { 62 borderTopWidth: 1, 63 }) 64 style = addStyle(style, pal.border) 65 } 66 return <View ref={ref} style={style} {...props} /> 67}) 68 69export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>( 70 { 71 contentContainerStyle, 72 style, 73 contentOffset, 74 desktopFixedHeight, 75 ...props 76 }: React.PropsWithChildren< 77 Omit<FlatListProps<ItemT>, 'CellRendererComponent'> & AddedProps 78 >, 79 ref: React.Ref<FlatList<ItemT>>, 80) { 81 const {isMobile} = useWebMediaQueries() 82 const {centerColumnOffset} = useLayoutBreakpoints() 83 const {isWithinDialog} = useDialogContext() 84 if (!isMobile) { 85 contentContainerStyle = addStyle( 86 contentContainerStyle, 87 styles.containerScroll, 88 ) 89 } 90 if (centerColumnOffset && !isWithinDialog) { 91 style = addStyle(style, styles.containerOffset) 92 } 93 if (contentOffset && contentOffset?.y !== 0) { 94 // NOTE 95 // we use paddingTop & contentOffset to space around the floating header 96 // but reactnative web puts the paddingTop on the wrong element (style instead of the contentContainer) 97 // so we manually correct it here 98 // -prf 99 style = addStyle(style, { 100 paddingTop: 0, 101 }) 102 contentContainerStyle = addStyle(contentContainerStyle, { 103 paddingTop: Math.abs(contentOffset.y), 104 }) 105 } 106 if (desktopFixedHeight) { 107 if (typeof desktopFixedHeight === 'number') { 108 // @ts-expect-error Web only -prf 109 style = addStyle(style, { 110 height: `calc(100vh - ${desktopFixedHeight}px)`, 111 }) 112 } else { 113 style = addStyle(style, styles.fixedHeight) 114 } 115 if (!isMobile) { 116 // NOTE 117 // react native web produces *three* wrapping divs 118 // the first two use the `style` prop and the innermost uses the 119 // `contentContainerStyle`. Unfortunately the stable-gutter style 120 // needs to be applied to only the "middle" of these. To hack 121 // around this, we set data-stable-gutters which can then be 122 // styled in our external CSS. 123 // -prf 124 // @ts-expect-error web only -prf 125 props.dataSet = props.dataSet || {} 126 // @ts-expect-error web only -prf 127 props.dataSet.stableGutters = '1' 128 } 129 } 130 return ( 131 <Animated.FlatList 132 ref={ref} 133 contentContainerStyle={[styles.contentContainer, contentContainerStyle]} 134 style={style} 135 contentOffset={contentOffset} 136 {...props} 137 /> 138 ) 139}) 140 141/** 142 * @deprecated use `Layout` components 143 */ 144export const ScrollView = React.forwardRef(function ScrollViewImpl( 145 {contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>, 146 ref: React.Ref<Animated.ScrollView>, 147) { 148 const {isMobile} = useWebMediaQueries() 149 const {centerColumnOffset} = useLayoutBreakpoints() 150 if (!isMobile) { 151 contentContainerStyle = addStyle( 152 contentContainerStyle, 153 styles.containerScroll, 154 ) 155 } 156 if (centerColumnOffset) { 157 contentContainerStyle = addStyle( 158 contentContainerStyle, 159 styles.containerOffset, 160 ) 161 } 162 return ( 163 <Animated.ScrollView 164 contentContainerStyle={[styles.contentContainer, contentContainerStyle]} 165 ref={ref} 166 {...props} 167 /> 168 ) 169}) 170 171const styles = StyleSheet.create({ 172 contentContainer: { 173 // @ts-expect-error web only 174 minHeight: '100vh', 175 }, 176 container: { 177 width: '100%', 178 maxWidth: 600, 179 marginLeft: 'auto', 180 marginRight: 'auto', 181 }, 182 containerOffset: { 183 transform: [{translateX: CENTER_COLUMN_OFFSET}], 184 }, 185 containerScroll: { 186 width: '100%', 187 maxWidth: 600, 188 marginLeft: 'auto', 189 marginRight: 'auto', 190 }, 191 fixedHeight: { 192 // @ts-expect-error web only 193 height: '100vh', 194 }, 195})