template_0205
This commit is contained in:
337
RN_TEMPLATE/app/components/Header.tsx
Normal file
337
RN_TEMPLATE/app/components/Header.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
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<TextStyle>
|
||||
/**
|
||||
* Optional outer title container style override.
|
||||
*/
|
||||
titleContainerStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Optional inner header wrapper style override.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Optional outer header container style override.
|
||||
*/
|
||||
containerStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* 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 (
|
||||
<View style={[$container, $containerInsets, { backgroundColor }, $containerStyleOverride]}>
|
||||
<View style={[$styles.row, $wrapper, $styleOverride]}>
|
||||
<HeaderAction
|
||||
tx={leftTx}
|
||||
text={leftText}
|
||||
icon={leftIcon}
|
||||
iconColor={leftIconColor}
|
||||
onPress={onLeftPress}
|
||||
txOptions={leftTxOptions}
|
||||
backgroundColor={backgroundColor}
|
||||
ActionComponent={LeftActionComponent}
|
||||
/>
|
||||
|
||||
{!!titleContent && (
|
||||
<View
|
||||
style={[
|
||||
$titleWrapperPointerEvents,
|
||||
titleMode === "center" && themed($titleWrapperCenter),
|
||||
titleMode === "flex" && $titleWrapperFlex,
|
||||
$titleContainerStyleOverride,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
weight="medium"
|
||||
size="md"
|
||||
text={titleContent}
|
||||
style={[$title, $titleStyleOverride]}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<HeaderAction
|
||||
tx={rightTx}
|
||||
text={rightText}
|
||||
icon={rightIcon}
|
||||
iconColor={rightIconColor}
|
||||
onPress={onRightPress}
|
||||
txOptions={rightTxOptions}
|
||||
backgroundColor={backgroundColor}
|
||||
ActionComponent={RightActionComponent}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 (
|
||||
<TouchableOpacity
|
||||
style={themed([$actionTextContainer, { backgroundColor }])}
|
||||
onPress={onPress}
|
||||
disabled={!onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text weight="medium" size="md" text={content} style={themed($actionText)} />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
return (
|
||||
<PressableIcon
|
||||
size={s(24)}
|
||||
icon={icon}
|
||||
color={iconColor}
|
||||
onPress={onPress}
|
||||
containerStyle={[
|
||||
themed([$actionIconContainer, { backgroundColor }]),
|
||||
isRTL ? { transform: [{ rotate: "180deg" }] } : {},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <View style={[$actionFillerContainer, { backgroundColor }]} />
|
||||
}
|
||||
|
||||
const $wrapper: ViewStyle = {
|
||||
height: s(56),
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}
|
||||
|
||||
const $container: ViewStyle = {
|
||||
width: "100%",
|
||||
}
|
||||
|
||||
const $title: TextStyle = {
|
||||
textAlign: "center",
|
||||
}
|
||||
|
||||
const $actionTextContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
flexGrow: 0,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%",
|
||||
paddingHorizontal: spacing.md,
|
||||
zIndex: 2,
|
||||
})
|
||||
|
||||
const $actionText: ThemedStyle<TextStyle> = ({ colors }) => ({
|
||||
color: colors.tint,
|
||||
})
|
||||
|
||||
const $actionIconContainer: ThemedStyle<ViewStyle> = ({ 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<ViewStyle> = ({ spacing }) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
paddingHorizontal: spacing.xxl,
|
||||
zIndex: 1,
|
||||
})
|
||||
|
||||
const $titleWrapperFlex: ViewStyle = {
|
||||
justifyContent: "center",
|
||||
flexGrow: 1,
|
||||
}
|
||||
Reference in New Issue
Block a user