my fork of the bluesky client
at main 204 lines 4.8 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {AppBskyLabelerDefs} from '@atproto/api' 4import {msg, Plural, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {getLabelingServiceTitle} from '#/lib/moderation' 8import {sanitizeHandle} from '#/lib/strings/handles' 9import {useLabelerInfoQuery} from '#/state/queries/labeler' 10import {UserAvatar} from '#/view/com/util/UserAvatar' 11import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 12import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 13import {Link as InternalLink, LinkProps} from '#/components/Link' 14import {RichText} from '#/components/RichText' 15import {Text} from '#/components/Typography' 16import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' 17 18type LabelingServiceProps = { 19 labeler: AppBskyLabelerDefs.LabelerViewDetailed 20} 21 22export function Outer({ 23 children, 24 style, 25}: React.PropsWithChildren<ViewStyleProp>) { 26 return ( 27 <View 28 style={[ 29 a.flex_row, 30 a.gap_md, 31 a.w_full, 32 a.p_lg, 33 a.pr_md, 34 a.overflow_hidden, 35 style, 36 ]}> 37 {children} 38 </View> 39 ) 40} 41 42export function Avatar({avatar}: {avatar?: string}) { 43 return <UserAvatar type="labeler" size={40} avatar={avatar} /> 44} 45 46export function Title({value}: {value: string}) { 47 return ( 48 <Text emoji style={[a.text_md, a.font_bold, a.leading_tight]}> 49 {value} 50 </Text> 51 ) 52} 53 54export function Description({value, handle}: {value?: string; handle: string}) { 55 const {_} = useLingui() 56 return value ? ( 57 <Text numberOfLines={2}> 58 <RichText value={value} style={[a.leading_snug]} /> 59 </Text> 60 ) : ( 61 <Text emoji style={[a.leading_snug]}> 62 {_(msg`By ${sanitizeHandle(handle, '@')}`)} 63 </Text> 64 ) 65} 66 67export function RegionalNotice() { 68 const t = useTheme() 69 return ( 70 <View 71 style={[ 72 a.flex_row, 73 a.align_center, 74 a.gap_xs, 75 a.pt_2xs, 76 {marginLeft: -2}, 77 ]}> 78 <Flag fill={t.atoms.text_contrast_low.color} size="sm" /> 79 <Text style={[a.italic, a.leading_snug]}> 80 <Trans>Required in your region</Trans> 81 </Text> 82 </View> 83 ) 84} 85 86export function LikeCount({count}: {count: number}) { 87 const t = useTheme() 88 return ( 89 <Text 90 style={[ 91 a.mt_sm, 92 a.text_sm, 93 t.atoms.text_contrast_medium, 94 {fontWeight: '600'}, 95 ]}> 96 <Plural value={count} one="Liked by # user" other="Liked by # users" /> 97 </Text> 98 ) 99} 100 101export function Content({children}: React.PropsWithChildren<{}>) { 102 const t = useTheme() 103 104 return ( 105 <View 106 style={[ 107 a.flex_1, 108 a.flex_row, 109 a.gap_md, 110 a.align_center, 111 a.justify_between, 112 ]}> 113 <View style={[a.gap_2xs, a.flex_1]}>{children}</View> 114 115 <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} /> 116 </View> 117 ) 118} 119 120/** 121 * The canonical view for a labeling service. Use this or compose your own. 122 */ 123export function Default({ 124 labeler, 125 style, 126}: LabelingServiceProps & ViewStyleProp) { 127 return ( 128 <Outer style={style}> 129 <Avatar avatar={labeler.creator.avatar} /> 130 <Content> 131 <Title 132 value={getLabelingServiceTitle({ 133 displayName: labeler.creator.displayName, 134 handle: labeler.creator.handle, 135 })} 136 /> 137 <Description 138 value={labeler.creator.description} 139 handle={labeler.creator.handle} 140 /> 141 {labeler.likeCount ? <LikeCount count={labeler.likeCount} /> : null} 142 </Content> 143 </Outer> 144 ) 145} 146 147export function Link({ 148 children, 149 labeler, 150}: LabelingServiceProps & Pick<LinkProps, 'children'>) { 151 const {_} = useLingui() 152 153 return ( 154 <InternalLink 155 to={{ 156 screen: 'Profile', 157 params: { 158 name: labeler.creator.handle, 159 }, 160 }} 161 label={_( 162 msg`View the labeling service provided by @${labeler.creator.handle}`, 163 )}> 164 {children} 165 </InternalLink> 166 ) 167} 168 169// TODO not finished yet 170export function DefaultSkeleton() { 171 return ( 172 <View> 173 <Text>Loading</Text> 174 </View> 175 ) 176} 177 178export function Loader({ 179 did, 180 loading: LoadingComponent = DefaultSkeleton, 181 error: ErrorComponent, 182 component: Component, 183}: { 184 did: string 185 loading?: React.ComponentType<{}> 186 error?: React.ComponentType<{error: string}> 187 component: React.ComponentType<{ 188 labeler: AppBskyLabelerDefs.LabelerViewDetailed 189 }> 190}) { 191 const {isLoading, data, error} = useLabelerInfoQuery({did}) 192 193 return isLoading ? ( 194 LoadingComponent ? ( 195 <LoadingComponent /> 196 ) : null 197 ) : error || !data ? ( 198 ErrorComponent ? ( 199 <ErrorComponent error={error?.message || 'Unknown error'} /> 200 ) : null 201 ) : ( 202 <Component labeler={data} /> 203 ) 204}