Files
minibits_wallet/src/components/Header.tsx
T

397 lines
11 KiB
TypeScript

import React, { ReactElement } from "react"
import {
StyleProp,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewStyle,
StatusBar,
ColorValue,
Platform
} from "react-native"
import Animated, {
SharedValue,
useAnimatedStyle,
interpolate,
Extrapolation,
} from "react-native-reanimated"
import { useIsFocused } from '@react-navigation/native'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { translate } from "../i18n"
import { useThemeColor, spacing, colors, typography } from "../theme"
import { displayName as appName } from '../../app.json'
import { ExtendedEdge, useSafeAreaInsetsStyle } from "../utils/useSafeAreaInsetsStyle"
import { Icon, IconTypes } 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?: ColorValue
/**
* Title action custom ReactElement.
* Overrides `title`, `titleTx`.
*/
TitleActionComponent?: ReactElement
/**
* 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?: ColorValue
/**
* 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[]
/**
* Shared value from scroll position for animated title.
* When provided, the title will fade in as the user scrolls.
*/
scrollY?: SharedValue<number>
/**
* The scroll distance over which the title animation occurs.
* Defaults to 60.
*/
scrollDistance?: number
}
interface HeaderActionProps {
backgroundColor?: ColorValue
icon?: IconTypes
iconColor?: string
text?: TextProps["text"]
tx?: TextProps["tx"]
txOptions?: TextProps["txOptions"]
onPress?: TouchableOpacityProps["onPress"]
ActionComponent?: ReactElement
}
/* Keeps status bar style in sync with navigation focus — only applies when this screen is active.
Uses React Native's StatusBar (status bar only) so it does not override the nav bar icon style
managed by the root SystemBars in AppNavigator. */
function FocusAwareStatusBar() {
const isFocused = useIsFocused()
return isFocused ? <StatusBar translucent barStyle="light-content" /> : null
}
/**
* 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 })`.
*
* - [Documentation and Examples](https://github.com/infinitered/ignite/blob/master/docs/Components-Header.md)
*/
export function Header(props: HeaderProps) {
//const isInternetReachable = useIsInternetReachable()
const {
backgroundColor = useThemeColor('header'),
LeftActionComponent,
leftIcon,
leftIconColor = "white",
leftText,
leftTx,
leftTxOptions,
onLeftPress,
onRightPress,
RightActionComponent,
rightIcon,
rightIconColor = "white",
rightText,
rightTx,
rightTxOptions,
safeAreaEdges = ["top"],
TitleActionComponent,
// title = appName,
title,
titleMode = "center",
titleTx,
titleTxOptions,
titleContainerStyle: $titleContainerStyleOverride,
style: $styleOverride,
titleStyle: $titleStyleOverride,
containerStyle: $containerStyleOverride,
scrollY,
scrollDistance = 60,
} = props
const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)
const titleContent = !!TitleActionComponent ? undefined : titleTx ? translate(titleTx, titleTxOptions) : title
// Animated title opacity - fades in as user scrolls
const animatedTitleStyle = useAnimatedStyle(() => {
if (!scrollY) {
return {}
}
const opacity = interpolate(
scrollY.value,
[scrollDistance * 0.5, scrollDistance],
[0, 1],
Extrapolation.CLAMP
)
return { opacity }
})
return (
<View style={[$container, $containerInsets, { backgroundColor }, $containerStyleOverride]}>
<FocusAwareStatusBar />
<View style={[$wrapper, $styleOverride]}>
<HeaderAction
tx={leftTx}
text={leftText}
icon={leftIcon === "faArrowLeft" ? Platform.OS === 'ios' ? 'faChevronLeft' : leftIcon : leftIcon}
iconColor={leftIconColor as string}
onPress={onLeftPress}
txOptions={leftTxOptions}
backgroundColor={backgroundColor}
ActionComponent={LeftActionComponent}
/>
{!!TitleActionComponent && (
<View
style={[
titleMode === "center" && $titleWrapperCenter,
titleMode === "flex" && $titleWrapperFlex,
$titleContainerStyleOverride,
]}
pointerEvents="none"
>
{TitleActionComponent}
</View>
)}
{!!titleContent && (
<Animated.View
style={[
titleMode === "center" && $titleWrapperCenter,
titleMode === "flex" && $titleWrapperFlex,
$titleContainerStyleOverride,
scrollY ? animatedTitleStyle : undefined,
]}
pointerEvents="none"
>
<Text
weight="medium"
size="lg"
text={titleContent}
style={[$title, $titleStyleOverride]}
/>
</Animated.View>
)}
<HeaderAction
tx={rightTx}
text={rightText}
icon={rightIcon}
iconColor={rightIconColor as string}
onPress={onRightPress}
txOptions={rightTxOptions}
backgroundColor={backgroundColor}
ActionComponent={RightActionComponent}
/>
</View>
</View>
)
}
function HeaderAction(props: HeaderActionProps) {
const { backgroundColor, icon, text, tx, txOptions, onPress, ActionComponent, iconColor } = props
const content = tx ? translate(tx, txOptions) : text
if (ActionComponent) return ActionComponent
if (content) {
return (
<TouchableOpacity
style={[$actionTextContainer, { backgroundColor }]}
onPress={onPress}
disabled={!onPress}
activeOpacity={0.8}
>
<Text weight="medium" size="md" text={content} style={$actionText} />
</TouchableOpacity>
)
}
if (icon) {
return (
<Icon
size={20}
icon={icon}
color={iconColor}
onPress={onPress}
containerStyle={[$actionIconContainer, { backgroundColor }]}
/>
)
}
return <View style={[$actionFillerContainer, { backgroundColor }]} />
}
const $wrapper: ViewStyle = {
height: 56,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
// overflow: 'visible',
// borderWidth: 1,
// borderColor: 'red',
}
const $container: ViewStyle = {
width: "100%",
}
const $title: TextStyle = {
textAlign: "center",
//fontFamily: typography.logo.normal,
color: 'white'
}
const $actionTextContainer: ViewStyle = {
flexGrow: 0,
alignItems: "center",
justifyContent: "center",
height: "100%",
paddingHorizontal: spacing.medium,
zIndex: 2,
}
const $actionText: TextStyle = {
color: colors.light.tint,
}
const $actionIconContainer: ViewStyle = {
flexGrow: 0,
alignItems: "center",
justifyContent: "center",
height: "100%",
paddingHorizontal: spacing.medium,
zIndex: 2,
}
const $actionFillerContainer: ViewStyle = {
width: 16,
}
const $titleWrapperCenter: ViewStyle = {
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
position: "absolute",
paddingHorizontal: spacing.huge,
zIndex: 1,
}
const $titleWrapperFlex: ViewStyle = {
justifyContent: "center",
flexGrow: 1,
}
/* const $offline: TextStyle = {
position: 'absolute',
top: 15,
alignItems: "center",
justifyContent: "center",
fontSize: 10,
fontFamily: typography.code?.normal,
color: colors.palette.accent200,
}*/