Make optimistic deleted state not look like crap in tree view

This commit is contained in:
Eric Bailey
2025-06-04 19:47:34 -05:00
parent de2bcaac2e
commit 25ee588e27
+146 -90
View File
@@ -66,7 +66,7 @@ export function ThreadReply({
const postShadow = usePostShadow(item.value.post) const postShadow = usePostShadow(item.value.post)
if (postShadow === POST_TOMBSTONE) { if (postShadow === POST_TOMBSTONE) {
return <PostThreadItemDeleted /> return <ThreadItemTreeReplyDeleted item={item} />
} }
return ( return (
@@ -82,27 +82,158 @@ export function ThreadReply({
) )
} }
function PostThreadItemDeleted({hideTopBorder}: {hideTopBorder?: boolean}) { function ThreadItemTreeReplyDeleted({
item,
}: {
item: Extract<ThreadItem, {type: 'threadPost'}>
}) {
const t = useTheme() const t = useTheme()
return ( return (
<ThreadItemTreeReplyOuter item={item}>
<ThreadItemTreeReplyInner item={item}>
<View <View
style={[ style={[
t.atoms.bg,
t.atoms.border_contrast_low,
a.p_xl,
a.pl_lg,
a.flex_row, a.flex_row,
a.gap_md, a.align_center,
!hideTopBorder && a.border_t, a.rounded_sm,
t.atoms.bg_contrast_25,
{
gap: 6,
paddingHorizontal: OUTER_SPACE / 2,
height: TREE_AVI_WIDTH,
},
]}> ]}>
<TrashIcon style={[t.atoms.text]} /> <TrashIcon style={[t.atoms.text]} width={14} />
<Text style={[t.atoms.text_contrast_medium, a.mt_2xs]}> <Text style={[t.atoms.text_contrast_medium, a.mt_2xs]}>
<Trans>This post has been deleted.</Trans> <Trans>This post has been deleted.</Trans>
</Text> </Text>
</View> </View>
{item.ui.isLastChild && !item.ui.precedesChildReadMore && (
<View style={{height: OUTER_SPACE / 2}} />
)}
</ThreadItemTreeReplyInner>
</ThreadItemTreeReplyOuter>
) )
} }
const ThreadItemTreeReplyOuter = memo(function ThreadItemTreeReplyOuter({
item,
children,
}: {
item: Extract<ThreadItem, {type: 'threadPost'}>
children: React.ReactNode
}) {
const t = useTheme()
const indents = Math.max(0, item.ui.indent - 1)
return (
<View
style={[
a.flex_row,
item.ui.indent === 1 &&
!item.ui.showParentReplyLine && [
a.border_t,
t.atoms.border_contrast_low,
],
]}>
{Array.from(Array(indents)).map((_, n: number) => {
const isSkipped = item.ui.skippedIndentIndices.has(n)
return (
<View
key={`${item.value.post.uri}-padding-${n}`}
style={[
t.atoms.border_contrast_low,
{
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
left: 1,
},
]}
/>
)
})}
{children}
</View>
)
})
const ThreadItemTreeReplyInner = memo(function ThreadItemTreeReplyInner({
item,
children,
}: {
item: Extract<ThreadItem, {type: 'threadPost'}>
children: React.ReactNode
}) {
const t = useTheme()
return (
<View
style={[
a.flex_1, // TODO check on ios
{
paddingHorizontal: OUTER_SPACE,
paddingTop: OUTER_SPACE / 2,
},
item.ui.indent === 1 && [
!item.ui.showParentReplyLine && a.pt_lg,
!item.ui.showChildReplyLine && a.pb_sm,
],
item.ui.isLastChild &&
!item.ui.precedesChildReadMore && [
{
paddingBottom: OUTER_SPACE / 2,
},
],
]}>
{item.ui.indent > 1 && (
<View
style={[
a.absolute,
t.atoms.border_contrast_low,
{
left: -1,
top: 0,
height:
TREE_AVI_WIDTH / 2 + REPLY_LINE_WIDTH / 2 + OUTER_SPACE / 2,
width: OUTER_SPACE,
borderLeftWidth: REPLY_LINE_WIDTH,
borderBottomWidth: REPLY_LINE_WIDTH,
borderBottomLeftRadius: a.rounded_sm.borderRadius,
},
]}
/>
)}
{children}
</View>
)
})
const ThreadItemTreeReplyChildReplyLine = memo(
function ThreadItemTreeReplyChildReplyLine({
item,
}: {
item: Extract<ThreadItem, {type: 'threadPost'}>
}) {
const t = useTheme()
return (
<View style={[a.relative, {width: TREE_AVI_PLUS_SPACE}]}>
{item.ui.showChildReplyLine && (
<View
style={[
a.flex_1,
t.atoms.border_contrast_low,
{
borderRightWidth: 2,
width: '50%',
left: -1,
},
]}
/>
)}
</View>
)
},
)
const ThreadReplyInner = memo(function ThreadReplyInner({ const ThreadReplyInner = memo(function ThreadReplyInner({
item, item,
postShadow, postShadow,
@@ -119,7 +250,6 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
onPostSuccess?: (data: OnPostSuccessData) => void onPostSuccess?: (data: OnPostSuccessData) => void
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode { }): React.ReactNode {
const t = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
@@ -180,35 +310,8 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
setLimitLines(false) setLimitLines(false)
}, [setLimitLines]) }, [setLimitLines])
const indents = Math.max(0, item.ui.indent - 1)
return ( return (
<View <ThreadItemTreeReplyOuter item={item}>
style={[
a.flex_row,
item.ui.indent === 1 &&
!item.ui.showParentReplyLine && [
a.border_t,
t.atoms.border_contrast_low,
],
]}>
{Array.from(Array(indents)).map((_, n: number) => {
const isSkipped = item.ui.skippedIndentIndices.has(n)
return (
<View
key={`${post.uri}-padding-${n}`}
style={[
t.atoms.border_contrast_low,
{
borderRightWidth: isSkipped ? 0 : REPLY_LINE_WIDTH,
width: TREE_INDENT + TREE_AVI_WIDTH / 2,
left: 1,
},
]}
/>
)
})}
<View style={a.flex_1}>
<SubtleHover> <SubtleHover>
<PostHider <PostHider
testID={`postThreadItem-by-${post.author.handle}`} testID={`postThreadItem-by-${post.author.handle}`}
@@ -219,39 +322,7 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
iconStyles={{marginLeft: 2, marginRight: 2}} iconStyles={{marginLeft: 2, marginRight: 2}}
profile={post.author} profile={post.author}
interpretFilterAsBlur> interpretFilterAsBlur>
<View <ThreadItemTreeReplyInner item={item}>
style={[
{
paddingHorizontal: OUTER_SPACE,
paddingTop: OUTER_SPACE / 2,
},
item.ui.indent === 1 && [
!item.ui.showParentReplyLine && a.pt_lg,
!item.ui.showChildReplyLine && a.pb_sm,
],
item.ui.isLastChild &&
!item.ui.precedesChildReadMore && [a.pb_sm],
]}>
{item.ui.indent > 1 && (
<View
style={[
a.absolute,
t.atoms.border_contrast_low,
{
left: -1,
top: 0,
height:
TREE_AVI_WIDTH / 2 +
REPLY_LINE_WIDTH / 2 +
OUTER_SPACE / 2,
width: OUTER_SPACE,
borderLeftWidth: REPLY_LINE_WIDTH,
borderBottomWidth: REPLY_LINE_WIDTH,
borderBottomLeftRadius: a.rounded_sm.borderRadius,
},
]}
/>
)}
<View style={[a.flex_row, a.gap_md]}> <View style={[a.flex_row, a.gap_md]}>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
<PostMeta <PostMeta
@@ -264,21 +335,7 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
showAvatar showAvatar
/> />
<View style={[a.flex_row]}> <View style={[a.flex_row]}>
<View style={[a.relative, {width: TREE_AVI_PLUS_SPACE}]}> <ThreadItemTreeReplyChildReplyLine item={item} />
{item.ui.showChildReplyLine && (
<View
style={[
a.flex_1,
t.atoms.border_contrast_low,
{
borderRightWidth: 2,
width: '50%',
left: -1,
},
]}
/>
)}
</View>
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
<LabelsOnMyPost post={post} style={[a.pb_2xs]} /> <LabelsOnMyPost post={post} style={[a.pb_2xs]} />
<PostAlerts <PostAlerts
@@ -329,11 +386,10 @@ const ThreadReplyInner = memo(function ThreadReplyInner({
</View> </View>
</View> </View>
</View> </View>
</View> </ThreadItemTreeReplyInner>
</PostHider> </PostHider>
</SubtleHover> </SubtleHover>
</View> </ThreadItemTreeReplyOuter>
</View>
) )
}) })
@@ -347,7 +403,7 @@ function SubtleHover({children}: {children: React.ReactNode}) {
<View <View
onPointerEnter={onHoverIn} onPointerEnter={onHoverIn}
onPointerLeave={onHoverOut} onPointerLeave={onHoverOut}
style={a.pointer}> style={[a.flex_1, a.pointer]}>
<SubtleWebHover hover={hover} /> <SubtleWebHover hover={hover} />
{children} {children}
</View> </View>