Limit height of autocomplete, adjust emoji autocomplete design
This commit is contained in:
+2
-2
@@ -88,8 +88,8 @@
|
|||||||
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
||||||
"@bsky.app/expo-translate-text": "^0.2.9",
|
"@bsky.app/expo-translate-text": "^0.2.9",
|
||||||
"@bsky.app/react-native-mmkv": "2.12.5",
|
"@bsky.app/react-native-mmkv": "2.12.5",
|
||||||
"@bsky.app/sift": "^0.2.6",
|
"@bsky.app/sift": "^0.2.8",
|
||||||
"@bsky.app/tapper": "^0.4.1",
|
"@bsky.app/tapper": "^0.4.2",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
"@emoji-mart/data": "^1.2.1",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ export function AutocompleteItemEmoji({
|
|||||||
<SiftItem
|
<SiftItem
|
||||||
{...props}
|
{...props}
|
||||||
style={s => [
|
style={s => [
|
||||||
a.px_md,
|
{paddingVertical: 6, paddingHorizontal: 10},
|
||||||
a.py_sm,
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
active || s.hovered || s.pressed ? [t.atoms.bg_contrast_25] : [],
|
active || s.hovered || s.pressed ? [t.atoms.bg_contrast_25] : [],
|
||||||
]}>
|
]}>
|
||||||
<Text>{item.value}</Text>
|
<Text style={[a.text_xl, a.leading_tight]}>{item.value}</Text>
|
||||||
|
<Text style={[a.text_md, a.leading_tight]}>:{item.emoji.id}:</Text>
|
||||||
</SiftItem>
|
</SiftItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {type Sift} from '@bsky.app/sift'
|
import {type Sift} from '@bsky.app/sift'
|
||||||
|
import {type Emoji} from '@emoji-mart/data'
|
||||||
|
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export type AutocompleteEmoji = {
|
|||||||
key: string
|
key: string
|
||||||
type: 'emoji'
|
type: 'emoji'
|
||||||
value: string
|
value: string
|
||||||
emoji: string
|
emoji: Emoji
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AutocompleteItem =
|
export type AutocompleteItem =
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function useAutocomplete({
|
|||||||
key: result.item.id,
|
key: result.item.id,
|
||||||
type: 'emoji' as const,
|
type: 'emoji' as const,
|
||||||
value: result.item.skins[0].native,
|
value: result.item.skins[0].native,
|
||||||
emoji: result.item.skins[0].native,
|
emoji: result.item,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/**
|
|
||||||
* TODO
|
|
||||||
*
|
|
||||||
* Native
|
|
||||||
* - make sure we limit the height of autocomplete so it doesn't go off screen
|
|
||||||
*/
|
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -14,7 +8,6 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
type TextInput,
|
type TextInput,
|
||||||
type TextInputProps,
|
|
||||||
type TextInputSubmitEditingEvent,
|
type TextInputSubmitEditingEvent,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
@@ -22,8 +15,10 @@ import Animated, {
|
|||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {useSift, type UseSiftReturn} from '@bsky.app/sift'
|
import {useSift, type UseSiftReturn} from '@bsky.app/sift'
|
||||||
import {
|
import {
|
||||||
|
facets,
|
||||||
type TapperActiveFacet,
|
type TapperActiveFacet,
|
||||||
type TapperFacet,
|
type TapperFacet,
|
||||||
useTapper,
|
useTapper,
|
||||||
@@ -45,7 +40,10 @@ import {
|
|||||||
parseAutocompleteItemType,
|
parseAutocompleteItemType,
|
||||||
useAutocomplete,
|
useAutocomplete,
|
||||||
} from '#/components/Autocomplete'
|
} from '#/components/Autocomplete'
|
||||||
import {AutosizedTextarea} from '#/components/forms/AutosizedTextarea'
|
import {
|
||||||
|
AutosizedTextarea,
|
||||||
|
type AutosizedTextareaProps,
|
||||||
|
} from '#/components/forms/AutosizedTextarea'
|
||||||
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
|
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
|
||||||
import {Span, Text} from '#/components/Typography'
|
import {Span, Text} from '#/components/Typography'
|
||||||
import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
|
import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||||
@@ -71,6 +69,7 @@ export type ComposerInternalApi = {
|
|||||||
input?: ReturnType<typeof useTapper>['input']
|
input?: ReturnType<typeof useTapper>['input']
|
||||||
clear: () => void
|
clear: () => void
|
||||||
insert(text: string): void
|
insert(text: string): void
|
||||||
|
setAnchorRef(ref: View | null): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useComposerInternalApiRef() {
|
export function useComposerInternalApiRef() {
|
||||||
@@ -82,7 +81,7 @@ export function useComposerInternalApiRef() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type ComposerProps = Omit<
|
export type ComposerProps = Omit<
|
||||||
TextInputProps,
|
AutosizedTextareaProps,
|
||||||
| 'value'
|
| 'value'
|
||||||
| 'onChange'
|
| 'onChange'
|
||||||
| 'onChangeText'
|
| 'onChangeText'
|
||||||
@@ -91,57 +90,65 @@ export type ComposerProps = Omit<
|
|||||||
| 'style'
|
| 'style'
|
||||||
| 'onSubmitEditing'
|
| 'onSubmitEditing'
|
||||||
> & {
|
> & {
|
||||||
children?: React.ReactNode
|
|
||||||
label: string
|
label: string
|
||||||
ref?: React.Ref<TextInput>
|
ref?: React.Ref<TextInput>
|
||||||
style?: ViewStyleProp['style']
|
internalApiRef?: React.Ref<ComposerInternalApi>
|
||||||
padding?: {
|
outerStyle?: ViewStyleProp['style']
|
||||||
|
contentTextStyle?: TextStyleProp['style']
|
||||||
|
contentPaddingStyle?: {
|
||||||
paddingTop?: number
|
paddingTop?: number
|
||||||
paddingBottom?: number
|
paddingBottom?: number
|
||||||
paddingLeft?: number
|
paddingLeft?: number
|
||||||
paddingRight?: number
|
paddingRight?: number
|
||||||
}
|
}
|
||||||
textStyle?: TextStyleProp['style']
|
|
||||||
maxNumberOfLines?: number
|
|
||||||
initialText?: string
|
|
||||||
onChange?: (text: string) => void
|
onChange?: (text: string) => void
|
||||||
onActiveFacet?: (activeFacet: TapperActiveFacet | null) => void
|
onActiveFacet?: (activeFacet: TapperActiveFacet | null) => void
|
||||||
onFacetCommitted?: (facet: TapperFacet) => void
|
onFacetCommitted?: (facet: TapperFacet) => void
|
||||||
onRequestSubmit?: (request: SubmitRequest) => void
|
onRequestSubmit?: (request: SubmitRequest) => void
|
||||||
internalApiRef?: React.Ref<ComposerInternalApi>
|
|
||||||
autocompletePlacement?: Exclude<
|
autocompletePlacement?: Exclude<
|
||||||
Parameters<typeof useSift>[0],
|
Parameters<typeof useSift>[0],
|
||||||
undefined
|
undefined
|
||||||
>['placement']
|
>['placement']
|
||||||
|
disableEmojiFacets?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Composer({
|
export function Composer({
|
||||||
children,
|
|
||||||
label,
|
label,
|
||||||
placeholder,
|
ref,
|
||||||
style,
|
internalApiRef,
|
||||||
padding,
|
outerStyle,
|
||||||
textStyle: rawTextStyle,
|
contentTextStyle,
|
||||||
maxNumberOfLines,
|
contentPaddingStyle,
|
||||||
initialText,
|
|
||||||
onChange: onChangeOuter,
|
onChange: onChangeOuter,
|
||||||
onActiveFacet: onActiveFacetOuter,
|
onActiveFacet: onActiveFacetOuter,
|
||||||
onFacetCommitted: onFacetCommittedOuter,
|
onFacetCommitted: onFacetCommittedOuter,
|
||||||
onRequestSubmit,
|
onRequestSubmit,
|
||||||
internalApiRef,
|
|
||||||
autocompletePlacement,
|
autocompletePlacement,
|
||||||
|
defaultValue,
|
||||||
|
disableEmojiFacets = !IS_WEB,
|
||||||
...rest
|
...rest
|
||||||
}: ComposerProps) {
|
}: ComposerProps) {
|
||||||
const {theme: t, fonts} = useAlf()
|
const {theme: t, fonts} = useAlf()
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Meat and potatoes
|
* Meat and potatoes
|
||||||
*/
|
*/
|
||||||
const tapper = useTapper({initialText})
|
const tapper = useTapper({
|
||||||
|
initialText: defaultValue ?? '',
|
||||||
|
facets: disableEmojiFacets
|
||||||
|
? {
|
||||||
|
mention: facets.mention,
|
||||||
|
tag: facets.tag,
|
||||||
|
url: facets.url,
|
||||||
|
}
|
||||||
|
: facets,
|
||||||
|
})
|
||||||
const sift = useSift({
|
const sift = useSift({
|
||||||
offset: a.p_sm.padding,
|
offset: a.p_sm.padding,
|
||||||
placement: autocompletePlacement,
|
placement: autocompletePlacement,
|
||||||
dynamicWidth: IS_WEB,
|
dynamicWidth: IS_WEB,
|
||||||
|
insets,
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -166,8 +173,11 @@ export function Composer({
|
|||||||
inputScrollSharedValue.value = 0
|
inputScrollSharedValue.value = 0
|
||||||
},
|
},
|
||||||
insert: tapper.insert,
|
insert: tapper.insert,
|
||||||
|
setAnchorRef: (ref: View | null) => {
|
||||||
|
sift.refs.setAnchor(ref)
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[tapper.input, tapper.insert, inputScrollSharedValue],
|
[tapper.input, tapper.insert, inputScrollSharedValue, sift.refs],
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -220,7 +230,7 @@ export function Composer({
|
|||||||
}))
|
}))
|
||||||
const textStyle = useMemo(() => {
|
const textStyle = useMemo(() => {
|
||||||
const ts = normalizeTextStyles(
|
const ts = normalizeTextStyles(
|
||||||
[a.leading_snug, rawTextStyle, t.atoms.text],
|
[a.leading_snug, t.atoms.text, contentTextStyle],
|
||||||
{
|
{
|
||||||
fontScale: fonts.scaleMultiplier,
|
fontScale: fonts.scaleMultiplier,
|
||||||
fontFamily: fonts.family,
|
fontFamily: fonts.family,
|
||||||
@@ -240,7 +250,7 @@ export function Composer({
|
|||||||
delete ts.lineHeight
|
delete ts.lineHeight
|
||||||
}
|
}
|
||||||
return ts
|
return ts
|
||||||
}, [rawTextStyle, fonts])
|
}, [contentTextStyle, fonts])
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Web keyboard handling
|
* Web keyboard handling
|
||||||
@@ -282,13 +292,13 @@ export function Composer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={[a.relative, style]}>
|
<View style={[a.relative, outerStyle]}>
|
||||||
<View
|
<View
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
style={[a.absolute, a.inset_0, a.z_10, {overflow: 'hidden'}]}>
|
style={[a.absolute, a.inset_0, a.z_10, {overflow: 'hidden'}]}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
padding,
|
contentPaddingStyle,
|
||||||
{position: 'absolute', left: 0, right: 0},
|
{position: 'absolute', left: 0, right: 0},
|
||||||
previewScrollStyle,
|
previewScrollStyle,
|
||||||
]}>
|
]}>
|
||||||
@@ -317,8 +327,6 @@ export function Composer({
|
|||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
<AutosizedTextarea
|
<AutosizedTextarea
|
||||||
maxRows={12}
|
|
||||||
placeholder={placeholder}
|
|
||||||
placeholderTextColor={t.palette.contrast_500}
|
placeholderTextColor={t.palette.contrast_500}
|
||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
accessibilityHint={label}
|
accessibilityHint={label}
|
||||||
@@ -327,7 +335,7 @@ export function Composer({
|
|||||||
}}
|
}}
|
||||||
style={[
|
style={[
|
||||||
textStyle,
|
textStyle,
|
||||||
padding,
|
contentPaddingStyle,
|
||||||
a.z_20,
|
a.z_20,
|
||||||
{
|
{
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
@@ -341,11 +349,7 @@ export function Composer({
|
|||||||
{...rest}
|
{...rest}
|
||||||
{...tapper.inputProps}
|
{...tapper.inputProps}
|
||||||
{...sift.targetProps}
|
{...sift.targetProps}
|
||||||
ref={mergeRefs([
|
ref={mergeRefs([ref, tapper.inputProps.ref, sift.targetProps.ref])}
|
||||||
rest.ref,
|
|
||||||
tapper.inputProps.ref,
|
|
||||||
sift.targetProps.ref,
|
|
||||||
])}
|
|
||||||
onBlur={e => {
|
onBlur={e => {
|
||||||
rest.onBlur?.(e)
|
rest.onBlur?.(e)
|
||||||
setActiveFacet(null)
|
setActiveFacet(null)
|
||||||
@@ -368,11 +372,9 @@ export function Composer({
|
|||||||
}}
|
}}
|
||||||
onUpdateHeight={updateAutocompletePosition}
|
onUpdateHeight={updateAutocompletePosition}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{children}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{activeFacet && (
|
{activeFacet && activeFacet.type !== 'url' && (
|
||||||
<AutocompleteInner
|
<AutocompleteInner
|
||||||
sift={sift}
|
sift={sift}
|
||||||
activeFacet={activeFacet}
|
activeFacet={activeFacet}
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ import {atoms as a, extractPadding, useAlf, web} from '#/alf'
|
|||||||
import {normalizeTextStyles} from '#/alf/typography'
|
import {normalizeTextStyles} from '#/alf/typography'
|
||||||
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
||||||
|
|
||||||
|
export type AutosizedTextareaProps = Omit<TextInputProps, 'multiline'> & {
|
||||||
|
ref?: React.Ref<TextInput>
|
||||||
|
label: string
|
||||||
|
minRows?: number
|
||||||
|
maxRows?: number
|
||||||
|
onUpdateHeight?: (height: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
export function AutosizedTextarea({
|
export function AutosizedTextarea({
|
||||||
ref,
|
ref,
|
||||||
label,
|
label,
|
||||||
@@ -21,13 +29,7 @@ export function AutosizedTextarea({
|
|||||||
onContentSizeChange: onContentSizeChangeOuter,
|
onContentSizeChange: onContentSizeChangeOuter,
|
||||||
style: outerStyle,
|
style: outerStyle,
|
||||||
...rest
|
...rest
|
||||||
}: Omit<TextInputProps, 'multiline'> & {
|
}: AutosizedTextareaProps) {
|
||||||
ref?: React.Ref<TextInput>
|
|
||||||
label: string
|
|
||||||
minRows?: number
|
|
||||||
maxRows?: number
|
|
||||||
onUpdateHeight?: (height: number) => void
|
|
||||||
}) {
|
|
||||||
const {theme: t, fonts} = useAlf()
|
const {theme: t, fonts} = useAlf()
|
||||||
const internalRef = useRef<TextInput>(null)
|
const internalRef = useRef<TextInput>(null)
|
||||||
const {style, minInputHeight, maxInputHeight, verticalContentPadding} =
|
const {style, minInputHeight, maxInputHeight, verticalContentPadding} =
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {useInteractionState} from '#/components/hooks/useInteractionState'
|
|||||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
||||||
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
|
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {IS_ANDROID, IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
|
||||||
export function MessageComposer({
|
export function MessageComposer({
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
@@ -107,25 +107,84 @@ export function MessageComposer({
|
|||||||
<>
|
<>
|
||||||
<View style={[a.px_md, a.pb_sm, a.pt_xs]}>
|
<View style={[a.px_md, a.pb_sm, a.pt_xs]}>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
ref={node => {
|
||||||
|
if (!IS_WEB && node) {
|
||||||
|
composerInternalApiRef.current?.setAnchorRef(node)
|
||||||
|
}
|
||||||
|
}}
|
||||||
// @ts-expect-error web only
|
// @ts-expect-error web only
|
||||||
onMouseEnter={onHoverIn}
|
onMouseEnter={onHoverIn}
|
||||||
onMouseLeave={onHoverOut}>
|
onMouseLeave={onHoverOut}
|
||||||
|
style={[a.w_full, a.flex_row, a.gap_sm]}>
|
||||||
|
{IS_WEB && (
|
||||||
|
<Pressable
|
||||||
|
onPress={e => {
|
||||||
|
e.currentTarget.measure((_fx, _fy, _width, _height, px, py) => {
|
||||||
|
openEmojiPicker?.({
|
||||||
|
top: py,
|
||||||
|
left: px,
|
||||||
|
right: px,
|
||||||
|
bottom: py,
|
||||||
|
nextFocusRef: {
|
||||||
|
current: composerInternalApiRef.current?.input?.element,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
style={[
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_full,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
a.z_30,
|
||||||
|
{
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
top: 8,
|
||||||
|
left: 8,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
accessibilityLabel={l`Open emoji picker`}
|
||||||
|
accessibilityHint="">
|
||||||
|
{state => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
state.hovered || state.focused || state.pressed
|
||||||
|
? t.atoms.bg.backgroundColor
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<EmojiSmile size="lg" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
|
||||||
<Composer
|
<Composer
|
||||||
internalApiRef={composerInternalApiRef}
|
|
||||||
initialText={text}
|
|
||||||
onChange={setText}
|
|
||||||
editable={editable}
|
|
||||||
autoFocus={IS_WEB}
|
|
||||||
label={l`Message input field`}
|
label={l`Message input field`}
|
||||||
placeholder={l`Write a message`}
|
placeholder={l`Write a message`}
|
||||||
maxNumberOfLines={12}
|
autocompletePlacement="top-start"
|
||||||
style={[
|
internalApiRef={composerInternalApiRef}
|
||||||
|
defaultValue={text}
|
||||||
|
editable={editable}
|
||||||
|
autoFocus={IS_WEB}
|
||||||
|
maxRows={12}
|
||||||
|
outerStyle={[
|
||||||
|
a.flex_1,
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
{
|
{
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
borderRadius: 25,
|
borderRadius: 22,
|
||||||
},
|
},
|
||||||
editable &&
|
editable &&
|
||||||
hovered && {
|
hovered && {
|
||||||
@@ -136,15 +195,16 @@ export function MessageComposer({
|
|||||||
borderColor: t.palette.primary_500,
|
borderColor: t.palette.primary_500,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
padding={{
|
contentTextStyle={[a.text_md, a.leading_snug]}
|
||||||
paddingLeft: IS_WEB ? 30 + 8 : 16,
|
contentPaddingStyle={{
|
||||||
|
paddingLeft: IS_WEB ? 30 + 12 : 12,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
paddingBottom: 12,
|
paddingBottom: 12,
|
||||||
paddingRight: 35 + a.p_sm.padding,
|
paddingRight: 12,
|
||||||
}}
|
}}
|
||||||
textStyle={[a.text_md, a.leading_snug]}
|
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
onChange={setText}
|
||||||
onFacetCommitted={facet => {
|
onFacetCommitted={facet => {
|
||||||
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
|
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
|
||||||
setEmbed(facet.value)
|
setEmbed(facet.value)
|
||||||
@@ -154,77 +214,25 @@ export function MessageComposer({
|
|||||||
if (req.platform === 'web' && req.shiftKey) return
|
if (req.platform === 'web' && req.shiftKey) return
|
||||||
req.nativeEvent.preventDefault()
|
req.nativeEvent.preventDefault()
|
||||||
onSubmit()
|
onSubmit()
|
||||||
}}>
|
}}
|
||||||
{IS_WEB && (
|
/>
|
||||||
<Pressable
|
|
||||||
onPress={e => {
|
{focused || text.length ? (
|
||||||
e.currentTarget.measure(
|
|
||||||
(_fx, _fy, _width, _height, px, py) => {
|
|
||||||
openEmojiPicker?.({
|
|
||||||
top: py,
|
|
||||||
left: px,
|
|
||||||
right: px,
|
|
||||||
bottom: py,
|
|
||||||
nextFocusRef: {
|
|
||||||
current:
|
|
||||||
composerInternalApiRef.current?.input?.element,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
style={[
|
|
||||||
a.overflow_hidden,
|
|
||||||
a.absolute,
|
|
||||||
a.rounded_full,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
a.z_30,
|
|
||||||
{
|
|
||||||
height: 30,
|
|
||||||
width: 30,
|
|
||||||
top: 7,
|
|
||||||
left: 7,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
accessibilityLabel={l`Open emoji picker`}
|
|
||||||
accessibilityHint="">
|
|
||||||
{state => (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.absolute,
|
|
||||||
a.inset_0,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{
|
|
||||||
backgroundColor:
|
|
||||||
state.hovered || state.focused || state.pressed
|
|
||||||
? t.atoms.bg.backgroundColor
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<EmojiSmile size="lg" />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
)}
|
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={l`Send message`}
|
accessibilityLabel={l`Send message`}
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
hitSlop={HITSLOP_10}
|
hitSlop={HITSLOP_10}
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
|
||||||
a.rounded_full,
|
a.rounded_full,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
|
a.self_end,
|
||||||
a.z_30,
|
a.z_30,
|
||||||
{
|
{
|
||||||
height: 35,
|
height: 44,
|
||||||
width: 35,
|
width: 44,
|
||||||
backgroundColor: t.palette.primary_500,
|
backgroundColor: t.palette.primary_500,
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onPress={onSubmit}
|
onPress={onSubmit}
|
||||||
@@ -234,7 +242,7 @@ export function MessageComposer({
|
|||||||
style={[a.relative, {left: 1}]}
|
style={[a.relative, {left: 1}]}
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</Composer>
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -2413,15 +2413,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@bsky.app/react-native-mmkv/-/react-native-mmkv-2.12.5.tgz#eb17d31a6158c74393f617a1763ac223ff3f83a6"
|
resolved "https://registry.yarnpkg.com/@bsky.app/react-native-mmkv/-/react-native-mmkv-2.12.5.tgz#eb17d31a6158c74393f617a1763ac223ff3f83a6"
|
||||||
integrity sha512-3vUz1nQY1DiKIPAWRkpp5ZGxH5f2G6Ui0UuQuEYjYv81xx1qFcSzS9KQ2sHcOKYdkOM9amWV2Q8TQCxt1lrAHg==
|
integrity sha512-3vUz1nQY1DiKIPAWRkpp5ZGxH5f2G6Ui0UuQuEYjYv81xx1qFcSzS9KQ2sHcOKYdkOM9amWV2Q8TQCxt1lrAHg==
|
||||||
|
|
||||||
"@bsky.app/sift@^0.2.6":
|
"@bsky.app/sift@^0.2.8":
|
||||||
version "0.2.6"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/@bsky.app/sift/-/sift-0.2.6.tgz#9ceeebd67e5cda693fbac666c424b2025b01d366"
|
resolved "https://registry.yarnpkg.com/@bsky.app/sift/-/sift-0.2.8.tgz#58cb8ed82e6402061a1e2315a03e1aaec66e6718"
|
||||||
integrity sha512-AC6YXP24tusGtHXYT5rEcRVJkW5zAe9GksR4agi5XoCqcZ+Z2jf8QTPbU6wFQxybjmKUxW36WLF7uMMGo4+PaQ==
|
integrity sha512-CVBo0fp8kpSPhiGYO1H0J8aaEREQ87fzoXdL2qaH64GgX+tqFJT9BlS4EvEyhJoyMHdRWinGgTTubf4Gkvcg/Q==
|
||||||
|
|
||||||
"@bsky.app/tapper@^0.4.1":
|
"@bsky.app/tapper@^0.4.2":
|
||||||
version "0.4.1"
|
version "0.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@bsky.app/tapper/-/tapper-0.4.1.tgz#f8f68f411fbd5ce421cf482a03078b18ac23ecac"
|
resolved "https://registry.yarnpkg.com/@bsky.app/tapper/-/tapper-0.4.2.tgz#a01355f87bb17c4767cb66930a9d1e65904b298a"
|
||||||
integrity sha512-1ET0T34VKkKmaKj6WY6Gnmx0UC7FByPwGZ2SiLJUwJP6Ott5gBfk9xhaag3pdf3OZsZ3O1uIs99J7pdGcQVrew==
|
integrity sha512-64+EI5zzIFAMnLFlHFRuS0L26ANCLnMQS1Q4GszOo6/vhWko6opQA0yYowizEFHCQ73uiquaup2Oc8m7pDSxTA==
|
||||||
|
|
||||||
"@crowdin/cli@^4.14.1":
|
"@crowdin/cli@^4.14.1":
|
||||||
version "4.14.1"
|
version "4.14.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user