my fork of the bluesky client
at main 179 lines 4.3 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {BSKY_LABELER_DID, ModerationCause} from '@atproto/api' 4import {Trans} from '@lingui/macro' 5 6import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 7import {UserAvatar} from '#/view/com/util/UserAvatar' 8import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 9import {Button} from '#/components/Button' 10import { 11 ModerationDetailsDialog, 12 useModerationDetailsDialogControl, 13} from '#/components/moderation/ModerationDetailsDialog' 14import {Text} from '#/components/Typography' 15 16export type AppModerationCause = 17 | ModerationCause 18 | { 19 type: 'reply-hidden' 20 source: {type: 'user'; did: string} 21 priority: 6 22 downgraded?: boolean 23 } 24 25export type CommonProps = { 26 size?: 'sm' | 'lg' 27} 28 29export function Row({ 30 children, 31 style, 32 size = 'sm', 33}: {children: React.ReactNode | React.ReactNode[]} & CommonProps & 34 ViewStyleProp) { 35 const styles = React.useMemo(() => { 36 switch (size) { 37 case 'lg': 38 return [{gap: 5}] 39 case 'sm': 40 default: 41 return [{gap: 3}] 42 } 43 }, [size]) 44 return ( 45 <View style={[a.flex_row, a.flex_wrap, a.gap_xs, styles, style]}> 46 {children} 47 </View> 48 ) 49} 50 51export type LabelProps = { 52 cause: AppModerationCause 53 disableDetailsDialog?: boolean 54 noBg?: boolean 55} & CommonProps 56 57export function Label({ 58 cause, 59 size = 'sm', 60 disableDetailsDialog, 61 noBg, 62}: LabelProps) { 63 const t = useTheme() 64 const control = useModerationDetailsDialogControl() 65 const desc = useModerationCauseDescription(cause) 66 const isLabeler = Boolean(desc.sourceType && desc.sourceDid) 67 const isBlueskyLabel = 68 desc.sourceType === 'labeler' && desc.sourceDid === BSKY_LABELER_DID 69 70 const {outer, avi, text} = React.useMemo(() => { 71 switch (size) { 72 case 'lg': { 73 return { 74 outer: [ 75 t.atoms.bg_contrast_25, 76 { 77 gap: 5, 78 paddingHorizontal: 5, 79 paddingVertical: 5, 80 }, 81 ], 82 avi: 16, 83 text: [a.text_sm], 84 } 85 } 86 case 'sm': 87 default: { 88 return { 89 outer: [ 90 !noBg && t.atoms.bg_contrast_25, 91 { 92 gap: 3, 93 paddingHorizontal: 3, 94 paddingVertical: 3, 95 }, 96 ], 97 avi: 12, 98 text: [a.text_xs], 99 } 100 } 101 } 102 }, [t, size, noBg]) 103 104 return ( 105 <> 106 <Button 107 disabled={disableDetailsDialog} 108 label={desc.name} 109 onPress={e => { 110 e.preventDefault() 111 e.stopPropagation() 112 control.open() 113 }}> 114 {({hovered, pressed}) => ( 115 <View 116 style={[ 117 a.flex_row, 118 a.align_center, 119 a.rounded_full, 120 outer, 121 (hovered || pressed) && t.atoms.bg_contrast_50, 122 ]}> 123 {isBlueskyLabel || !isLabeler ? ( 124 <desc.icon 125 width={avi} 126 fill={t.atoms.text_contrast_medium.color} 127 /> 128 ) : ( 129 <UserAvatar avatar={desc.sourceAvi} size={avi} /> 130 )} 131 132 <Text 133 emoji 134 style={[ 135 text, 136 a.font_bold, 137 a.leading_tight, 138 t.atoms.text_contrast_medium, 139 {paddingRight: 3}, 140 ]}> 141 {desc.name} 142 </Text> 143 </View> 144 )} 145 </Button> 146 147 {!disableDetailsDialog && ( 148 <ModerationDetailsDialog control={control} modcause={cause} /> 149 )} 150 </> 151 ) 152} 153 154export function FollowsYou({size = 'sm'}: CommonProps) { 155 const t = useTheme() 156 157 const variantStyles = React.useMemo(() => { 158 switch (size) { 159 case 'sm': 160 case 'lg': 161 default: 162 return [ 163 { 164 paddingHorizontal: 6, 165 paddingVertical: 3, 166 borderRadius: 4, 167 }, 168 ] 169 } 170 }, [size]) 171 172 return ( 173 <View style={[variantStyles, a.justify_center, t.atoms.bg_contrast_25]}> 174 <Text style={[a.text_xs, a.leading_tight]}> 175 <Trans>Follows You</Trans> 176 </Text> 177 </View> 178 ) 179}