|
| 1 | +import { useTypeScriptHappyCallback } from "@follow/hooks/exports" |
| 2 | +import type { NativeStackNavigationOptions } from "@react-navigation/native-stack" |
| 3 | +import { Stack } from "expo-router" |
| 4 | +import type { FC } from "react" |
| 5 | +import { memo } from "react" |
| 6 | +import { StyleSheet, View } from "react-native" |
| 7 | +import type { SharedValue } from "react-native-reanimated" |
| 8 | +import Animated, { useAnimatedStyle } from "react-native-reanimated" |
| 9 | +import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context" |
| 10 | +import { useColor } from "react-native-uikit-colors" |
| 11 | + |
| 12 | +import { HeaderTitleExtra } from "../../common/HeaderTitleExtra" |
| 13 | +import { ModalHeaderCloseButton } from "../../common/ModalSharedComponents" |
| 14 | +import { ThemedBlurView } from "../../common/ThemedBlurView" |
| 15 | +import { useModalScrollViewContext } from "../contexts/ModalScrollViewContext" |
| 16 | +import { getDefaultHeaderHeight } from "../utils" |
| 17 | + |
| 18 | +interface ModalHeaderProps |
| 19 | + extends Omit<NativeStackNavigationOptions, "headerLeft" | "headerRight" | "headerTitle"> { |
| 20 | + headerLeft?: React.ReactNode |
| 21 | + headerRight?: React.ReactNode |
| 22 | + headerTitle?: string |
| 23 | + headerSubtitle?: string |
| 24 | +} |
| 25 | + |
| 26 | +export const ModalHeader: FC<ModalHeaderProps> = (props) => { |
| 27 | + const { animatedY } = useModalScrollViewContext() |
| 28 | + |
| 29 | + return ( |
| 30 | + <Stack.Screen |
| 31 | + options={{ |
| 32 | + ...props, |
| 33 | + headerTransparent: true, |
| 34 | + headerShown: true, |
| 35 | + headerLeft: () => props.headerLeft ?? <ModalHeaderCloseButton />, |
| 36 | + headerRight: () => props.headerRight, |
| 37 | + |
| 38 | + header: useTypeScriptHappyCallback( |
| 39 | + ({ options }) => ( |
| 40 | + <Header |
| 41 | + headerTitle={props.headerTitle} |
| 42 | + headerLeft={options.headerLeft?.({} as any)} |
| 43 | + headerRight={options.headerRight?.({} as any)} |
| 44 | + animatedY={animatedY} |
| 45 | + headerSubtitle={props.headerSubtitle} |
| 46 | + /> |
| 47 | + ), |
| 48 | + [animatedY, props.headerSubtitle, props.headerTitle], |
| 49 | + ), |
| 50 | + }} |
| 51 | + /> |
| 52 | + ) |
| 53 | +} |
| 54 | + |
| 55 | +interface HeaderProps { |
| 56 | + headerTitle?: string |
| 57 | + headerLeft?: React.ReactNode |
| 58 | + headerRight?: React.ReactNode |
| 59 | + animatedY: SharedValue<number> |
| 60 | + headerSubtitle?: string |
| 61 | +} |
| 62 | + |
| 63 | +const InternalBlurEffectWithBottomBorder = (props: { animatedY: SharedValue<number> }) => { |
| 64 | + const border = useColor("opaqueSeparator") |
| 65 | + const animatedStyle = useAnimatedStyle(() => { |
| 66 | + return { |
| 67 | + opacity: Math.max(0, Math.min(1, props.animatedY.value / 10)), |
| 68 | + borderBottomWidth: StyleSheet.hairlineWidth, |
| 69 | + borderBottomColor: border, |
| 70 | + } |
| 71 | + }) |
| 72 | + return ( |
| 73 | + <Animated.View className={"absolute inset-0 overflow-hidden"} style={animatedStyle}> |
| 74 | + <ThemedBlurView style={StyleSheet.absoluteFillObject} /> |
| 75 | + </Animated.View> |
| 76 | + ) |
| 77 | +} |
| 78 | + |
| 79 | +const Header: FC<HeaderProps> = memo( |
| 80 | + ({ headerTitle, headerLeft, headerRight, headerSubtitle, animatedY }) => { |
| 81 | + const frame = useSafeAreaFrame() |
| 82 | + const insets = useSafeAreaInsets() |
| 83 | + const height = getDefaultHeaderHeight(frame, true, insets.top) |
| 84 | + |
| 85 | + return ( |
| 86 | + <View style={{ height }} className="items-center justify-center"> |
| 87 | + <InternalBlurEffectWithBottomBorder animatedY={animatedY} /> |
| 88 | + {/* Grid */} |
| 89 | + <View className="mx-5 flex-row items-center"> |
| 90 | + {/* Left actions */} |
| 91 | + <View className="flex-1 flex-row items-center justify-start gap-2">{headerLeft}</View> |
| 92 | + |
| 93 | + {/* Title */} |
| 94 | + <View className="mx-8 flex-row items-center justify-center text-center"> |
| 95 | + {/* <Text>Title</Text> */} |
| 96 | + <HeaderTitleExtra subText={headerSubtitle}>{headerTitle}</HeaderTitleExtra> |
| 97 | + </View> |
| 98 | + |
| 99 | + {/* Right actions */} |
| 100 | + <View className="flex-1 flex-row items-center justify-end gap-2">{headerRight}</View> |
| 101 | + </View> |
| 102 | + </View> |
| 103 | + ) |
| 104 | + }, |
| 105 | +) |
0 commit comments