Add cursor to clickable elements (#491)

* Add cursor to clickable elements

* Add cursor to clickable elements

* Update per comments

* Fix word wrap in notifications

* Center the web login-load screen

* Add hover states on web

* Fix lint

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
zio/stable
Ollie Hsieh 2023-04-19 18:05:10 -07:00 committed by GitHub
parent 1472bd4f17
commit b24ba3adc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 184 additions and 142 deletions

View File

@ -1,7 +1,8 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, StyleSheet, View} from 'react-native' import {ActivityIndicator, StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {CenteredView} from '../util/Views'
import {LoggedOut} from './LoggedOut' import {LoggedOut} from './LoggedOut'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -30,14 +31,14 @@ function Loading() {
}, [setIsTakingTooLong]) }, [setIsTakingTooLong])
return ( return (
<View style={[styles.loading, pal.view]}> <CenteredView style={[styles.loading, pal.view]}>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
<Text type="2xl" style={[styles.loadingText, pal.textLight]}> <Text type="2xl" style={[styles.loadingText, pal.textLight]}>
{isTakingTooLong {isTakingTooLong
? "This is taking too long. There may be a problem with your internet or with the service, but we're going to try a couple more times..." ? "This is taking too long. There may be a problem with your internet or with the service, but we're going to try a couple more times..."
: 'Connecting...'} : 'Connecting...'}
</Text> </Text>
</View> </CenteredView>
) )
} }

View File

@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
import { import {
Animated, Animated,
TouchableOpacity, TouchableOpacity,
TouchableWithoutFeedback, Pressable,
StyleSheet, StyleSheet,
View, View,
} from 'react-native' } from 'react-native'
@ -124,7 +124,7 @@ export const FeedItem = observer(function FeedItem({
return <></> return <></>
} }
let authors: Author[] = [ const authors: Author[] = [
{ {
href: `/profile/${item.author.handle}`, href: `/profile/${item.author.handle}`,
handle: item.author.handle, handle: item.author.handle,
@ -132,18 +132,18 @@ export const FeedItem = observer(function FeedItem({
avatar: item.author.avatar, avatar: item.author.avatar,
labels: item.author.labels, labels: item.author.labels,
}, },
] ...(item.additional?.map(
if (item.additional?.length) { ({author: {avatar, labels, handle, displayName}}) => {
authors = authors.concat( return {
item.additional.map(item2 => ({ href: `/profile/${handle}`,
href: `/profile/${item2.author.handle}`, handle,
handle: item2.author.handle, displayName,
displayName: item2.author.displayName, avatar,
avatar: item2.author.avatar, labels,
labels: item2.author.labels,
})),
)
} }
},
) || []),
]
return ( return (
<Link <Link
@ -161,7 +161,6 @@ export const FeedItem = observer(function FeedItem({
href={itemHref} href={itemHref}
title={itemTitle} title={itemTitle}
noFeedback> noFeedback>
<View style={styles.layout}>
<View style={styles.layoutIcon}> <View style={styles.layoutIcon}>
{icon === 'HeartIconSolid' ? ( {icon === 'HeartIconSolid' ? (
<HeartIconSolid size={28} style={[styles.icon, ...iconStyle]} /> <HeartIconSolid size={28} style={[styles.icon, ...iconStyle]} />
@ -174,18 +173,14 @@ export const FeedItem = observer(function FeedItem({
)} )}
</View> </View>
<View style={styles.layoutContent}> <View style={styles.layoutContent}>
<TouchableWithoutFeedback <Pressable
onPress={authors.length > 1 ? onToggleAuthorsExpanded : () => {}}> onPress={authors.length > 1 ? onToggleAuthorsExpanded : () => {}}>
<View>
<CondensedAuthorsList <CondensedAuthorsList
visible={!isAuthorsExpanded} visible={!isAuthorsExpanded}
authors={authors} authors={authors}
onToggleAuthorsExpanded={onToggleAuthorsExpanded} onToggleAuthorsExpanded={onToggleAuthorsExpanded}
/> />
<ExpandedAuthorsList <ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
visible={isAuthorsExpanded}
authors={authors}
/>
<View style={styles.meta}> <View style={styles.meta}>
<TextLink <TextLink
key={authors[0].href} key={authors[0].href}
@ -199,8 +194,7 @@ export const FeedItem = observer(function FeedItem({
<> <>
<Text style={[styles.metaItem, pal.text]}>and</Text> <Text style={[styles.metaItem, pal.text]}>and</Text>
<Text style={[styles.metaItem, pal.text, s.bold]}> <Text style={[styles.metaItem, pal.text, s.bold]}>
{authors.length - 1}{' '} {authors.length - 1} {pluralize(authors.length - 1, 'other')}
{pluralize(authors.length - 1, 'other')}
</Text> </Text>
</> </>
) : undefined} ) : undefined}
@ -209,14 +203,10 @@ export const FeedItem = observer(function FeedItem({
{ago(item.indexedAt)} {ago(item.indexedAt)}
</Text> </Text>
</View> </View>
</View> </Pressable>
</TouchableWithoutFeedback>
{item.isLike || item.isRepost || item.isQuote ? ( {item.isLike || item.isRepost || item.isQuote ? (
<AdditionalPostText additionalPost={item.additionalPost} /> <AdditionalPostText additionalPost={item.additionalPost} />
) : ( ) : null}
<></>
)}
</View>
</View> </View>
</Link> </Link>
) )
@ -392,8 +382,6 @@ const styles = StyleSheet.create({
padding: 10, padding: 10,
paddingRight: 15, paddingRight: 15,
borderTopWidth: 1, borderTopWidth: 1,
},
layout: {
flexDirection: 'row', flexDirection: 'row',
}, },
layoutIcon: { layoutIcon: {
@ -405,6 +393,9 @@ const styles = StyleSheet.create({
marginRight: 10, marginRight: 10,
marginTop: 4, marginTop: 4,
}, },
layoutContent: {
flex: 1,
},
avis: { avis: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -413,9 +404,6 @@ const styles = StyleSheet.create({
fontWeight: 'bold', fontWeight: 'bold',
paddingLeft: 6, paddingLeft: 6,
}, },
layoutContent: {
flex: 1,
},
meta: { meta: {
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',

View File

@ -1,11 +1,7 @@
import React, {createRef, useState, useMemo, useRef} from 'react' import React, {createRef, useState, useMemo, useRef} from 'react'
import { import {Animated, StyleSheet, View} from 'react-native'
Animated,
StyleSheet,
TouchableWithoutFeedback,
View,
} from 'react-native'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {PressableWithHover} from '../util/PressableWithHover'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb} from 'platform/detection'
@ -109,20 +105,21 @@ export function TabBar({
{items.map((item, i) => { {items.map((item, i) => {
const selected = i === selectedPage const selected = i === selectedPage
return ( return (
<TouchableWithoutFeedback <PressableWithHover
key={i} ref={itemRefs[i]}
testID={testID ? `${testID}-${item}` : undefined} key={item}
onPress={() => onPressItem(i)}>
<View
style={ style={
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
} }
ref={itemRefs[i]}> hoverStyle={pal.viewLight}
<Text type="xl-bold" style={selected ? pal.text : pal.textLight}> onPress={() => onPressItem(i)}>
<Text
type="xl-bold"
testID={testID ? `${testID}-${item}` : undefined}
style={selected ? pal.text : pal.textLight}>
{item} {item}
</Text> </Text>
</View> </PressableWithHover>
</TouchableWithoutFeedback>
) )
})} })}
</View> </View>
@ -138,18 +135,19 @@ const styles = isDesktopWeb
itemTop: { itemTop: {
paddingTop: 16, paddingTop: 16,
paddingBottom: 14, paddingBottom: 14,
marginRight: 24, paddingHorizontal: 12,
}, },
itemBottom: { itemBottom: {
paddingTop: 14, paddingTop: 14,
paddingBottom: 16, paddingBottom: 16,
marginRight: 24, paddingHorizontal: 12,
}, },
indicator: { indicator: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
width: 1, width: 1,
height: 3, height: 3,
zIndex: 1,
}, },
}) })
: StyleSheet.create({ : StyleSheet.create({

View File

@ -48,7 +48,6 @@ export function ProfileCard({
]} ]}
href={`/profile/${handle}`} href={`/profile/${handle}`}
title={handle} title={handle}
noFeedback
asAnchor> asAnchor>
<View style={styles.layout}> <View style={styles.layout}>
<View style={styles.layoutAvi}> <View style={styles.layoutAvi}>

View File

@ -64,7 +64,7 @@ export const ProfileHeader = observer(
<View style={[styles.buttonsLine]}> <View style={[styles.buttonsLine]}>
<LoadingPlaceholder width={100} height={31} style={styles.br50} /> <LoadingPlaceholder width={100} height={31} style={styles.br50} />
</View> </View>
<View style={styles.displayNameLine}> <View>
<Text type="title-2xl" style={[pal.text, styles.title]}> <Text type="title-2xl" style={[pal.text, styles.title]}>
{sanitizeDisplayName(view.displayName || view.handle)} {sanitizeDisplayName(view.displayName || view.handle)}
</Text> </Text>
@ -273,7 +273,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({
</DropdownButton> </DropdownButton>
) : undefined} ) : undefined}
</View> </View>
<View style={styles.displayNameLine}> <View>
<Text <Text
testID="profileHeaderDisplayName" testID="profileHeaderDisplayName"
type="title-2xl" type="title-2xl"
@ -431,11 +431,6 @@ const styles = StyleSheet.create({
borderRadius: 50, borderRadius: 50,
marginLeft: 6, marginLeft: 6,
}, },
displayNameLine: {
// paddingLeft: 86,
// marginBottom: 14,
},
title: {lineHeight: 38}, title: {lineHeight: 38},
handleLine: { handleLine: {

View File

@ -0,0 +1,45 @@
import React, {
useState,
useCallback,
PropsWithChildren,
forwardRef,
Ref,
} from 'react'
import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
import {addStyle} from 'lib/styles'
interface PressableWithHover extends PressableProps {
hoverStyle: StyleProp<ViewStyle>
}
export const PressableWithHover = forwardRef(
(
{
children,
style,
hoverStyle,
...props
}: PropsWithChildren<PressableWithHover>,
ref: Ref<any>,
) => {
const [isHovering, setIsHovering] = useState(false)
const onHoverIn = useCallback(() => setIsHovering(true), [setIsHovering])
const onHoverOut = useCallback(() => setIsHovering(false), [setIsHovering])
style =
typeof style !== 'function' && isHovering
? addStyle(style, hoverStyle)
: style
return (
<Pressable
{...props}
style={style}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
ref={ref}>
{children}
</Pressable>
)
},
)

View File

@ -1,10 +1,5 @@
import React, {createRef, useState, useMemo, useRef} from 'react' import React, {createRef, useState, useMemo, useRef} from 'react'
import { import {Animated, Pressable, StyleSheet, View} from 'react-native'
Animated,
StyleSheet,
TouchableWithoutFeedback,
View,
} from 'react-native'
import {Text} from './text/Text' import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -99,7 +94,7 @@ export function Selector({
{items.map((item, i) => { {items.map((item, i) => {
const selected = i === selectedIndex const selected = i === selectedIndex
return ( return (
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}> <Pressable key={item} onPress={() => onPressItem(i)}>
<View style={styles.item} ref={itemRefs[i]}> <View style={styles.item} ref={itemRefs[i]}>
<Text <Text
style={ style={
@ -110,7 +105,7 @@ export function Selector({
{item} {item}
</Text> </Text>
</View> </View>
</TouchableWithoutFeedback> </Pressable>
) )
})} })}
</View> </View>

View File

@ -40,7 +40,7 @@ export function ContentHider({
} }
if (labelPref.pref === 'hide') { if (labelPref.pref === 'hide') {
return <></> return null
} }
return ( return (

View File

@ -1,8 +1,8 @@
import React from 'react' import React, {useCallback} from 'react'
import { import {
Keyboard,
StyleSheet, StyleSheet,
TouchableWithoutFeedback, TouchableWithoutFeedback,
Keyboard,
View, View,
} from 'react-native' } from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
@ -26,6 +26,7 @@ import {s} from 'lib/styles'
import {ProfileCard} from 'view/com/profile/ProfileCard' import {ProfileCard} from 'view/com/profile/ProfileCard'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {isAndroid, isIOS} from 'platform/detection'
type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
export const SearchScreen = withAuthRequired( export const SearchScreen = withAuthRequired(
@ -110,8 +111,14 @@ export const SearchScreen = withAuthRequired(
}, [store, autocompleteView, foafs, suggestedActors, onSoftReset]), }, [store, autocompleteView, foafs, suggestedActors, onSoftReset]),
) )
const onPress = useCallback(() => {
if (isIOS || isAndroid) {
Keyboard.dismiss()
}
}, [])
return ( return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> <TouchableWithoutFeedback onPress={onPress}>
<View style={[pal.view, styles.container]}> <View style={[pal.view, styles.container]}>
<HeaderWithInput <HeaderWithInput
isInputFocused={isInputFocused} isInputFocused={isInputFocused}
@ -139,16 +146,19 @@ export const SearchScreen = withAuthRequired(
scrollEventThrottle={100}> scrollEventThrottle={100}>
{query && autocompleteView.searchRes.length ? ( {query && autocompleteView.searchRes.length ? (
<> <>
{autocompleteView.searchRes.map(item => ( {autocompleteView.searchRes.map(
({did, handle, displayName, labels, avatar}, index) => (
<ProfileCard <ProfileCard
key={item.did} key={did}
testID={`searchAutoCompleteResult-${item.handle}`} testID={`searchAutoCompleteResult-${handle}`}
handle={item.handle} handle={handle}
displayName={item.displayName} displayName={displayName}
labels={item.labels} labels={labels}
avatar={item.avatar} avatar={avatar}
noBorder={index === 0}
/> />
))} ),
)}
</> </>
) : query && !autocompleteView.searchRes.length ? ( ) : query && !autocompleteView.searchRes.length ? (
<View> <View>

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {PressableWithHover} from 'view/com/util/PressableWithHover'
import {useNavigation, useNavigationState} from '@react-navigation/native' import {useNavigation, useNavigationState} from '@react-navigation/native'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
@ -88,6 +89,9 @@ const NavItem = observer(
const isCurrent = isTab(currentRouteName, pathName) const isCurrent = isTab(currentRouteName, pathName)
return ( return (
<PressableWithHover
style={styles.navItemWrapper}
hoverStyle={pal.viewLight}>
<Link href={href} style={styles.navItem}> <Link href={href} style={styles.navItem}>
<View style={[styles.navItemIconWrapper]}> <View style={[styles.navItemIconWrapper]}>
{isCurrent ? iconFilled : icon} {isCurrent ? iconFilled : icon}
@ -101,6 +105,7 @@ const NavItem = observer(
{label} {label}
</Text> </Text>
</Link> </Link>
</PressableWithHover>
) )
}, },
) )
@ -193,13 +198,14 @@ const styles = StyleSheet.create({
leftNav: { leftNav: {
position: 'absolute', position: 'absolute',
top: 10, top: 10,
right: 'calc(50vw + 300px)', right: 'calc(50vw + 312px)',
width: 220, width: 220,
}, },
profileCard: { profileCard: {
marginVertical: 10, marginVertical: 10,
width: 60, width: 60,
paddingLeft: 12,
}, },
backBtn: { backBtn: {
@ -210,11 +216,15 @@ const styles = StyleSheet.create({
height: 30, height: 30,
}, },
navItemWrapper: {
paddingHorizontal: 12,
borderRadius: 8,
},
navItem: { navItem: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingTop: 14, paddingTop: 12,
paddingBottom: 10, paddingBottom: 12,
}, },
navItemIconWrapper: { navItemIconWrapper: {
alignItems: 'center', alignItems: 'center',
@ -245,6 +255,7 @@ const styles = StyleSheet.create({
paddingVertical: 10, paddingVertical: 10,
paddingHorizontal: 16, paddingHorizontal: 16,
backgroundColor: colors.blue3, backgroundColor: colors.blue3,
marginLeft: 12,
marginTop: 20, marginTop: 20,
marginBottom: 10, marginBottom: 10,
}, },