import { ReactElement } from "react" import { StyleProp, TextStyle, TouchableOpacity, TouchableOpacityProps, View, ViewStyle, } from "react-native" import { useTranslation } from "react-i18next" import { isRTL } from "@/i18n" import { useAppTheme } from "@/theme/context" import { $styles } from "@/theme/styles" import type { ThemedStyle } from "@/theme/types" import { s } from "@/utils/responsive" import { ExtendedEdge, useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle" import { IconTypes, PressableIcon } from "./Icon" import { Text, TextProps } from "./Text" export interface HeaderProps { /** * The layout of the title relative to the action components. * - `center` will force the title to always be centered relative to the header. If the title or the action buttons are too long, the title will be cut off. * - `flex` will attempt to center the title relative to the action buttons. If the action buttons are different widths, the title will be off-center relative to the header. */ titleMode?: "center" | "flex" /** * Optional title style override. */ titleStyle?: StyleProp /** * Optional outer title container style override. */ titleContainerStyle?: StyleProp /** * Optional inner header wrapper style override. */ style?: StyleProp /** * Optional outer header container style override. */ containerStyle?: StyleProp /** * Background color */ backgroundColor?: string /** * Title text to display if not using `tx` or nested components. */ title?: TextProps["text"] /** * Title text which is looked up via i18n. */ titleTx?: TextProps["tx"] /** * Optional options to pass to i18n. Useful for interpolation * as well as explicitly setting locale or translation fallbacks. */ titleTxOptions?: TextProps["txOptions"] /** * Icon that should appear on the left. * Can be used with `onLeftPress`. */ leftIcon?: IconTypes /** * An optional tint color for the left icon */ leftIconColor?: string /** * Left action text to display if not using `leftTx`. * Can be used with `onLeftPress`. Overrides `leftIcon`. */ leftText?: TextProps["text"] /** * Left action text text which is looked up via i18n. * Can be used with `onLeftPress`. Overrides `leftIcon`. */ leftTx?: TextProps["tx"] /** * Left action custom ReactElement if the built in action props don't suffice. * Overrides `leftIcon`, `leftTx` and `leftText`. */ LeftActionComponent?: ReactElement /** * Optional options to pass to i18n. Useful for interpolation * as well as explicitly setting locale or translation fallbacks. */ leftTxOptions?: TextProps["txOptions"] /** * What happens when you press the left icon or text action. */ onLeftPress?: TouchableOpacityProps["onPress"] /** * Icon that should appear on the right. * Can be used with `onRightPress`. */ rightIcon?: IconTypes /** * An optional tint color for the right icon */ rightIconColor?: string /** * Right action text to display if not using `rightTx`. * Can be used with `onRightPress`. Overrides `rightIcon`. */ rightText?: TextProps["text"] /** * Right action text text which is looked up via i18n. * Can be used with `onRightPress`. Overrides `rightIcon`. */ rightTx?: TextProps["tx"] /** * Right action custom ReactElement if the built in action props don't suffice. * Overrides `rightIcon`, `rightTx` and `rightText`. */ RightActionComponent?: ReactElement /** * Optional options to pass to i18n. Useful for interpolation * as well as explicitly setting locale or translation fallbacks. */ rightTxOptions?: TextProps["txOptions"] /** * What happens when you press the right icon or text action. */ onRightPress?: TouchableOpacityProps["onPress"] /** * Override the default edges for the safe area. */ safeAreaEdges?: ExtendedEdge[] } interface HeaderActionProps { backgroundColor?: string icon?: IconTypes iconColor?: string text?: TextProps["text"] tx?: TextProps["tx"] txOptions?: TextProps["txOptions"] onPress?: TouchableOpacityProps["onPress"] ActionComponent?: ReactElement } /** * Header that appears on many screens. Will hold navigation buttons and screen title. * The Header is meant to be used with the `screenOptions.header` option on navigators, routes, or screen components via `navigation.setOptions({ header })`. * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Header/} * @param {HeaderProps} props - The props for the `Header` component. * @returns {JSX.Element} The rendered `Header` component. */ export function Header(props: HeaderProps) { const { theme: { colors }, themed, } = useAppTheme() const { t } = useTranslation() const { backgroundColor = colors.background, LeftActionComponent, leftIcon, leftIconColor, leftText, leftTx, leftTxOptions, onLeftPress, onRightPress, RightActionComponent, rightIcon, rightIconColor, rightText, rightTx, rightTxOptions, safeAreaEdges = ["top"], title, titleMode = "center", titleTx, titleTxOptions, titleContainerStyle: $titleContainerStyleOverride, style: $styleOverride, titleStyle: $titleStyleOverride, containerStyle: $containerStyleOverride, } = props const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges) const titleContent = titleTx ? t(titleTx, titleTxOptions) : title return ( {!!titleContent && ( )} ) } /** * @param {HeaderActionProps} props - The props for the `HeaderAction` component. * @returns {JSX.Element} The rendered `HeaderAction` component. */ function HeaderAction(props: HeaderActionProps) { const { backgroundColor, icon, text, tx, txOptions, onPress, ActionComponent, iconColor } = props const { themed } = useAppTheme() const { t } = useTranslation() const content = tx ? t(tx, txOptions) : text if (ActionComponent) return ActionComponent if (content) { return ( ) } if (icon) { return ( ) } return } const $wrapper: ViewStyle = { height: s(56), alignItems: "center", justifyContent: "space-between", } const $container: ViewStyle = { width: "100%", } const $title: TextStyle = { textAlign: "center", } const $actionTextContainer: ThemedStyle = ({ spacing }) => ({ flexGrow: 0, alignItems: "center", justifyContent: "center", height: "100%", paddingHorizontal: spacing.md, zIndex: 2, }) const $actionText: ThemedStyle = ({ colors }) => ({ color: colors.tint, }) const $actionIconContainer: ThemedStyle = ({ spacing }) => ({ flexGrow: 0, alignItems: "center", justifyContent: "center", height: "100%", paddingHorizontal: spacing.md, zIndex: 2, }) const $actionFillerContainer: ViewStyle = { width: s(16), } const $titleWrapperPointerEvents: ViewStyle = { pointerEvents: "none", } const $titleWrapperCenter: ThemedStyle = ({ spacing }) => ({ alignItems: "center", justifyContent: "center", height: "100%", width: "100%", position: "absolute", paddingHorizontal: spacing.xxl, zIndex: 1, }) const $titleWrapperFlex: ViewStyle = { justifyContent: "center", flexGrow: 1, }