All files / components PageHeader.tsx

70% Statements 7/10
41.37% Branches 12/29
50% Functions 1/2
70% Lines 7/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                                                2x                                   5x 5x 5x 5x   5x               5x                                                                                                                                                    
import { TouchableOpacity } from 'react-native';
import type { EdgeInsets } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router';
import Ionicons from '@expo/vector-icons/Ionicons';
import { Text, XStack, YStack } from 'tamagui';
 
import { primaryScale } from '@/src/design-system/tokens';
import { useThemeColors, useIsDarkMode } from '@/src/hooks/useThemeColors';
 
import { IconSymbol } from './ui/IconSymbol';
 
interface HeaderIcon {
  name: React.ComponentProps<typeof IconSymbol>['name'];
  size?: number;
  color?: string;
  backgroundColor?: string;
  borderColor?: string;
}
 
export interface PageHeaderProps {
  title: string;
  subtitle?: string;
  icon?: HeaderIcon;
  showBackButton?: boolean;
  onBackPress?: () => void;
  insets: EdgeInsets;
  backgroundColor?: string;
  showBorder?: boolean;
  rightElement?: React.ReactNode;
  variant?: 'default' | 'compact' | 'prominent';
}
 
const variantConfig = {
  compact: { pb: '$2', title: 18, subtitle: 11, iconSize: 40, iconInner: 20 },
  prominent: { pb: '$5', title: 28, subtitle: 15, iconSize: 48, iconInner: 26 },
  default: { pb: '$3', title: 20, subtitle: 12, iconSize: 40, iconInner: 22 },
};
 
export function PageHeader({
  title,
  subtitle,
  icon,
  showBackButton = false,
  onBackPress,
  insets,
  backgroundColor = '$background',
  showBorder = true,
  rightElement,
  variant = 'default',
}: PageHeaderProps) {
  const router = useRouter();
  const config = variantConfig[variant];
  const colors = useThemeColors();
  const isDark = useIsDarkMode();
 
  const handleBackPress = () => {
    if (onBackPress) {
      onBackPress();
    } else {
      router.back();
    }
  };
 
  return (
    <YStack
      paddingTop={insets.top}
      paddingHorizontal="$4"
      paddingBottom={config.pb as any}
      backgroundColor={backgroundColor as any}
      borderBottomWidth={showBorder ? 1 : 0}
      borderBottomColor="$borderColor"
    >
      <XStack alignItems="center" gap="$2.5" paddingTop="$2.5">
        {/* 返回按钮 */}
        {showBackButton && (
          <TouchableOpacity onPress={handleBackPress} activeOpacity={0.7}>
            <YStack
              padding="$2"
              borderRadius="$3"
              backgroundColor="$background"
              borderWidth={1}
              borderColor="$borderColor"
            >
              <Ionicons name="chevron-back" size={24} color={colors.icon} />
            </YStack>
          </TouchableOpacity>
        )}
 
        {/* 图标 */}
        {icon && !showBackButton && (
          <YStack
            width={config.iconSize}
            height={config.iconSize}
            borderRadius={9999}
            backgroundColor={
              (icon.backgroundColor || (isDark ? '#3D2A1F' : primaryScale.primary2)) as any
            }
            alignItems="center"
            justifyContent="center"
            borderWidth={1.5}
            borderColor={(icon.borderColor || (isDark ? '#4D3A2F' : primaryScale.primary4)) as any}
          >
            <IconSymbol
              name={icon.name}
              size={icon.size || config.iconInner}
              color={icon.color || colors.primary}
            />
          </YStack>
        )}
 
        {/* 标题 */}
        <YStack flex={1}>
          <Text
            fontSize={config.title}
            fontWeight="700"
            color={colors.text as any}
            letterSpacing={0.3}
          >
            {title}
          </Text>
          {subtitle && (
            <Text
              fontSize={config.subtitle}
              color={colors.textSecondary as any}
              fontWeight="500"
              marginTop="$0.5"
            >
              {subtitle}
            </Text>
          )}
        </YStack>
 
        {rightElement && <YStack>{rightElement}</YStack>}
      </XStack>
    </YStack>
  );
}