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:
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user