Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import React from 'react'
2import {View} from 'react-native'
3import {type AtUri} from '@atproto/api'
4import {msg} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6
7import {PressableScale} from '#/lib/custom-animations/PressableScale'
8import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
9// import {makeProfileLink} from '#/lib/routes/links'
10// import {feedUriToHref} from '#/lib/strings/url-helpers'
11// import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
12// import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote'
13// import {UserAvatar} from '#/view/com/util/UserAvatar'
14import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
15import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
16import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
17import {Link as InternalLink, type LinkProps} from '#/components/Link'
18import {Text} from '#/components/Typography'
19
20export function TrendingTopic({
21 topic: raw,
22 size,
23 style,
24 hovered,
25}: {
26 topic: TrendingTopic
27 size?: 'large' | 'small'
28 hovered?: boolean
29} & ViewStyleProp) {
30 const topic = useTopic(raw)
31
32 const isSmall = size === 'small'
33 const hasIcon = topic.type === 'starter-pack' && !isSmall
34 const iconSize = 20
35
36 return (
37 <View
38 style={[
39 a.flex_row,
40 a.align_center,
41 isSmall
42 ? [
43 {
44 paddingVertical: 2,
45 paddingHorizontal: 4,
46 },
47 ]
48 : [a.py_xs, a.px_sm],
49 hasIcon && {gap: 6},
50 style,
51 ]}>
52 {hasIcon && topic.type === 'starter-pack' && (
53 <StarterPackIcon
54 gradient="sky"
55 width={iconSize}
56 style={{marginLeft: -3, marginVertical: -1}}
57 />
58 )}
59
60 {/*
61 <View
62 style={[
63 a.align_center,
64 a.justify_center,
65 a.rounded_full,
66 a.overflow_hidden,
67 {
68 width: iconSize,
69 height: iconSize,
70 },
71 ]}>
72 {topic.type === 'tag' ? (
73 <Hashtag width={iconSize} />
74 ) : topic.type === 'topic' ? (
75 <Quote width={iconSize - 2} />
76 ) : topic.type === 'feed' ? (
77 <UserAvatar
78 type="user"
79 size={aviSize}
80 avatar=""
81 />
82 ) : (
83 <UserAvatar
84 type="user"
85 size={aviSize}
86 avatar=""
87 />
88 )}
89 </View>
90 */}
91
92 <Text
93 style={[
94 a.font_semi_bold,
95 a.leading_tight,
96 isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}],
97 hovered && {textDecorationLine: 'underline'},
98 ]}
99 numberOfLines={1}>
100 {topic.displayName}
101 </Text>
102 </View>
103 )
104}
105
106export function TrendingTopicSkeleton({
107 size = 'large',
108 index = 0,
109}: {
110 size?: 'large' | 'small'
111 index?: number
112}) {
113 const t = useTheme()
114 const isSmall = size === 'small'
115
116 const enableSquareButtons = useEnableSquareButtons()
117
118 return (
119 <View
120 style={[
121 enableSquareButtons ? a.rounded_sm : a.rounded_full,
122 a.border,
123 t.atoms.border_contrast_medium,
124 t.atoms.bg_contrast_25,
125 isSmall
126 ? {
127 width: index % 2 === 0 ? 75 : 90,
128 height: 27,
129 }
130 : {
131 width: index % 2 === 0 ? 90 : 110,
132 height: 36,
133 },
134 ]}
135 />
136 )
137}
138
139export function TrendingTopicLink({
140 topic: raw,
141 children,
142 ...rest
143}: {
144 topic: TrendingTopic
145} & Omit<LinkProps, 'to' | 'label'>) {
146 const topic = useTopic(raw)
147
148 return (
149 <InternalLink
150 label={topic.label}
151 to={topic.url}
152 PressableComponent={native(PressableScale)}
153 {...rest}>
154 {children}
155 </InternalLink>
156 )
157}
158
159type ParsedTrendingTopic =
160 | {
161 type: 'topic' | 'tag' | 'starter-pack' | 'unknown'
162 label: string
163 displayName: string
164 url: string
165 uri: undefined
166 }
167 | {
168 type: 'profile' | 'feed'
169 label: string
170 displayName: string
171 url: string
172 uri: AtUri
173 }
174
175export function useTopic(raw: TrendingTopic): ParsedTrendingTopic {
176 const {_} = useLingui()
177 return React.useMemo(() => {
178 const {topic: displayName, link} = raw
179
180 if (link.startsWith('/search')) {
181 return {
182 type: 'topic',
183 label: _(msg`Browse posts about ${displayName}`),
184 displayName,
185 uri: undefined,
186 url: link,
187 }
188 } else if (link.startsWith('/hashtag')) {
189 return {
190 type: 'tag',
191 label: _(msg`Browse posts tagged with ${displayName}`),
192 displayName,
193 // displayName: displayName.replace(/^#/, ''),
194 uri: undefined,
195 url: link,
196 }
197 } else if (link.startsWith('/starter-pack')) {
198 return {
199 type: 'starter-pack',
200 label: _(msg`Browse starter pack ${displayName}`),
201 displayName,
202 uri: undefined,
203 url: link,
204 }
205 }
206
207 /*
208 if (!link.startsWith('at://')) {
209 // above logic
210 } else {
211 const urip = new AtUri(link)
212 switch (urip.collection) {
213 case 'app.bsky.actor.profile': {
214 return {
215 type: 'profile',
216 label: _(msg`View ${displayName}'s profile`),
217 displayName,
218 uri: urip,
219 url: makeProfileLink({did: urip.host, handle: urip.host}),
220 }
221 }
222 case 'app.bsky.feed.generator': {
223 return {
224 type: 'feed',
225 label: _(msg`Browse the ${displayName} feed`),
226 displayName,
227 uri: urip,
228 url: feedUriToHref(link),
229 }
230 }
231 }
232 }
233 */
234
235 return {
236 type: 'unknown',
237 label: _(msg`Browse topic ${displayName}`),
238 displayName,
239 uri: undefined,
240 url: link,
241 }
242 }, [_, raw])
243}