Add basic post and anchor skeletons
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {
|
||||
atoms as a,
|
||||
flatten,
|
||||
type TextStyleProp,
|
||||
useAlf,
|
||||
useTheme,
|
||||
type ViewStyleProp,
|
||||
} from '#/alf'
|
||||
import {normalizeTextStyles} from '#/alf/typography'
|
||||
|
||||
type SkeletonProps = {
|
||||
blend?: boolean
|
||||
}
|
||||
|
||||
export function Text({blend, style}: TextStyleProp & SkeletonProps) {
|
||||
const {fonts, flags, theme: t} = useAlf()
|
||||
const {width, ...flattened} = flatten(style)
|
||||
const {lineHeight = 14, ...rest} = normalizeTextStyles(
|
||||
[a.text_sm, a.leading_snug, flattened],
|
||||
{
|
||||
fontScale: fonts.scaleMultiplier,
|
||||
fontFamily: fonts.family,
|
||||
flags,
|
||||
},
|
||||
)
|
||||
return (
|
||||
<View
|
||||
style={[a.flex_1, {maxWidth: width, paddingVertical: lineHeight * 0.15}]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
height: lineHeight * 0.7,
|
||||
opacity: blend ? 0.6 : 1,
|
||||
},
|
||||
rest,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function Circle({
|
||||
size,
|
||||
blend,
|
||||
style,
|
||||
}: ViewStyleProp & {size: number} & SkeletonProps) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
width: size,
|
||||
height: size,
|
||||
opacity: blend ? 0.6 : 1,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Pill({
|
||||
size,
|
||||
blend,
|
||||
style,
|
||||
}: ViewStyleProp & {size: number} & SkeletonProps) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
width: size * 1.618,
|
||||
height: size,
|
||||
opacity: blend ? 0.6 : 1,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Col({
|
||||
children,
|
||||
style,
|
||||
}: ViewStyleProp & {children?: React.ReactNode}) {
|
||||
return <View style={[a.flex_1, style]}>{children}</View>
|
||||
}
|
||||
|
||||
export function Row({
|
||||
children,
|
||||
style,
|
||||
}: ViewStyleProp & {children?: React.ReactNode}) {
|
||||
return <View style={[a.flex_row, style]}>{children}</View>
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import {type AppModerationCause} from '#/components/Pills'
|
||||
import {PostControls} from '#/components/PostControls'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import * as Skele from '#/components/Skeleton'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
||||
import {WhoCanReply} from '#/components/WhoCanReply'
|
||||
@@ -574,3 +575,33 @@ function getThreadAuthor(
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function ThreadAnchorSkeleton() {
|
||||
return (
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
<Skele.Row style={[a.align_center, a.gap_md]}>
|
||||
<Skele.Circle size={42} />
|
||||
|
||||
<Skele.Col>
|
||||
<Skele.Text style={[a.text_lg, {width: '20%'}]} />
|
||||
<Skele.Text blend style={[a.text_md, {width: '40%'}]} />
|
||||
</Skele.Col>
|
||||
</Skele.Row>
|
||||
|
||||
<View>
|
||||
<Skele.Text style={[a.text_xl, {width: '100%'}]} />
|
||||
<Skele.Text style={[a.text_xl, {width: '60%'}]} />
|
||||
</View>
|
||||
|
||||
<Skele.Text style={[a.text_sm, {width: '50%'}]} />
|
||||
|
||||
<Skele.Row style={[a.justify_between]}>
|
||||
<Skele.Pill blend size={24} />
|
||||
<Skele.Pill blend size={24} />
|
||||
<Skele.Pill blend size={24} />
|
||||
<Skele.Circle blend size={24} />
|
||||
<Skele.Circle blend size={24} />
|
||||
</Skele.Row>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {PostHider} from '#/components/moderation/PostHider'
|
||||
import {type AppModerationCause} from '#/components/Pills'
|
||||
import {PostControls} from '#/components/PostControls'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import * as Skele from '#/components/Skeleton'
|
||||
import {SubtleWebHover} from '#/components/SubtleWebHover'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -320,3 +321,40 @@ function SubtleHover({children}: {children: React.ReactNode}) {
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function ThreadPostSkeleton() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{paddingHorizontal: OUTER_SPACE, paddingVertical: OUTER_SPACE / 1.5},
|
||||
a.gap_md,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<Skele.Row style={[a.align_start, a.gap_md]}>
|
||||
<Skele.Circle size={42} />
|
||||
|
||||
<Skele.Col style={[a.gap_xs]}>
|
||||
<Skele.Row style={[a.gap_sm]}>
|
||||
<Skele.Text style={[a.text_md, {width: '20%'}]} />
|
||||
<Skele.Text blend style={[a.text_md, {width: '30%'}]} />
|
||||
</Skele.Row>
|
||||
|
||||
<Skele.Col>
|
||||
<Skele.Text blend style={[a.text_md, {width: '100%'}]} />
|
||||
<Skele.Text blend style={[a.text_md, {width: '60%'}]} />
|
||||
</Skele.Col>
|
||||
|
||||
<Skele.Row style={[a.justify_between, a.pt_xs]}>
|
||||
<Skele.Pill blend size={16} />
|
||||
<Skele.Pill blend size={16} />
|
||||
<Skele.Pill blend size={16} />
|
||||
<Skele.Circle blend size={16} />
|
||||
<View />
|
||||
</Skele.Row>
|
||||
</Skele.Col>
|
||||
</Skele.Row>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,14 @@ import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShow
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {HeaderDropdown} from '#/screens/PostThread/components/HeaderDropdown'
|
||||
import {ReadMore} from '#/screens/PostThread/components/ReadMore'
|
||||
import {ThreadAnchor} from '#/screens/PostThread/components/ThreadAnchor'
|
||||
import {ThreadPost} from '#/screens/PostThread/components/ThreadPost'
|
||||
import {
|
||||
ThreadAnchor,
|
||||
ThreadAnchorSkeleton,
|
||||
} from '#/screens/PostThread/components/ThreadAnchor'
|
||||
import {
|
||||
ThreadPost,
|
||||
ThreadPostSkeleton,
|
||||
} from '#/screens/PostThread/components/ThreadPost'
|
||||
import {ThreadReply} from '#/screens/PostThread/components/ThreadReply'
|
||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
@@ -305,6 +311,12 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (item.type === 'skeleton') {
|
||||
if (item.item === 'anchor') {
|
||||
return <ThreadAnchorSkeleton />
|
||||
} else if (item.item === 'reply') {
|
||||
return <ThreadPostSkeleton />
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -358,7 +370,7 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
* Using `isFetching` over `isFetchingNextPage` is done on
|
||||
* purpose here so we get the loader on initial render
|
||||
*/
|
||||
isFetchingNextPage={isFetching}
|
||||
// isFetchingNextPage={isFetching}
|
||||
error={cleanError(error)}
|
||||
onRetry={refetch}
|
||||
/*
|
||||
|
||||
@@ -54,7 +54,7 @@ export function usePostThread({
|
||||
if (placeholder) {
|
||||
return {thread: [placeholder], hasHiddenReplies: false}
|
||||
}
|
||||
return
|
||||
return {thread: [], hasHiddenReplies: false}
|
||||
},
|
||||
select(data) {
|
||||
const threadgate = getThreadgateRecord(data.threadgate)
|
||||
@@ -96,6 +96,38 @@ export function usePostThread({
|
||||
queryClient: qc,
|
||||
})
|
||||
|
||||
if (query.isPlaceholderData) {
|
||||
const anchor = items.at(0)
|
||||
const skeletonReplies =
|
||||
anchor && anchor.type === 'threadPost'
|
||||
? anchor?.value.post.replyCount ?? 4
|
||||
: 4
|
||||
|
||||
if (!items.length) {
|
||||
items.push({
|
||||
type: 'skeleton',
|
||||
key: params.anchor!,
|
||||
item: 'anchor',
|
||||
})
|
||||
|
||||
if (hasSession) {
|
||||
items.push({
|
||||
type: 'skeleton',
|
||||
key: 'replyComposer',
|
||||
item: 'replyComposer',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < skeletonReplies; i++) {
|
||||
items.push({
|
||||
type: 'skeleton',
|
||||
key: `${params.anchor!}-reply-${i}`,
|
||||
item: 'reply',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...query,
|
||||
data: {
|
||||
|
||||
@@ -92,6 +92,11 @@ export type ThreadItem =
|
||||
moreReplies: number
|
||||
skippedIndentIndices: Set<number>
|
||||
}
|
||||
| {
|
||||
type: 'skeleton'
|
||||
key: string
|
||||
item: 'anchor' | 'reply' | 'replyComposer'
|
||||
}
|
||||
|
||||
export type TraversalMetadata = {
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user