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 {ActivityIndicator, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {CenteredView} from '../util/Views'
import {LoggedOut} from './LoggedOut'
import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
@ -30,14 +31,14 @@ function Loading() {
}, [setIsTakingTooLong])
return (
<View style={[styles.loading, pal.view]}>
<CenteredView style={[styles.loading, pal.view]}>
<ActivityIndicator size="large" />
<Text type="2xl" style={[styles.loadingText, pal.textLight]}>
{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..."
: 'Connecting...'}
</Text>
</View>
</CenteredView>
)
}

View File

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

View File

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

View File

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

View File

@ -64,7 +64,7 @@ export const ProfileHeader = observer(
<View style={[styles.buttonsLine]}>
<LoadingPlaceholder width={100} height={31} style={styles.br50} />
</View>
<View style={styles.displayNameLine}>
<View>
<Text type="title-2xl" style={[pal.text, styles.title]}>
{sanitizeDisplayName(view.displayName || view.handle)}
</Text>
@ -273,7 +273,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoaded({
</DropdownButton>
) : undefined}
</View>
<View style={styles.displayNameLine}>
<View>
<Text
testID="profileHeaderDisplayName"
type="title-2xl"
@ -431,11 +431,6 @@ const styles = StyleSheet.create({
borderRadius: 50,
marginLeft: 6,
},
displayNameLine: {
// paddingLeft: 86,
// marginBottom: 14,
},
title: {lineHeight: 38},
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 {
Animated,
StyleSheet,
TouchableWithoutFeedback,
View,
} from 'react-native'
import {Animated, Pressable, StyleSheet, View} from 'react-native'
import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette'
@ -99,7 +94,7 @@ export function Selector({
{items.map((item, i) => {
const selected = i === selectedIndex
return (
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
<Pressable key={item} onPress={() => onPressItem(i)}>
<View style={styles.item} ref={itemRefs[i]}>
<Text
style={
@ -110,7 +105,7 @@ export function Selector({
{item}
</Text>
</View>
</TouchableWithoutFeedback>
</Pressable>
)
})}
</View>

View File

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

View File

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

View File

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