my fork of the bluesky client
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}