Web sticky headers for most screens (#7153)
* web sticky headers, with opt-out for notifs * rm from postthread * Fix jump --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
|||||||
useBreakpoints,
|
useBreakpoints,
|
||||||
useGutters,
|
useGutters,
|
||||||
useTheme,
|
useTheme,
|
||||||
|
web,
|
||||||
} from '#/alf'
|
} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonProps} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonProps} from '#/components/Button'
|
||||||
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
|
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
|
||||||
@@ -29,9 +30,13 @@ import {Text} from '#/components/Typography'
|
|||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
children,
|
||||||
noBottomBorder,
|
noBottomBorder,
|
||||||
|
headerRef,
|
||||||
|
sticky = true,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
noBottomBorder?: boolean
|
noBottomBorder?: boolean
|
||||||
|
headerRef?: React.MutableRefObject<View | null>
|
||||||
|
sticky?: boolean
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const gutters = useGutters([0, 'base'])
|
const gutters = useGutters([0, 'base'])
|
||||||
@@ -40,12 +45,14 @@ export function Outer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
ref={headerRef}
|
||||||
style={[
|
style={[
|
||||||
a.w_full,
|
a.w_full,
|
||||||
!noBottomBorder && a.border_b,
|
!noBottomBorder && a.border_b,
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.gap_sm,
|
a.gap_sm,
|
||||||
|
sticky && web([a.sticky, {top: 0}, a.z_10, t.atoms.bg]),
|
||||||
gutters,
|
gutters,
|
||||||
platform({
|
platform({
|
||||||
native: [a.pb_xs, {minHeight: 48}],
|
native: [a.pb_xs, {minHeight: 48}],
|
||||||
@@ -85,17 +92,7 @@ export function Content({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Slot({children}: {children?: React.ReactNode}) {
|
export function Slot({children}: {children?: React.ReactNode}) {
|
||||||
return (
|
return <View style={[a.z_50, {width: HEADER_SLOT_SIZE}]}>{children}</View>
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.z_50,
|
|
||||||
{
|
|
||||||
width: HEADER_SLOT_SIZE,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
|
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell'
|
import {useComposerControls} from '#/state/shell'
|
||||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||||
|
import {List, ListMethods} from '#/view/com/util/List'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Header} from '#/components/Layout'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {List, ListMethods} from '../util/List'
|
|
||||||
import {ViewHeader} from '../util/ViewHeader'
|
|
||||||
import {PostThreadComposePrompt} from './PostThreadComposePrompt'
|
import {PostThreadComposePrompt} from './PostThreadComposePrompt'
|
||||||
import {PostThreadItem} from './PostThreadItem'
|
import {PostThreadItem} from './PostThreadItem'
|
||||||
import {PostThreadLoadMore} from './PostThreadLoadMore'
|
import {PostThreadLoadMore} from './PostThreadLoadMore'
|
||||||
@@ -95,6 +95,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
const [hiddenRepliesState, setHiddenRepliesState] = React.useState(
|
const [hiddenRepliesState, setHiddenRepliesState] = React.useState(
|
||||||
HiddenRepliesState.Hide,
|
HiddenRepliesState.Hide,
|
||||||
)
|
)
|
||||||
|
const headerRef = React.useRef<View | null>(null)
|
||||||
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {
|
const {
|
||||||
@@ -284,18 +285,18 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
}
|
}
|
||||||
// wait for loading to finish
|
// wait for loading to finish
|
||||||
if (thread?.type === 'post' && !!thread.parent) {
|
if (thread?.type === 'post' && !!thread.parent) {
|
||||||
function onMeasure(pageY: number) {
|
// Measure synchronously to avoid a layout jump.
|
||||||
|
const postNode = highlightedPostRef.current
|
||||||
|
const headerNode = headerRef.current
|
||||||
|
if (postNode && headerNode) {
|
||||||
|
let pageY = (postNode as any as Element).getBoundingClientRect().top
|
||||||
|
pageY -= (headerNode as any as Element).getBoundingClientRect().height
|
||||||
|
pageY = Math.max(0, pageY)
|
||||||
ref.current?.scrollToOffset({
|
ref.current?.scrollToOffset({
|
||||||
animated: false,
|
animated: false,
|
||||||
offset: pageY,
|
offset: pageY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Measure synchronously to avoid a layout jump.
|
|
||||||
const domNode = highlightedPostRef.current
|
|
||||||
if (domNode) {
|
|
||||||
const pageY = (domNode as any as Element).getBoundingClientRect().top
|
|
||||||
onMeasure(pageY)
|
|
||||||
}
|
|
||||||
didAdjustScrollWeb.current = true
|
didAdjustScrollWeb.current = true
|
||||||
}
|
}
|
||||||
}, [thread])
|
}, [thread])
|
||||||
@@ -367,7 +368,6 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
skeleton?.highlightedPost?.type === 'post' &&
|
skeleton?.highlightedPost?.type === 'post' &&
|
||||||
(skeleton.highlightedPost.ctx.isParentLoading ||
|
(skeleton.highlightedPost.ctx.isParentLoading ||
|
||||||
Boolean(skeleton?.parents && skeleton.parents.length > 0))
|
Boolean(skeleton?.parents && skeleton.parents.length > 0))
|
||||||
const showHeader = isNative || !hasParents || !isFetching
|
|
||||||
|
|
||||||
const renderItem = ({item, index}: {item: RowItem; index: number}) => {
|
const renderItem = ({item, index}: {item: RowItem; index: number}) => {
|
||||||
if (item === REPLY_PROMPT && hasSession) {
|
if (item === REPLY_PROMPT && hasSession) {
|
||||||
@@ -484,12 +484,15 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showHeader && (
|
<Header.Outer sticky={true} headerRef={headerRef}>
|
||||||
<ViewHeader
|
<Header.BackButton />
|
||||||
title={_(msg({message: `Post`, context: 'description'}))}
|
<Header.Content>
|
||||||
showBorder
|
<Header.TitleText>
|
||||||
/>
|
<Trans context="description">Post</Trans>
|
||||||
)}
|
</Header.TitleText>
|
||||||
|
</Header.Content>
|
||||||
|
<Header.Slot />
|
||||||
|
</Header.Outer>
|
||||||
|
|
||||||
<ScrollProvider onMomentumEnd={onMomentumEnd}>
|
<ScrollProvider onMomentumEnd={onMomentumEnd}>
|
||||||
<List
|
<List
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function NotificationsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="notificationsScreen">
|
<Layout.Screen testID="notificationsScreen">
|
||||||
<Layout.Header.Outer noBottomBorder>
|
<Layout.Header.Outer noBottomBorder sticky={false}>
|
||||||
<Layout.Header.MenuButton />
|
<Layout.Header.MenuButton />
|
||||||
<Layout.Header.Content>
|
<Layout.Header.Content>
|
||||||
<Layout.Header.TitleText>
|
<Layout.Header.TitleText>
|
||||||
|
|||||||
Reference in New Issue
Block a user