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:
Samuel Newman
2024-12-18 16:58:20 +00:00
committed by GitHub
parent 3262b83420
commit 2d827430ee
3 changed files with 28 additions and 28 deletions
+8 -11
View File
@@ -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>) {
+19 -16
View File
@@ -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
+1 -1
View File
@@ -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>