Fix overflow issue on iOS autocomplete, among other things (#6611)

* stop using ref in render

* fix display name fallback on web

* use unicode ellipsis for useGrapheme

* fix overflow issue

* sanitize handle/displayname on web
This commit is contained in:
Samuel Newman
2024-11-21 22:32:32 +00:00
committed by GitHub
parent f67e00a85c
commit 9f1e648686
3 changed files with 27 additions and 40 deletions
@@ -13,7 +13,7 @@ export const useGrapheme = () => {
if (graphemes.length > length) { if (graphemes.length > length) {
remainingCharacters = 0 remainingCharacters = 0
name = `${graphemes.slice(0, length).join('')}...` name = `${graphemes.slice(0, length).join('')}…`
} else { } else {
remainingCharacters = length - graphemes.length remainingCharacters = length - graphemes.length
name = graphemes.join('') name = graphemes.join('')
@@ -1,7 +1,5 @@
import {useRef} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated'
import {AppBskyActorDefs} from '@atproto/api'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {PressableScale} from '#/lib/custom-animations/PressableScale' import {PressableScale} from '#/lib/custom-animations/PressableScale'
@@ -11,7 +9,6 @@ import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useGrapheme} from '../hooks/useGrapheme'
export function Autocomplete({ export function Autocomplete({
prefix, prefix,
@@ -22,15 +19,11 @@ export function Autocomplete({
}) { }) {
const t = useTheme() const t = useTheme()
const {getGraphemeString} = useGrapheme()
const isActive = !!prefix const isActive = !!prefix
const {data: suggestions, isFetching} = useActorAutocompleteQuery(prefix) const {data: suggestions, isFetching} = useActorAutocompleteQuery(
const suggestionsRef = useRef< prefix,
AppBskyActorDefs.ProfileViewBasic[] | undefined true,
>(undefined) )
if (suggestions) {
suggestionsRef.current = suggestions
}
if (!isActive) return null if (!isActive) return null
@@ -46,26 +39,8 @@ export function Autocomplete({
t.atoms.border_contrast_high, t.atoms.border_contrast_high,
{marginLeft: -62}, {marginLeft: -62},
]}> ]}>
{suggestionsRef.current?.length ? ( {suggestions?.length ? (
suggestionsRef.current.slice(0, 5).map((item, index, arr) => { suggestions.slice(0, 5).map((item, index, arr) => {
// Eventually use an average length
const MAX_CHARS = 40
const MAX_HANDLE_CHARS = 20
// Using this approach because styling is not respecting
// bounding box wrapping (before converting to ellipsis)
const {name: displayHandle, remainingCharacters} = getGraphemeString(
item.handle,
MAX_HANDLE_CHARS,
)
const {name: displayName} = getGraphemeString(
item.displayName || item.handle,
MAX_CHARS -
MAX_HANDLE_CHARS +
(remainingCharacters > 0 ? remainingCharacters : 0),
)
return ( return (
<View <View
style={[ style={[
@@ -93,15 +68,23 @@ export function Autocomplete({
type={item.associated?.labeler ? 'labeler' : 'user'} type={item.associated?.labeler ? 'labeler' : 'user'}
/> />
<Text <Text
style={[a.text_md, a.font_bold]} style={[a.flex_1, a.text_md, a.font_bold]}
emoji={true} emoji
numberOfLines={1}> numberOfLines={1}>
{sanitizeDisplayName(displayName)} {sanitizeDisplayName(
item.displayName || sanitizeHandle(item.handle),
)}
</Text>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_right,
{maxWidth: '50%'},
]}
numberOfLines={1}>
{sanitizeHandle(item.handle, '@')}
</Text> </Text>
</View> </View>
<Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
{sanitizeHandle(displayHandle, '@')}
</Text>
</PressableScale> </PressableScale>
</View> </View>
) )
@@ -10,6 +10,8 @@ import {
import tippy, {Instance as TippyInstance} from 'tippy.js' import tippy, {Instance as TippyInstance} from 'tippy.js'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {ActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
@@ -148,7 +150,9 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>(
{items.length > 0 ? ( {items.length > 0 ? (
items.map((item, index) => { items.map((item, index) => {
const {name: displayName} = getGraphemeString( const {name: displayName} = getGraphemeString(
item.displayName ?? item.handle, sanitizeDisplayName(
item.displayName || sanitizeHandle(item.handle),
),
30, // Heuristic value; can be modified 30, // Heuristic value; can be modified
) )
const isSelected = selectedIndex === index const isSelected = selectedIndex === index
@@ -181,7 +185,7 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>(
</Text> </Text>
</View> </View>
<Text type="xs" style={pal.textLight} numberOfLines={1}> <Text type="xs" style={pal.textLight} numberOfLines={1}>
@{item.handle} {sanitizeHandle(item.handle, '@')}
</Text> </Text>
</Pressable> </Pressable>
) )