forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2import {View} from 'react-native'
3import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api'
4
5import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
6import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
7import {UserAvatar} from '#/view/com/util/UserAvatar'
8import {atoms as a, useTheme, type 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 enableSquareButtons = useEnableSquareButtons()
71
72 const {outer, avi, text} = React.useMemo(() => {
73 switch (size) {
74 case 'lg': {
75 return {
76 outer: [
77 t.atoms.bg_contrast_25,
78 {
79 gap: 5,
80 paddingHorizontal: 5,
81 paddingVertical: 5,
82 },
83 ],
84 avi: 16,
85 text: [a.text_sm],
86 }
87 }
88 case 'sm':
89 default: {
90 return {
91 outer: [
92 !noBg && t.atoms.bg_contrast_25,
93 {
94 gap: 3,
95 paddingHorizontal: 3,
96 paddingVertical: 3,
97 },
98 ],
99 avi: 12,
100 text: [a.text_xs],
101 }
102 }
103 }
104 }, [t, size, noBg])
105
106 return (
107 <>
108 <Button
109 disabled={disableDetailsDialog}
110 label={desc.name}
111 onPress={e => {
112 e.preventDefault()
113 e.stopPropagation()
114 control.open()
115 }}>
116 {({hovered, pressed}) => (
117 <View
118 style={[
119 a.flex_row,
120 a.align_center,
121 enableSquareButtons ? a.rounded_sm : a.rounded_full,
122 outer,
123 (hovered || pressed) && t.atoms.bg_contrast_50,
124 ]}>
125 {isBlueskyLabel || !isLabeler ? (
126 <desc.icon
127 width={avi}
128 fill={t.atoms.text_contrast_medium.color}
129 />
130 ) : (
131 <UserAvatar avatar={desc.sourceAvi} type="user" size={avi} />
132 )}
133
134 <Text
135 emoji
136 style={[
137 text,
138 a.font_semi_bold,
139 a.leading_tight,
140 t.atoms.text_contrast_medium,
141 {paddingRight: 3},
142 ]}>
143 {desc.name}
144 </Text>
145 </View>
146 )}
147 </Button>
148
149 {!disableDetailsDialog && (
150 <ModerationDetailsDialog control={control} modcause={cause} />
151 )}
152 </>
153 )
154}