[Layout] Bleed profile banner into safe area (#6967)
* bleed profile banner into safe area (cherry picked from commit 50b3a4d0c6fd94b583ffe4efa65de35c81ae7f4e) * pointer events none when hidden (cherry picked from commit bae2c7b2dd6d7f858a98812196628308c0877755) * fix web (cherry picked from commit e3f9597170375f2903b6e567b963f008ec95aed1) * add status bar shadow * rm log * rm mini header * speed up animation * pass bool rather than int in light status bar
This commit is contained in:
@@ -1,40 +1,48 @@
|
||||
import React, {useContext} from 'react'
|
||||
import {SharedValue} from 'react-native-reanimated'
|
||||
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {isNative} from '#/platform/detection'
|
||||
|
||||
export const PagerHeaderContext =
|
||||
React.createContext<SharedValue<number> | null>(null)
|
||||
export const PagerHeaderContext = React.createContext<{
|
||||
scrollY: SharedValue<number>
|
||||
headerHeight: number
|
||||
} | null>(null)
|
||||
|
||||
/**
|
||||
* Passes the scrollY value to the pager header's banner, so it can grow on
|
||||
* overscroll on iOS. Not necessary to use this context provider on other platforms.
|
||||
* Passes information about the scroll position and header height down via
|
||||
* context for the pager header to consume.
|
||||
*
|
||||
* @platform ios
|
||||
* @platform ios, android
|
||||
*/
|
||||
export function PagerHeaderProvider({
|
||||
scrollY,
|
||||
headerHeight,
|
||||
children,
|
||||
}: {
|
||||
scrollY: SharedValue<number>
|
||||
headerHeight: number
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const value = React.useMemo(
|
||||
() => ({scrollY, headerHeight}),
|
||||
[scrollY, headerHeight],
|
||||
)
|
||||
return (
|
||||
<PagerHeaderContext.Provider value={scrollY}>
|
||||
<PagerHeaderContext.Provider value={value}>
|
||||
{children}
|
||||
</PagerHeaderContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function usePagerHeaderContext() {
|
||||
const scrollY = useContext(PagerHeaderContext)
|
||||
if (isIOS) {
|
||||
if (!scrollY) {
|
||||
const ctx = useContext(PagerHeaderContext)
|
||||
if (isNative) {
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
'usePagerHeaderContext must be used within a HeaderProvider',
|
||||
)
|
||||
}
|
||||
return {scrollY}
|
||||
return ctx
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -38,7 +38,11 @@ export interface PagerWithHeaderProps {
|
||||
| ((props: PagerWithHeaderChildParams) => JSX.Element)
|
||||
items: string[]
|
||||
isHeaderReady: boolean
|
||||
renderHeader?: () => JSX.Element
|
||||
renderHeader?: ({
|
||||
setMinimumHeight,
|
||||
}: {
|
||||
setMinimumHeight: (height: number) => void
|
||||
}) => JSX.Element
|
||||
initialPage?: number
|
||||
onPageSelected?: (index: number) => void
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
@@ -83,7 +87,9 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
const renderTabBar = React.useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
return (
|
||||
<PagerHeaderProvider scrollY={scrollY}>
|
||||
<PagerHeaderProvider
|
||||
scrollY={scrollY}
|
||||
headerHeight={headerOnlyHeight}>
|
||||
<PagerTabBar
|
||||
headerOnlyHeight={headerOnlyHeight}
|
||||
items={items}
|
||||
@@ -237,7 +243,11 @@ let PagerTabBar = ({
|
||||
items: string[]
|
||||
testID?: string
|
||||
scrollY: SharedValue<number>
|
||||
renderHeader?: () => JSX.Element
|
||||
renderHeader?: ({
|
||||
setMinimumHeight,
|
||||
}: {
|
||||
setMinimumHeight: (height: number) => void
|
||||
}) => JSX.Element
|
||||
onHeaderOnlyLayout: (height: number) => void
|
||||
onTabBarLayout: (e: LayoutChangeEvent) => void
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
@@ -246,8 +256,13 @@ let PagerTabBar = ({
|
||||
dragProgress: SharedValue<number>
|
||||
dragState: SharedValue<'idle' | 'dragging' | 'settling'>
|
||||
}): React.ReactNode => {
|
||||
const [minimumHeaderHeight, setMinimumHeaderHeight] = React.useState(0)
|
||||
const headerTransform = useAnimatedStyle(() => {
|
||||
const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1
|
||||
const translateY =
|
||||
Math.min(
|
||||
scrollY.get(),
|
||||
Math.max(headerOnlyHeight - minimumHeaderHeight, 0),
|
||||
) * -1
|
||||
return {
|
||||
transform: [
|
||||
{
|
||||
@@ -267,7 +282,7 @@ let PagerTabBar = ({
|
||||
ref={headerRef}
|
||||
pointerEvents={isIOS ? 'auto' : 'box-none'}
|
||||
collapsable={false}>
|
||||
{renderHeader?.()}
|
||||
{renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
|
||||
{
|
||||
// It wouldn't be enough to place `onLayout` on the parent node because
|
||||
// this would risk measuring before `isHeaderReady` has turned `true`.
|
||||
|
||||
@@ -21,7 +21,11 @@ export interface PagerWithHeaderProps {
|
||||
| ((props: PagerWithHeaderChildParams) => JSX.Element)
|
||||
items: string[]
|
||||
isHeaderReady: boolean
|
||||
renderHeader?: () => JSX.Element
|
||||
renderHeader?: ({
|
||||
setMinimumHeight,
|
||||
}: {
|
||||
setMinimumHeight: () => void
|
||||
}) => JSX.Element
|
||||
initialPage?: number
|
||||
onPageSelected?: (index: number) => void
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
@@ -115,7 +119,11 @@ let PagerTabBar = ({
|
||||
currentPage: number
|
||||
items: string[]
|
||||
testID?: string
|
||||
renderHeader?: () => JSX.Element
|
||||
renderHeader?: ({
|
||||
setMinimumHeight,
|
||||
}: {
|
||||
setMinimumHeight: () => void
|
||||
}) => JSX.Element
|
||||
isHeaderReady: boolean
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
onSelect?: (index: number) => void
|
||||
@@ -123,7 +131,7 @@ let PagerTabBar = ({
|
||||
}): React.ReactNode => {
|
||||
return (
|
||||
<>
|
||||
<Layout.Center>{renderHeader?.()}</Layout.Center>
|
||||
<Layout.Center>{renderHeader?.({setMinimumHeight: noop})}</Layout.Center>
|
||||
{tabBarAnchor}
|
||||
<Layout.Center
|
||||
style={web([
|
||||
@@ -175,3 +183,5 @@ function toArray<T>(v: T | T[]): T[] {
|
||||
}
|
||||
return [v]
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
Reference in New Issue
Block a user