mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {useRef} from 'react'
2import {
3 Share,
4 StyleProp,
5 StyleSheet,
6 TouchableOpacity,
7 TouchableWithoutFeedback,
8 View,
9 ViewStyle,
10} from 'react-native'
11import {IconProp} from '@fortawesome/fontawesome-svg-core'
12import RootSiblings from 'react-native-root-siblings'
13import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
14import {Text} from '../text/Text'
15import {Button, ButtonType} from './Button'
16import {colors} from '../../../lib/styles'
17import {toShareUrl} from '../../../../lib/strings'
18import {useStores} from '../../../../state'
19import {ReportPostModal, ConfirmModal} from '../../../../state/models/shell-ui'
20import {TABS_ENABLED} from '../../../../build-flags'
21
22const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
23
24export interface DropdownItem {
25 icon?: IconProp
26 label: string
27 onPress: () => void
28}
29
30export type DropdownButtonType = ButtonType | 'bare'
31
32export function DropdownButton({
33 type = 'bare',
34 style,
35 items,
36 label,
37 menuWidth,
38 children,
39}: {
40 type: DropdownButtonType
41 style?: StyleProp<ViewStyle>
42 items: DropdownItem[]
43 label?: string
44 menuWidth?: number
45 children?: React.ReactNode
46}) {
47 const ref = useRef<TouchableOpacity>(null)
48
49 const onPress = () => {
50 ref.current?.measure(
51 (
52 _x: number,
53 _y: number,
54 width: number,
55 height: number,
56 pageX: number,
57 pageY: number,
58 ) => {
59 if (!menuWidth) {
60 menuWidth = 200
61 }
62 createDropdownMenu(
63 pageX + width - menuWidth,
64 pageY + height,
65 menuWidth,
66 items,
67 )
68 },
69 )
70 }
71
72 if (type === 'bare') {
73 return (
74 <TouchableOpacity
75 style={style}
76 onPress={onPress}
77 hitSlop={HITSLOP}
78 // Fix an issue where specific references cause runtime error in jest environment
79 ref={process.env.JEST_WORKER_ID != null ? null : ref}>
80 {children}
81 </TouchableOpacity>
82 )
83 }
84 return (
85 <View ref={ref}>
86 <Button onPress={onPress} style={style} label={label}>
87 {children}
88 </Button>
89 </View>
90 )
91}
92
93export function PostDropdownBtn({
94 style,
95 children,
96 itemHref,
97 isAuthor,
98 onCopyPostText,
99 onDeletePost,
100}: {
101 style?: StyleProp<ViewStyle>
102 children?: React.ReactNode
103 itemHref: string
104 itemTitle: string
105 isAuthor: boolean
106 onCopyPostText: () => void
107 onDeletePost: () => void
108}) {
109 const store = useStores()
110
111 const dropdownItems: DropdownItem[] = [
112 TABS_ENABLED
113 ? {
114 icon: ['far', 'clone'],
115 label: 'Open in new tab',
116 onPress() {
117 store.nav.newTab(itemHref)
118 },
119 }
120 : undefined,
121 {
122 icon: ['far', 'paste'],
123 label: 'Copy post text',
124 onPress() {
125 onCopyPostText()
126 },
127 },
128 {
129 icon: 'share',
130 label: 'Share...',
131 onPress() {
132 Share.share({url: toShareUrl(itemHref)})
133 },
134 },
135 {
136 icon: 'circle-exclamation',
137 label: 'Report post',
138 onPress() {
139 store.shell.openModal(new ReportPostModal(itemHref))
140 },
141 },
142 isAuthor
143 ? {
144 icon: ['far', 'trash-can'],
145 label: 'Delete post',
146 onPress() {
147 store.shell.openModal(
148 new ConfirmModal(
149 'Delete this post?',
150 'Are you sure? This can not be undone.',
151 onDeletePost,
152 ),
153 )
154 },
155 }
156 : undefined,
157 ].filter(Boolean) as DropdownItem[]
158
159 return (
160 <DropdownButton style={style} items={dropdownItems} menuWidth={200}>
161 {children}
162 </DropdownButton>
163 )
164}
165
166function createDropdownMenu(
167 x: number,
168 y: number,
169 width: number,
170 items: DropdownItem[],
171): RootSiblings {
172 const onPressItem = (index: number) => {
173 sibling.destroy()
174 items[index].onPress()
175 }
176 const onOuterPress = () => sibling.destroy()
177 const sibling = new RootSiblings(
178 (
179 <>
180 <TouchableWithoutFeedback onPress={onOuterPress}>
181 <View style={styles.bg} />
182 </TouchableWithoutFeedback>
183 <View style={[styles.menu, {left: x, top: y, width}]}>
184 {items.map((item, index) => (
185 <TouchableOpacity
186 key={index}
187 style={[styles.menuItem]}
188 onPress={() => onPressItem(index)}>
189 {item.icon && (
190 <FontAwesomeIcon style={styles.icon} icon={item.icon} />
191 )}
192 <Text style={styles.label}>{item.label}</Text>
193 </TouchableOpacity>
194 ))}
195 </View>
196 </>
197 ),
198 )
199 return sibling
200}
201
202const styles = StyleSheet.create({
203 bg: {
204 position: 'absolute',
205 top: 0,
206 right: 0,
207 bottom: 0,
208 left: 0,
209 backgroundColor: '#000',
210 opacity: 0.1,
211 },
212 menu: {
213 position: 'absolute',
214 backgroundColor: '#fff',
215 borderRadius: 14,
216 opacity: 1,
217 paddingVertical: 6,
218 },
219 menuItem: {
220 flexDirection: 'row',
221 alignItems: 'center',
222 paddingVertical: 10,
223 paddingLeft: 15,
224 paddingRight: 40,
225 },
226 menuItemBorder: {
227 borderTopWidth: 1,
228 borderTopColor: colors.gray1,
229 marginTop: 4,
230 paddingTop: 12,
231 },
232 icon: {
233 marginLeft: 6,
234 marginRight: 8,
235 },
236 label: {
237 fontSize: 18,
238 },
239})