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