[Chat] New post attachment style in composer (#10563)
This commit is contained in:
@@ -145,8 +145,6 @@ export function MessageComposer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ComposerContainer>
|
<ComposerContainer>
|
||||||
{children}
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
ref={native(
|
ref={native(
|
||||||
@@ -162,80 +160,83 @@ export function MessageComposer({
|
|||||||
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
|
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
|
||||||
tintColor={t.palette.contrast_50}
|
tintColor={t.palette.contrast_50}
|
||||||
fallbackStyle={[t.atoms.bg_contrast_50]}>
|
fallbackStyle={[t.atoms.bg_contrast_50]}>
|
||||||
{IS_WEB && (
|
{children}
|
||||||
<EmojiPicker.Root
|
<View style={[a.flex_1]}>
|
||||||
onEmojiSelect={emoji =>
|
{IS_WEB && (
|
||||||
composerInternalApiRef.current?.insert(emoji.native)
|
<EmojiPicker.Root
|
||||||
}
|
onEmojiSelect={emoji =>
|
||||||
nextFocusRef={() =>
|
composerInternalApiRef.current?.insert(emoji.native)
|
||||||
composerInternalApiRef.current?.input?.element
|
}
|
||||||
}>
|
nextFocusRef={() =>
|
||||||
<EmojiPicker.Trigger label={l`Open emoji picker`}>
|
composerInternalApiRef.current?.input?.element
|
||||||
{({props, state, control}) => (
|
}>
|
||||||
<Pressable
|
<EmojiPicker.Trigger label={l`Open emoji picker`}>
|
||||||
{...props}
|
{({props, state, control}) => (
|
||||||
style={[
|
<Pressable
|
||||||
a.overflow_hidden,
|
{...props}
|
||||||
a.absolute,
|
style={[
|
||||||
a.rounded_full,
|
a.overflow_hidden,
|
||||||
a.align_center,
|
a.absolute,
|
||||||
a.justify_center,
|
a.rounded_full,
|
||||||
a.z_30,
|
a.align_center,
|
||||||
{
|
a.justify_center,
|
||||||
height: 20,
|
a.z_30,
|
||||||
width: 20,
|
{
|
||||||
top: 10,
|
height: 20,
|
||||||
right: 10,
|
width: 20,
|
||||||
},
|
top: 10,
|
||||||
]}>
|
right: 10,
|
||||||
<EmojiSmileIcon
|
},
|
||||||
size="md"
|
]}>
|
||||||
style={
|
<EmojiSmileIcon
|
||||||
state.hovered ||
|
size="md"
|
||||||
state.focused ||
|
style={
|
||||||
state.pressed ||
|
state.hovered ||
|
||||||
control.isOpen
|
state.focused ||
|
||||||
? {color: t.palette.primary_500}
|
state.pressed ||
|
||||||
: t.atoms.text_contrast_high
|
control.isOpen
|
||||||
}
|
? {color: t.palette.primary_500}
|
||||||
/>
|
: t.atoms.text_contrast_high
|
||||||
</Pressable>
|
}
|
||||||
)}
|
/>
|
||||||
</EmojiPicker.Trigger>
|
</Pressable>
|
||||||
<EmojiPicker.Picker />
|
)}
|
||||||
</EmojiPicker.Root>
|
</EmojiPicker.Trigger>
|
||||||
)}
|
<EmojiPicker.Picker />
|
||||||
|
</EmojiPicker.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
<Composer
|
<Composer
|
||||||
nativeID={textInputId}
|
nativeID={textInputId}
|
||||||
label={l`Message input field`}
|
label={l`Message input field`}
|
||||||
placeholder={l`Message`}
|
placeholder={l`Message`}
|
||||||
autocompletePlacement="top-start"
|
autocompletePlacement="top-start"
|
||||||
internalApiRef={composerInternalApiRef}
|
internalApiRef={composerInternalApiRef}
|
||||||
defaultValue={text}
|
defaultValue={text}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
autoFocus={IS_WEB}
|
autoFocus={IS_WEB}
|
||||||
maxRows={12}
|
maxRows={12}
|
||||||
outerStyle={[a.flex_1]}
|
outerStyle={[a.flex_1]}
|
||||||
contentTextStyle={[a.text_md, a.leading_snug]}
|
contentTextStyle={[a.text_md, a.leading_snug]}
|
||||||
contentPaddingStyle={{
|
contentPaddingStyle={{
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
paddingRight: 16 + platform({web: 20, default: 0}),
|
paddingRight: 16 + platform({web: 20, default: 0}),
|
||||||
}}
|
}}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onFacetCommitted={facet => {
|
onFacetCommitted={facet => {
|
||||||
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
|
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
|
||||||
setEmbed(facet.value)
|
setEmbed(facet.value)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onRequestSubmit={req => {
|
onRequestSubmit={req => {
|
||||||
if (req.platform === 'web' && req.shiftKey) return
|
if (req.platform === 'web' && req.shiftKey) return
|
||||||
req.nativeEvent.preventDefault()
|
req.nativeEvent.preventDefault()
|
||||||
handleSubmit()
|
handleSubmit()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
</GlassView>
|
</GlassView>
|
||||||
<SubmitButton onPress={handleSubmit} disabled={submitDisabled} />
|
<SubmitButton onPress={handleSubmit} disabled={submitDisabled} />
|
||||||
</GlassContainer>
|
</GlassContainer>
|
||||||
|
|||||||
@@ -158,7 +158,6 @@ export function MessageInput({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ComposerContainer>
|
<ComposerContainer>
|
||||||
{children}
|
|
||||||
<GlassContainer
|
<GlassContainer
|
||||||
style={[a.flex_row, a.align_end, a.gap_sm]}
|
style={[a.flex_row, a.align_end, a.gap_sm]}
|
||||||
spacing={tokens.space.xs}>
|
spacing={tokens.space.xs}>
|
||||||
@@ -168,6 +167,7 @@ export function MessageInput({
|
|||||||
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
|
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
|
||||||
tintColor={t.palette.contrast_50}
|
tintColor={t.palette.contrast_50}
|
||||||
fallbackStyle={[t.atoms.bg_contrast_50]}>
|
fallbackStyle={[t.atoms.bg_contrast_50]}>
|
||||||
|
{children}
|
||||||
<AnimatedTextInput
|
<AnimatedTextInput
|
||||||
nativeID={textInputId}
|
nativeID={textInputId}
|
||||||
accessibilityLabel={l`Message input field`}
|
accessibilityLabel={l`Message input field`}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import {
|
|||||||
moderatePost,
|
moderatePost,
|
||||||
RichText as RichTextAPI,
|
RichText as RichTextAPI,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
|
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {HITSLOP_20} from '#/lib/constants'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {
|
import {
|
||||||
type CommonNavigatorParams,
|
type CommonNavigatorParams,
|
||||||
@@ -25,8 +25,8 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
|||||||
import {usePostQuery} from '#/state/queries/post'
|
import {usePostQuery} from '#/state/queries/post'
|
||||||
import {PostMeta} from '#/view/com/util/PostMeta'
|
import {PostMeta} from '#/view/com/util/PostMeta'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as MediaPreview from '#/components/MediaPreview'
|
import * as MediaPreview from '#/components/MediaPreview'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
@@ -41,10 +41,10 @@ export function useMessageEmbed() {
|
|||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const embedFromParams = route.params.embed
|
const embedFromParams = route.params.embed
|
||||||
|
|
||||||
const [embedUri, setEmbed] = useState(embedFromParams)
|
const [embedUri, setEmbedUri] = useState(embedFromParams)
|
||||||
|
|
||||||
if (embedFromParams && embedUri !== embedFromParams) {
|
if (embedFromParams && embedUri !== embedFromParams) {
|
||||||
setEmbed(embedFromParams)
|
setEmbedUri(embedFromParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -53,7 +53,7 @@ export function useMessageEmbed() {
|
|||||||
(embedUrl: string | undefined) => {
|
(embedUrl: string | undefined) => {
|
||||||
if (!embedUrl) {
|
if (!embedUrl) {
|
||||||
navigation.setParams({embed: ''})
|
navigation.setParams({embed: ''})
|
||||||
setEmbed(undefined)
|
setEmbedUri(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ export function useMessageEmbed() {
|
|||||||
const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
|
const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
|
||||||
const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
|
const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
|
||||||
|
|
||||||
setEmbed(uri)
|
setEmbedUri(uri)
|
||||||
},
|
},
|
||||||
[embedFromParams, navigation],
|
[embedFromParams, navigation],
|
||||||
),
|
),
|
||||||
@@ -103,7 +103,7 @@ export function MessageInputEmbed({
|
|||||||
setEmbed: (embedUrl: string | undefined) => void
|
setEmbed: (embedUrl: string | undefined) => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
const {data: post, status} = usePostQuery(embedUri)
|
const {data: post, status} = usePostQuery(embedUri)
|
||||||
|
|
||||||
@@ -138,25 +138,29 @@ export function MessageInputEmbed({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = null
|
const onRemove = () => {
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
|
setEmbed(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending': {
|
||||||
content = (
|
return (
|
||||||
<View
|
<SimpleContainer onRemove={onRemove}>
|
||||||
style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}>
|
|
||||||
<Loader />
|
<Loader />
|
||||||
</View>
|
</SimpleContainer>
|
||||||
)
|
)
|
||||||
break
|
}
|
||||||
case 'error':
|
case 'error': {
|
||||||
content = (
|
return (
|
||||||
<View
|
<SimpleContainer onRemove={onRemove}>
|
||||||
style={[a.flex_1, {minHeight: 64}, a.justify_center, a.align_center]}>
|
<Text style={[a.text_center, t.atoms.text_contrast_medium, a.italic]}>
|
||||||
<Text style={a.text_center}>Could not fetch post</Text>
|
<Trans>Could not fetch post</Trans>
|
||||||
</View>
|
</Text>
|
||||||
|
</SimpleContainer>
|
||||||
)
|
)
|
||||||
break
|
}
|
||||||
case 'success':
|
case 'success': {
|
||||||
const itemUrip = new AtUri(post.uri)
|
const itemUrip = new AtUri(post.uri)
|
||||||
const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
|
const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
|
||||||
|
|
||||||
@@ -164,62 +168,95 @@ export function MessageInputEmbed({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
content = (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
t.atoms.bg,
|
t.atoms.border_contrast_high,
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
a.rounded_md,
|
a.rounded_md,
|
||||||
a.border,
|
a.border,
|
||||||
a.p_sm,
|
a.p_sm,
|
||||||
a.mb_sm,
|
a.mt_sm,
|
||||||
]}
|
a.mx_sm,
|
||||||
pointerEvents="none">
|
]}>
|
||||||
<PostMeta
|
<View style={[a.flex_1, a.flex_row, a.gap_sm]}>
|
||||||
showAvatar
|
<PostMeta
|
||||||
author={post.author}
|
showAvatar
|
||||||
moderation={moderation}
|
author={post.author}
|
||||||
timestamp={post.indexedAt}
|
moderation={moderation}
|
||||||
postHref={itemHref}
|
timestamp={post.indexedAt}
|
||||||
style={a.flex_0}
|
postHref={itemHref}
|
||||||
/>
|
linkDisabled
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={l`Remove embed`}
|
||||||
|
onPress={onRemove}
|
||||||
|
style={[a.px_2xs, {transform: [{translateY: -2}]}]}
|
||||||
|
hitSlop={HITSLOP_20}>
|
||||||
|
<XIcon size="xs" style={t.atoms.text_contrast_high} />
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
<ContentHider modui={moderation.ui('contentView')}>
|
<ContentHider modui={moderation.ui('contentView')}>
|
||||||
<PostAlerts modui={moderation.ui('contentView')} style={a.py_xs} />
|
<PostAlerts
|
||||||
|
modui={moderation.ui('contentView')}
|
||||||
|
style={a.py_xs}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
{rt.text && (
|
{rt.text && (
|
||||||
<View style={a.mt_xs}>
|
<RichText
|
||||||
<RichText
|
enableTags
|
||||||
enableTags
|
testID="postText"
|
||||||
testID="postText"
|
value={rt}
|
||||||
value={rt}
|
style={[a.text_sm, t.atoms.text_contrast_high]}
|
||||||
style={[a.text_sm, t.atoms.text_contrast_high]}
|
authorHandle={post.author.handle}
|
||||||
authorHandle={post.author.handle}
|
numberOfLines={3}
|
||||||
numberOfLines={3}
|
/>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
<MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
|
<MediaPreview.Embed embed={post.embed} style={a.mt_sm} />
|
||||||
</ContentHider>
|
</ContentHider>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SimpleContainer({
|
||||||
|
children,
|
||||||
|
onRemove,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
onRemove?: () => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {t: l} = useLingui()
|
||||||
return (
|
return (
|
||||||
<View style={[a.flex_row, a.gap_sm]}>
|
<View
|
||||||
{content}
|
style={[
|
||||||
<Button
|
a.flex_1,
|
||||||
label={_(msg`Remove embed`)}
|
{minHeight: 80},
|
||||||
onPress={() => {
|
a.justify_center,
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
a.align_center,
|
||||||
setEmbed(undefined)
|
t.atoms.border_contrast_high,
|
||||||
}}
|
a.rounded_md,
|
||||||
size="tiny"
|
a.border,
|
||||||
variant="solid"
|
a.mt_sm,
|
||||||
color="secondary"
|
a.mx_sm,
|
||||||
shape="round">
|
]}>
|
||||||
<ButtonIcon icon={X} />
|
{children}
|
||||||
</Button>
|
{onRemove && (
|
||||||
|
<Button
|
||||||
|
label={l`Remove embed`}
|
||||||
|
onPress={onRemove}
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
{top: 10, right: 8},
|
||||||
|
a.px_2xs,
|
||||||
|
{transform: [{translateY: -2}]},
|
||||||
|
]}
|
||||||
|
hitSlop={HITSLOP_20}>
|
||||||
|
<XIcon size="xs" style={t.atoms.text_contrast_high} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user