Bluesky app fork with some witchin' additions 馃挮
at main 152 lines 4.9 kB view raw
1import React from 'react' 2import {Dimensions, ScrollView, View} from 'react-native' 3import {msg, Plural} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 7import {type FeedPostSlice} from '#/state/queries/post-feed' 8import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' 9import {atoms as a, useTheme} from '#/alf' 10import {Button, ButtonIcon} from '#/components/Button' 11import { 12 ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft, 13 ChevronRight_Stroke2_Corner0_Rounded as ChevronRight, 14} from '#/components/icons/Chevron' 15import {Text} from '#/components/Typography' 16import {PostFeedItem} from './PostFeedItem' 17 18const CARD_WIDTH = 320 19const CARD_INTERVAL = CARD_WIDTH + a.gap_md.gap 20 21export function PostFeedItemCarousel({items}: {items: FeedPostSlice[]}) { 22 const t = useTheme() 23 const {_} = useLingui() 24 const ref = React.useRef<ScrollView>(null) 25 const [scrollX, setScrollX] = React.useState(0) 26 27 const enableSquareButtons = useEnableSquareButtons() 28 29 const scrollTo = React.useCallback( 30 (item: number) => { 31 setScrollX(item) 32 33 ref.current?.scrollTo({ 34 x: item * CARD_INTERVAL, 35 y: 0, 36 animated: true, 37 }) 38 }, 39 [ref], 40 ) 41 42 const scrollLeft = React.useCallback(() => { 43 const newPos = scrollX > 0 ? scrollX - 1 : items.length - 1 44 scrollTo(newPos) 45 }, [scrollTo, scrollX, items.length]) 46 47 const scrollRight = React.useCallback(() => { 48 const newPos = scrollX < items.length - 1 ? scrollX + 1 : 0 49 scrollTo(newPos) 50 }, [scrollTo, scrollX, items.length]) 51 52 return ( 53 <View 54 style={[a.border_t, t.atoms.border_contrast_low, t.atoms.bg_contrast_25]}> 55 <View 56 style={[ 57 a.py_lg, 58 a.px_md, 59 a.pb_xs, 60 a.flex_row, 61 a.align_center, 62 a.justify_between, 63 ]}> 64 <Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}> 65 {items.length}{' '} 66 <Plural value={items.length} one="reskeet" other="reskeets" /> 67 </Text> 68 <View style={[a.gap_md, a.flex_row, a.align_end]}> 69 <Button 70 label={_(msg`Scroll carousel left`)} 71 size="tiny" 72 variant="ghost" 73 color="secondary" 74 shape={enableSquareButtons ? 'square' : 'round'} 75 onPress={() => scrollLeft()}> 76 <ButtonIcon icon={ChevronLeft} /> 77 </Button> 78 <Button 79 label={_(msg`Scroll carousel right`)} 80 size="tiny" 81 variant="ghost" 82 color="secondary" 83 shape={enableSquareButtons ? 'square' : 'round'} 84 onPress={() => scrollRight()}> 85 <ButtonIcon icon={ChevronRight} /> 86 </Button> 87 </View> 88 </View> 89 <BlockDrawerGesture> 90 <View> 91 <ScrollView 92 horizontal 93 snapToInterval={CARD_INTERVAL} 94 decelerationRate="fast" 95 /* TODO: figure out how to not get this to break on the last item 96 onScroll={e => { 97 setScrollX(Math.floor(e.nativeEvent.contentOffset.x / CARD_INTERVAL)) 98 }} 99*/ 100 ref={ref}> 101 <View 102 style={[ 103 a.px_md, 104 a.pt_sm, 105 a.pb_lg, 106 a.flex_row, 107 a.gap_md, 108 a.align_start, 109 ]}> 110 {items.map(slice => { 111 const item = slice.items[0] 112 113 return ( 114 <View 115 style={[ 116 { 117 maxHeight: Dimensions.get('window').height * 0.65, 118 width: CARD_WIDTH, 119 }, 120 a.rounded_md, 121 a.border, 122 t.atoms.bg, 123 t.atoms.border_contrast_low, 124 a.flex_shrink_0, 125 a.overflow_hidden, 126 ]} 127 key={item._reactKey}> 128 <PostFeedItem 129 post={item.post} 130 record={item.record} 131 reason={slice.reason} 132 feedContext={slice.feedContext} 133 moderation={item.moderation} 134 parentAuthor={item.parentAuthor} 135 isParentBlocked={item.isParentBlocked} 136 isParentNotFound={item.isParentNotFound} 137 hideTopBorder={true} 138 isCarouselItem={true} 139 rootPost={slice.items[0].post} 140 showReplyTo={false} 141 reqId={undefined} 142 /> 143 </View> 144 ) 145 })} 146 </View> 147 </ScrollView> 148 </View> 149 </BlockDrawerGesture> 150 </View> 151 ) 152}