React Native accessibility (#539)
* React Native accessibility * First round of changes * Latest update * Checkpoint * Wrap up * Lint * Remove unhelpful image hints * Fix navigation * Fix rebase and lint * Mitigate an known issue with the password entry in login * Fix composer dismiss * Remove focus on input elements for web * Remove i and npm * pls work * Remove stray declaration * Regenerate yarn.lock --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
parent
c75c888de2
commit
83959c595d
86 changed files with 2479 additions and 1827 deletions
|
@ -7,7 +7,6 @@ import {
|
|||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
|
@ -19,6 +18,8 @@ import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
|||
import {ExternalEmbed} from './ExternalEmbed'
|
||||
import {Text} from '../util/text/Text'
|
||||
import * as Toast from '../util/Toast'
|
||||
// TODO: Prevent naming components that coincide with RN primitives
|
||||
// due to linting false positives
|
||||
import {TextInput, TextInputRef} from './text-input/TextInput'
|
||||
import {CharProgress} from './char-progress/CharProgress'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
|
@ -87,27 +88,6 @@ export const ComposePost = observer(function ComposePost({
|
|||
autocompleteView.setup()
|
||||
}, [autocompleteView])
|
||||
|
||||
useEffect(() => {
|
||||
// HACK
|
||||
// wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
|
||||
// -prf
|
||||
let to: NodeJS.Timeout | undefined
|
||||
if (textInput.current) {
|
||||
to = setTimeout(() => {
|
||||
textInput.current?.focus()
|
||||
}, 250)
|
||||
}
|
||||
return () => {
|
||||
if (to) {
|
||||
clearTimeout(to)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onPressContainer = useCallback(() => {
|
||||
textInput.current?.focus()
|
||||
}, [textInput])
|
||||
|
||||
const onPressAddLinkCard = useCallback(
|
||||
(uri: string) => {
|
||||
setExtLink({uri, isLoading: true})
|
||||
|
@ -133,7 +113,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
|
||||
if (rt.text.trim().length === 0 && gallery.isEmpty) {
|
||||
setError('Did you want to say anything?')
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessing(true)
|
||||
|
@ -203,133 +183,149 @@ export const ComposePost = observer(function ComposePost({
|
|||
testID="composePostView"
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={styles.outer}>
|
||||
<TouchableWithoutFeedback onPressIn={onPressContainer}>
|
||||
<View style={[s.flex1, viewStyles]}>
|
||||
<View style={styles.topbar}>
|
||||
<TouchableOpacity
|
||||
testID="composerCancelButton"
|
||||
onPress={hackfixOnClose}>
|
||||
<Text style={[pal.link, s.f18]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
{isProcessing ? (
|
||||
<View style={styles.postBtn}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : canPost ? (
|
||||
<TouchableOpacity
|
||||
testID="composerPublishBtn"
|
||||
onPress={() => {
|
||||
onPressPublish(richtext)
|
||||
}}>
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={styles.postBtn}>
|
||||
<Text style={[s.white, s.f16, s.bold]}>
|
||||
{replyTo ? 'Reply' : 'Post'}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={[styles.postBtn, pal.btn]}>
|
||||
<Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal>
|
||||
<View style={styles.topbar}>
|
||||
<TouchableOpacity
|
||||
testID="composerCancelButton"
|
||||
onPress={hackfixOnClose}
|
||||
onAccessibilityEscape={hackfixOnClose}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Cancel"
|
||||
accessibilityHint="Closes post composer">
|
||||
<Text style={[pal.link, s.f18]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
{isProcessing ? (
|
||||
<View style={[pal.btn, styles.processingLine]}>
|
||||
<Text style={pal.text}>{processingState}</Text>
|
||||
<View style={styles.postBtn}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : undefined}
|
||||
{error !== '' && (
|
||||
<View style={styles.errorLine}>
|
||||
<View style={styles.errorIcon}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
style={{color: colors.red4}}
|
||||
size={10}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
||||
) : canPost ? (
|
||||
<TouchableOpacity
|
||||
testID="composerPublishBtn"
|
||||
onPress={() => {
|
||||
onPressPublish(richtext)
|
||||
}}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={replyTo ? 'Publish reply' : 'Publish post'}
|
||||
accessibilityHint={
|
||||
replyTo
|
||||
? 'Double tap to publish your reply'
|
||||
: 'Double tap to publish your post'
|
||||
}>
|
||||
<LinearGradient
|
||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={styles.postBtn}>
|
||||
<Text style={[s.white, s.f16, s.bold]}>
|
||||
{replyTo ? 'Reply' : 'Post'}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={[styles.postBtn, pal.btn]}>
|
||||
<Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
|
||||
</View>
|
||||
)}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
keyboardShouldPersistTaps="always">
|
||||
{replyTo ? (
|
||||
<View style={[pal.border, styles.replyToLayout]}>
|
||||
<UserAvatar avatar={replyTo.author.avatar} size={50} />
|
||||
<View style={styles.replyToPost}>
|
||||
<Text type="xl-medium" style={[pal.text]}>
|
||||
{sanitizeDisplayName(
|
||||
replyTo.author.displayName || replyTo.author.handle,
|
||||
)}
|
||||
</Text>
|
||||
<Text type="post-text" style={pal.text} numberOfLines={6}>
|
||||
{replyTo.text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
||||
<View style={[pal.border, styles.textInputLayout]}>
|
||||
<UserAvatar avatar={store.me.avatar} size={50} />
|
||||
<TextInput
|
||||
ref={textInput}
|
||||
richtext={richtext}
|
||||
placeholder={selectTextInputPlaceholder}
|
||||
suggestedLinks={suggestedLinks}
|
||||
autocompleteView={autocompleteView}
|
||||
setRichText={setRichText}
|
||||
onPhotoPasted={onPhotoPasted}
|
||||
onPressPublish={onPressPublish}
|
||||
onSuggestedLinksChanged={setSuggestedLinks}
|
||||
onError={setError}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Gallery gallery={gallery} />
|
||||
{gallery.isEmpty && extLink && (
|
||||
<ExternalEmbed
|
||||
link={extLink}
|
||||
onRemove={() => setExtLink(undefined)}
|
||||
/>
|
||||
)}
|
||||
{quote ? (
|
||||
<View style={s.mt5}>
|
||||
<QuoteEmbed quote={quote} />
|
||||
</View>
|
||||
) : undefined}
|
||||
</ScrollView>
|
||||
{!extLink && suggestedLinks.size > 0 ? (
|
||||
<View style={s.mb5}>
|
||||
{Array.from(suggestedLinks).map(url => (
|
||||
<TouchableOpacity
|
||||
key={`suggested-${url}`}
|
||||
testID="addLinkCardBtn"
|
||||
style={[pal.borderDark, styles.addExtLinkBtn]}
|
||||
onPress={() => onPressAddLinkCard(url)}>
|
||||
<Text style={pal.text}>
|
||||
Add link card: <Text style={pal.link}>{url}</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[pal.border, styles.bottomBar]}>
|
||||
{canSelectImages ? (
|
||||
<>
|
||||
<SelectPhotoBtn gallery={gallery} />
|
||||
<OpenCameraBtn gallery={gallery} />
|
||||
</>
|
||||
) : null}
|
||||
<View style={s.flex1} />
|
||||
<CharProgress count={graphemeLength} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
{isProcessing ? (
|
||||
<View style={[pal.btn, styles.processingLine]}>
|
||||
<Text style={pal.text}>{processingState}</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
{error !== '' && (
|
||||
<View style={styles.errorLine}>
|
||||
<View style={styles.errorIcon}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
style={{color: colors.red4}}
|
||||
size={10}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
||||
</View>
|
||||
)}
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
keyboardShouldPersistTaps="always">
|
||||
{replyTo ? (
|
||||
<View style={[pal.border, styles.replyToLayout]}>
|
||||
<UserAvatar avatar={replyTo.author.avatar} size={50} />
|
||||
<View style={styles.replyToPost}>
|
||||
<Text type="xl-medium" style={[pal.text]}>
|
||||
{sanitizeDisplayName(
|
||||
replyTo.author.displayName || replyTo.author.handle,
|
||||
)}
|
||||
</Text>
|
||||
<Text type="post-text" style={pal.text} numberOfLines={6}>
|
||||
{replyTo.text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
||||
<View style={[pal.border, styles.textInputLayout]}>
|
||||
<UserAvatar avatar={store.me.avatar} size={50} />
|
||||
<TextInput
|
||||
ref={textInput}
|
||||
richtext={richtext}
|
||||
placeholder={selectTextInputPlaceholder}
|
||||
suggestedLinks={suggestedLinks}
|
||||
autocompleteView={autocompleteView}
|
||||
autoFocus={true}
|
||||
setRichText={setRichText}
|
||||
onPhotoPasted={onPhotoPasted}
|
||||
onPressPublish={onPressPublish}
|
||||
onSuggestedLinksChanged={setSuggestedLinks}
|
||||
onError={setError}
|
||||
accessible={true}
|
||||
accessibilityLabel="Write post"
|
||||
accessibilityHint="Compose posts up to 300 characters in length"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Gallery gallery={gallery} />
|
||||
{gallery.isEmpty && extLink && (
|
||||
<ExternalEmbed
|
||||
link={extLink}
|
||||
onRemove={() => setExtLink(undefined)}
|
||||
/>
|
||||
)}
|
||||
{quote ? (
|
||||
<View style={s.mt5}>
|
||||
<QuoteEmbed quote={quote} />
|
||||
</View>
|
||||
) : undefined}
|
||||
</ScrollView>
|
||||
{!extLink && suggestedLinks.size > 0 ? (
|
||||
<View style={s.mb5}>
|
||||
{Array.from(suggestedLinks).map(url => (
|
||||
<TouchableOpacity
|
||||
key={`suggested-${url}`}
|
||||
testID="addLinkCardBtn"
|
||||
style={[pal.borderDark, styles.addExtLinkBtn]}
|
||||
onPress={() => onPressAddLinkCard(url)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Add link card"
|
||||
accessibilityHint={`Creates a card with a thumbnail. The card links to ${url}`}>
|
||||
<Text style={pal.text}>
|
||||
Add link card: <Text style={pal.link}>{url}</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[pal.border, styles.bottomBar]}>
|
||||
{canSelectImages ? (
|
||||
<>
|
||||
<SelectPhotoBtn gallery={gallery} />
|
||||
<OpenCameraBtn gallery={gallery} />
|
||||
</>
|
||||
) : null}
|
||||
<View style={s.flex1} />
|
||||
<CharProgress count={graphemeLength} />
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -60,7 +60,13 @@ export const ExternalEmbed = ({
|
|||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<TouchableOpacity style={styles.removeBtn} onPress={onRemove}>
|
||||
<TouchableOpacity
|
||||
style={styles.removeBtn}
|
||||
onPress={onRemove}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Remove image preview"
|
||||
accessibilityHint={`Removes default thumbnail from ${link.uri}`}
|
||||
onAccessibilityEscape={onRemove}>
|
||||
<FontAwesomeIcon size={18} icon="xmark" style={s.white} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
|
@ -13,7 +13,10 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
|
|||
<TouchableOpacity
|
||||
testID="replyPromptBtn"
|
||||
style={[pal.view, pal.border, styles.prompt]}
|
||||
onPress={() => onPressCompose()}>
|
||||
onPress={() => onPressCompose()}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Compose reply"
|
||||
accessibilityHint="Opens composer">
|
||||
<UserAvatar avatar={store.me.avatar} size={38} />
|
||||
<Text
|
||||
type="xl"
|
||||
|
|
|
@ -107,6 +107,9 @@ export const Gallery = observer(function ({gallery}: Props) {
|
|||
<View key={`selected-image-${image.path}`} style={[imageStyle]}>
|
||||
<TouchableOpacity
|
||||
testID="altTextButton"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Add alt text"
|
||||
accessibilityHint="Opens modal for inputting image alt text"
|
||||
onPress={() => {
|
||||
handleAddImageAltText(image)
|
||||
}}
|
||||
|
@ -116,6 +119,9 @@ export const Gallery = observer(function ({gallery}: Props) {
|
|||
<View style={imageControlsSubgroupStyle}>
|
||||
<TouchableOpacity
|
||||
testID="cropPhotoButton"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Crop image"
|
||||
accessibilityHint="Opens modal for cropping image"
|
||||
onPress={() => {
|
||||
handleEditPhoto(image)
|
||||
}}
|
||||
|
@ -128,6 +134,9 @@ export const Gallery = observer(function ({gallery}: Props) {
|
|||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
testID="removePhotoButton"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Remove image"
|
||||
accessibilityHint=""
|
||||
onPress={() => handleRemovePhoto(image)}
|
||||
style={styles.imageControl}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -144,6 +153,8 @@ export const Gallery = observer(function ({gallery}: Props) {
|
|||
source={{
|
||||
uri: image.compressed.path,
|
||||
}}
|
||||
accessible={true}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
</View>
|
||||
) : null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {useCallback} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {TouchableOpacity, StyleSheet} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
|
@ -7,7 +7,6 @@ import {
|
|||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnalytics} from 'lib/analytics'
|
||||
import {useStores} from 'state/index'
|
||||
import {s} from 'lib/styles'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
import {openCamera} from 'lib/media/picker'
|
||||
import {useCameraPermission} from 'lib/hooks/usePermissions'
|
||||
|
@ -54,8 +53,11 @@ export function OpenCameraBtn({gallery}: Props) {
|
|||
<TouchableOpacity
|
||||
testID="openCameraButton"
|
||||
onPress={onPressTakePicture}
|
||||
style={[s.pl5]}
|
||||
hitSlop={HITSLOP}>
|
||||
style={styles.button}
|
||||
hitSlop={HITSLOP}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Camera"
|
||||
accessibilityHint="Opens camera on device">
|
||||
<FontAwesomeIcon
|
||||
icon="camera"
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
|
@ -64,3 +66,9 @@ export function OpenCameraBtn({gallery}: Props) {
|
|||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React, {useCallback} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {TouchableOpacity, StyleSheet} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnalytics} from 'lib/analytics'
|
||||
import {s} from 'lib/styles'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
|
||||
import {GalleryModel} from 'state/models/media/gallery'
|
||||
|
@ -36,8 +35,11 @@ export function SelectPhotoBtn({gallery}: Props) {
|
|||
<TouchableOpacity
|
||||
testID="openGalleryBtn"
|
||||
onPress={onPressSelectPhotos}
|
||||
style={[s.pl5, s.pr20]}
|
||||
hitSlop={HITSLOP}>
|
||||
style={styles.button}
|
||||
hitSlop={HITSLOP}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Gallery"
|
||||
accessibilityHint="Opens device photo gallery">
|
||||
<FontAwesomeIcon
|
||||
icon={['far', 'image']}
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
|
@ -46,3 +48,9 @@ export function SelectPhotoBtn({gallery}: Props) {
|
|||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import React, {forwardRef, useCallback, useEffect, useRef, useMemo} from 'react'
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useRef,
|
||||
useMemo,
|
||||
ComponentProps,
|
||||
} from 'react'
|
||||
import {
|
||||
NativeSyntheticEvent,
|
||||
StyleSheet,
|
||||
TextInput as RNTextInput,
|
||||
TextInputSelectionChangeEventData,
|
||||
View,
|
||||
} from 'react-native'
|
||||
|
@ -27,14 +34,14 @@ export interface TextInputRef {
|
|||
blur: () => void
|
||||
}
|
||||
|
||||
interface TextInputProps {
|
||||
interface TextInputProps extends ComponentProps<typeof RNTextInput> {
|
||||
richtext: RichText
|
||||
placeholder: string
|
||||
suggestedLinks: Set<string>
|
||||
autocompleteView: UserAutocompleteModel
|
||||
setRichText: (v: RichText) => void
|
||||
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
||||
onPhotoPasted: (uri: string) => void
|
||||
onPressPublish: (richtext: RichText) => Promise<false | undefined>
|
||||
onPressPublish: (richtext: RichText) => Promise<void>
|
||||
onSuggestedLinksChanged: (uris: Set<string>) => void
|
||||
onError: (err: string) => void
|
||||
}
|
||||
|
@ -55,6 +62,7 @@ export const TextInput = forwardRef(
|
|||
onPhotoPasted,
|
||||
onSuggestedLinksChanged,
|
||||
onError,
|
||||
...props
|
||||
}: TextInputProps,
|
||||
ref,
|
||||
) => {
|
||||
|
@ -65,26 +73,11 @@ export const TextInput = forwardRef(
|
|||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
focus: () => textInput.current?.focus(),
|
||||
blur: () => textInput.current?.blur(),
|
||||
blur: () => {
|
||||
textInput.current?.blur()
|
||||
},
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
// HACK
|
||||
// wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
|
||||
// -prf
|
||||
let to: NodeJS.Timeout | undefined
|
||||
if (textInput.current) {
|
||||
to = setTimeout(() => {
|
||||
textInput.current?.focus()
|
||||
}, 250)
|
||||
}
|
||||
return () => {
|
||||
if (to) {
|
||||
clearTimeout(to)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onChangeText = useCallback(
|
||||
async (newText: string) => {
|
||||
const newRt = new RichText({text: newText})
|
||||
|
@ -206,8 +199,10 @@ export const TextInput = forwardRef(
|
|||
placeholder={placeholder}
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
keyboardAppearance={theme.colorScheme}
|
||||
autoFocus={true}
|
||||
multiline
|
||||
style={[pal.text, styles.textInput, styles.textInputFormatting]}>
|
||||
style={[pal.text, styles.textInput, styles.textInputFormatting]}
|
||||
{...props}>
|
||||
{textDecorated}
|
||||
</PasteInput>
|
||||
<Autocomplete
|
||||
|
|
|
@ -25,9 +25,9 @@ interface TextInputProps {
|
|||
placeholder: string
|
||||
suggestedLinks: Set<string>
|
||||
autocompleteView: UserAutocompleteModel
|
||||
setRichText: (v: RichText) => void
|
||||
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
||||
onPhotoPasted: (uri: string) => void
|
||||
onPressPublish: (richtext: RichText) => Promise<false | undefined>
|
||||
onPressPublish: (richtext: RichText) => Promise<void>
|
||||
onSuggestedLinksChanged: (uris: Set<string>) => void
|
||||
onError: (err: string) => void
|
||||
}
|
||||
|
|
|
@ -50,7 +50,9 @@ export const Autocomplete = observer(
|
|||
testID="autocompleteButton"
|
||||
key={item.handle}
|
||||
style={[pal.border, styles.item]}
|
||||
onPress={() => onSelect(item.handle)}>
|
||||
onPress={() => onSelect(item.handle)}
|
||||
accessibilityLabel={`Select ${item.handle}`}
|
||||
accessibilityHint={`Autocompletes to ${item.handle}`}>
|
||||
<Text type="md-medium" style={pal.text}>
|
||||
{item.displayName || item.handle}
|
||||
<Text type="sm" style={pal.textLight}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue