import { useCallback, useMemo, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Text, View } from "react-native"; import { router } from "expo-router"; import { ArrowLeft } from "lucide-react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { HeaderToggleButton } from "@/components/headers/header-toggle-button"; import { SidebarMenuToggle } from "@/components/headers/sidebar-menu-toggle"; import { IconButtonChromeProvider, iconButtonChromeGlyphSize, mutedIconColorMapping, } from "@/components/ui/icon-button-chrome"; import { HEADER_INNER_HEIGHT_MOBILE, HEADER_TOP_PADDING_MOBILE } from "@/constants/layout"; import type { ShortcutKey } from "@/utils/format-shortcut"; const ThemedArrowLeft = withUnistyles(ArrowLeft); const NO_SHORTCUT: ShortcutKey[] = []; interface CompactHeaderProps { /** The leading island: the sidebar menu, or a back button. */ navigation: "menu" | "back"; /** Back button action. Defaults to navigating back. */ onBack?: () => void; title?: string; titleTestID?: string; /** A line under the title. */ subtitle?: ReactNode; /** Show a placeholder instead of the title while it loads. */ loading?: boolean; /** Icon actions inherit the island chrome through the shared button primitives. */ actions?: ReactNode; } /** * The phone header: the navigation button and the screen's actions sit in islands, with the title * left-aligned between them. Neither the header nor its islands draw a background, so it reads the * same in flow and floating over content (see `FloatingHeaderLayout`, whose fade gives the icons * contrast). Its height matches the old flat header, so anything * offset by the header height still lines up. */ export function CompactHeader({ navigation, onBack, title, titleTestID, subtitle, loading = false, actions, }: CompactHeaderProps) { const insets = useSafeAreaInsets(); const rootStyle = useMemo( () => [styles.root, { paddingTop: insets.top + HEADER_TOP_PADDING_MOBILE }], [insets.top], ); return ( {navigation === "menu" ? : } {actions ? {actions} : null} ); } function CompactHeaderTitle({ loading, title, titleTestID, subtitle, }: Pick & { loading: boolean }) { if (loading) { return ; } return ( <> {title ? ( {title} ) : null} {subtitle} ); } function BackButton({ onBack }: { onBack?: () => void }) { const { t } = useTranslation(); const handlePress = useCallback(() => { if (onBack) { onBack(); return; } router.back(); }, [onBack]); return ( ); } const styles = StyleSheet.create((theme) => ({ root: { paddingHorizontal: theme.spacing[3], }, row: { height: HEADER_INNER_HEIGHT_MOBILE, flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, titleGroup: { flex: 1, minWidth: 0, // Both title lines receive the remaining width; text alignment does not size the slot. alignItems: "stretch", }, island: { flexDirection: "row", alignItems: "center", }, title: { fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.medium, color: theme.colors.foreground, textAlign: "left", }, // A title without a subtitle has the header to itself, so it reads one step larger. singleLineTitle: { fontSize: theme.fontSize.lg, fontWeight: theme.fontWeight.medium, color: theme.colors.foreground, textAlign: "left", }, titleSkeleton: { width: 140, maxWidth: "100%", height: 22, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.surface3, opacity: 0.25, }, }));