Bluesky app fork with some witchin' additions 💫

Add subtle web hover to interactive rows (#5989)

* Add subtle web hover to interactive rows

* Adjust numbers

* Ignore touch devices

authored by danabra.mov and committed by

GitHub ba802eb0 c8f264b7

+152 -43
+3
src/components/SubtleWebHover.tsx
··· 1 + export function SubtleWebHover({}: {hover: boolean}) { 2 + return null 3 + }
+48
src/components/SubtleWebHover.web.tsx
··· 1 + import React from 'react' 2 + import {StyleSheet, View} from 'react-native' 3 + 4 + import {isTouchDevice} from '#/lib/browser' 5 + import {useTheme} from '#/alf' 6 + 7 + export function SubtleWebHover({hover}: {hover: boolean}) { 8 + const t = useTheme() 9 + if (isTouchDevice) { 10 + return null 11 + } 12 + let opacity: number 13 + switch (t.name) { 14 + case 'dark': 15 + opacity = 0.4 16 + break 17 + case 'dim': 18 + opacity = 0.45 19 + break 20 + case 'light': 21 + opacity = 0.5 22 + break 23 + } 24 + return ( 25 + <View 26 + style={[ 27 + t.atoms.bg_contrast_25, 28 + styles.container, 29 + { 30 + opacity: hover ? opacity : 0, 31 + }, 32 + ]} 33 + /> 34 + ) 35 + } 36 + 37 + const styles = StyleSheet.create({ 38 + container: { 39 + position: 'absolute', 40 + left: 0, 41 + right: 0, 42 + bottom: 0, 43 + top: 0, 44 + pointerEvents: 'none', 45 + // @ts-ignore web only 46 + transition: '0.15s ease-in-out opacity', 47 + }, 48 + })
+11 -1
src/view/com/notifications/FeedItem.tsx
··· 51 51 import * as MediaPreview from '#/components/MediaPreview' 52 52 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 53 53 import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 54 + import {SubtleWebHover} from '#/components/SubtleWebHover' 54 55 import {FeedSourceCard} from '../feeds/FeedSourceCard' 55 56 import {Post} from '../post/Post' 56 57 import {Link, TextLink} from '../util/Link' ··· 128 129 })) || []), 129 130 ] 130 131 }, [item, moderationOpts]) 132 + 133 + const [hover, setHover] = React.useState(false) 131 134 132 135 if (item.subjectUri && !item.subject && item.type !== 'feedgen-like') { 133 136 // don't render anything if the target post was deleted or unfindable ··· 285 288 onToggleAuthorsExpanded() 286 289 } 287 290 }} 288 - onBeforePress={onBeforePress}> 291 + onBeforePress={onBeforePress} 292 + onPointerEnter={() => { 293 + setHover(true) 294 + }} 295 + onPointerLeave={() => { 296 + setHover(false) 297 + }}> 298 + <SubtleWebHover hover={hover} /> 289 299 <View style={[styles.layoutIcon, a.pr_sm]}> 290 300 {/* TODO: Prevent conditional rendering and move toward composable 291 301 notifications for clearer accessibility labeling */}
+16 -1
src/view/com/post-thread/PostThreadItem.tsx
··· 31 31 import {atoms as a, useTheme} from '#/alf' 32 32 import {AppModerationCause} from '#/components/Pills' 33 33 import {RichText} from '#/components/RichText' 34 + import {SubtleWebHover} from '#/components/SubtleWebHover' 34 35 import {Text as NewText} from '#/components/Typography' 35 36 import {ContentHider} from '../../../components/moderation/ContentHider' 36 37 import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' ··· 649 650 hideTopBorder?: boolean 650 651 }>) { 651 652 const t = useTheme() 653 + const [hover, setHover] = React.useState(false) 652 654 if (treeView && depth > 0) { 653 655 return ( 654 656 <View ··· 661 663 flexDirection: 'row', 662 664 borderTopWidth: depth === 1 ? a.border_t.borderTopWidth : 0, 663 665 }, 664 - ]}> 666 + ]} 667 + onPointerEnter={() => { 668 + setHover(true) 669 + }} 670 + onPointerLeave={() => { 671 + setHover(false) 672 + }}> 665 673 {Array.from(Array(depth - 1)).map((_, n: number) => ( 666 674 <View 667 675 key={`${post.uri}-padding-${n}`} ··· 681 689 } 682 690 return ( 683 691 <View 692 + onPointerEnter={() => { 693 + setHover(true) 694 + }} 695 + onPointerLeave={() => { 696 + setHover(false) 697 + }} 684 698 style={[ 685 699 a.border_t, 686 700 a.px_sm, ··· 689 703 hideTopBorder && styles.noTopBorder, 690 704 styles.cursor, 691 705 ]}> 706 + <SubtleWebHover hover={hover} /> 692 707 {children} 693 708 </View> 694 709 )
+10 -1
src/view/com/post/Post.tsx
··· 27 27 import {atoms as a} from '#/alf' 28 28 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 29 29 import {RichText} from '#/components/RichText' 30 + import {SubtleWebHover} from '#/components/SubtleWebHover' 30 31 import {ContentHider} from '../../../components/moderation/ContentHider' 31 32 import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' 32 33 import {PostAlerts} from '../../../components/moderation/PostAlerts' ··· 148 149 const {currentAccount} = useSession() 149 150 const isMe = replyAuthorDid === currentAccount?.did 150 151 152 + const [hover, setHover] = React.useState(false) 151 153 return ( 152 154 <Link 153 155 href={itemHref} ··· 157 159 !hideTopBorder && {borderTopWidth: StyleSheet.hairlineWidth}, 158 160 style, 159 161 ]} 160 - onBeforePress={onBeforePress}> 162 + onBeforePress={onBeforePress} 163 + onPointerEnter={() => { 164 + setHover(true) 165 + }} 166 + onPointerLeave={() => { 167 + setHover(false) 168 + }}> 169 + <SubtleWebHover hover={hover} /> 161 170 {showReplyLine && <View style={styles.replyLine} />} 162 171 <View style={styles.layout}> 163 172 <View style={styles.layoutAvi}>
+10 -1
src/view/com/posts/FeedItem.tsx
··· 46 46 import {AppModerationCause} from '#/components/Pills' 47 47 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 48 48 import {RichText} from '#/components/RichText' 49 + import {SubtleWebHover} from '#/components/SubtleWebHover' 49 50 import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link' 50 51 import {AviFollowButton} from './AviFollowButton' 51 52 ··· 237 238 ? rootPost.threadgate.record 238 239 : undefined 239 240 241 + const [hover, setHover] = useState(false) 240 242 return ( 241 243 <Link 242 244 testID={`feedItem-by-${post.author.handle}`} ··· 245 247 noFeedback 246 248 accessible={false} 247 249 onBeforePress={onBeforePress} 248 - dataSet={{feedContext}}> 250 + dataSet={{feedContext}} 251 + onPointerEnter={() => { 252 + setHover(true) 253 + }} 254 + onPointerLeave={() => { 255 + setHover(false) 256 + }}> 257 + <SubtleWebHover hover={hover} /> 249 258 <View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}> 250 259 <View style={{width: 42}}> 251 260 {isThreadChild && (
+1
src/view/com/util/Link.tsx
··· 49 49 anchorNoUnderline?: boolean 50 50 navigationAction?: 'push' | 'replace' | 'navigate' 51 51 onPointerEnter?: () => void 52 + onPointerLeave?: () => void 52 53 onBeforePress?: () => void 53 54 } 54 55
+53 -39
src/view/com/util/post-embeds/QuoteEmbed.tsx
··· 35 35 import {useSession} from '#/state/session' 36 36 import {atoms as a, useTheme} from '#/alf' 37 37 import {RichText} from '#/components/RichText' 38 + import {SubtleWebHover} from '#/components/SubtleWebHover' 38 39 import {ContentHider} from '../../../../components/moderation/ContentHider' 39 40 import {PostAlerts} from '../../../../components/moderation/PostAlerts' 40 41 import {Link} from '../Link' ··· 209 210 onOpen?.() 210 211 }, [queryClient, quote.author, onOpen]) 211 212 213 + const [hover, setHover] = React.useState(false) 212 214 return ( 213 - <ContentHider 214 - modui={moderation?.ui('contentList')} 215 - style={[ 216 - a.rounded_md, 217 - a.p_md, 218 - a.mt_sm, 219 - a.border, 220 - t.atoms.border_contrast_low, 221 - style, 222 - ]} 223 - childContainerStyle={[a.pt_sm]}> 224 - <Link 225 - hoverStyle={{borderColor: pal.colors.borderLinkHover}} 226 - href={itemHref} 227 - title={itemTitle} 228 - onBeforePress={onBeforePress}> 229 - <View pointerEvents="none"> 230 - <PostMeta 231 - author={quote.author} 232 - moderation={moderation} 233 - showAvatar 234 - postHref={itemHref} 235 - timestamp={quote.indexedAt} 236 - /> 237 - </View> 238 - {moderation ? ( 239 - <PostAlerts modui={moderation.ui('contentView')} style={[a.py_xs]} /> 240 - ) : null} 241 - {richText ? ( 242 - <RichText 243 - value={richText} 244 - style={a.text_md} 245 - numberOfLines={20} 246 - disableLinks 247 - /> 248 - ) : null} 249 - {embed && <PostEmbeds embed={embed} moderation={moderation} />} 250 - </Link> 251 - </ContentHider> 215 + <View 216 + onPointerEnter={() => { 217 + setHover(true) 218 + }} 219 + onPointerLeave={() => { 220 + setHover(false) 221 + }}> 222 + <ContentHider 223 + modui={moderation?.ui('contentList')} 224 + style={[ 225 + a.rounded_md, 226 + a.p_md, 227 + a.mt_sm, 228 + a.border, 229 + t.atoms.border_contrast_low, 230 + style, 231 + ]} 232 + childContainerStyle={[a.pt_sm]}> 233 + <SubtleWebHover hover={hover} /> 234 + <Link 235 + hoverStyle={{borderColor: pal.colors.borderLinkHover}} 236 + href={itemHref} 237 + title={itemTitle} 238 + onBeforePress={onBeforePress}> 239 + <View pointerEvents="none"> 240 + <PostMeta 241 + author={quote.author} 242 + moderation={moderation} 243 + showAvatar 244 + postHref={itemHref} 245 + timestamp={quote.indexedAt} 246 + /> 247 + </View> 248 + {moderation ? ( 249 + <PostAlerts 250 + modui={moderation.ui('contentView')} 251 + style={[a.py_xs]} 252 + /> 253 + ) : null} 254 + {richText ? ( 255 + <RichText 256 + value={richText} 257 + style={a.text_md} 258 + numberOfLines={20} 259 + disableLinks 260 + /> 261 + ) : null} 262 + {embed && <PostEmbeds embed={embed} moderation={moderation} />} 263 + </Link> 264 + </ContentHider> 265 + </View> 252 266 ) 253 267 } 254 268