CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-navigation--elements

UI Components for React Navigation providing headers, buttons, and layout components with cross-platform support

Pending
Overview
Eval results
Files

utility-functions.mddocs/

Utility Functions

Helper functions for calculating dimensions, resolving titles and labels, and other common navigation tasks with platform-specific optimizations.

Capabilities

getDefaultHeaderHeight

Calculates platform-specific default header height considering device type, orientation, modal presentation, and platform-specific features like Dynamic Island.

/**
 * Calculate platform-specific default header height
 * @param layout - Screen layout dimensions
 * @param modalPresentation - Whether header is in modal presentation
 * @param topInset - Top safe area inset (for status bar, notch, etc.)
 * @returns Calculated header height in pixels
 */
function getDefaultHeaderHeight(
  layout: Layout,
  modalPresentation: boolean,
  topInset: number
): number;

Usage Examples:

import { getDefaultHeaderHeight } from "@react-navigation/elements";

// Basic header height calculation
function MyScreen() {
  const [layout, setLayout] = useState({ width: 375, height: 812 });
  const { top: topInset } = useSafeAreaInsets();
  
  const headerHeight = getDefaultHeaderHeight(
    layout,
    false, // Not modal
    topInset
  );
  
  return (
    <View style={{ paddingTop: headerHeight }}>
      <Text>Content below header</Text>
    </View>
  );
}

// Modal header height
function ModalScreen() {
  const layout = useFrameSize(frame => frame);
  const { top: topInset } = useSafeAreaInsets();
  
  const modalHeaderHeight = getDefaultHeaderHeight(
    layout,
    true, // Modal presentation
    topInset
  );
  
  return (
    <View>
      <View style={{ height: modalHeaderHeight }}>
        <Text>Modal Header</Text>
      </View>
      <View style={{ flex: 1 }}>
        <Text>Modal Content</Text>
      </View>
    </View>
  );
}

// Dynamic header height based on orientation
function OrientationAwareHeader() {
  const [layout, setLayout] = useState({ width: 375, height: 812 });
  const { top: topInset } = useSafeAreaInsets();
  
  useEffect(() => {
    const subscription = Dimensions.addEventListener('change', ({ window }) => {
      setLayout({ width: window.width, height: window.height });
    });
    
    return subscription?.remove;
  }, []);
  
  const headerHeight = getDefaultHeaderHeight(layout, false, topInset);
  const isLandscape = layout.width > layout.height;
  
  return (
    <View style={{ height: headerHeight, backgroundColor: '#f8f9fa' }}>
      <Text>
        {isLandscape ? 'Landscape' : 'Portrait'} Header ({headerHeight}px)
      </Text>
    </View>
  );
}

getDefaultSidebarWidth

Calculates optimal sidebar width based on screen dimensions following Material Design guidelines and responsive design principles.

/**
 * Calculate optimal sidebar width based on screen width
 * @param dimensions - Object containing screen width
 * @returns Calculated sidebar width in pixels
 */
function getDefaultSidebarWidth({ width }: { width: number }): number;

/** Default drawer width constant */
const DEFAULT_DRAWER_WIDTH = 360;

/** Approximate app bar height constant */
const APPROX_APP_BAR_HEIGHT = 56;

Usage Examples:

import { getDefaultSidebarWidth } from "@react-navigation/elements";

// Basic sidebar width calculation
function DrawerNavigator() {
  const screenWidth = useFrameSize(frame => frame.width);
  const sidebarWidth = getDefaultSidebarWidth({ width: screenWidth });
  
  return (
    <View style={{ flexDirection: 'row' }}>
      <View style={{ width: sidebarWidth, backgroundColor: '#f5f5f5' }}>
        <Text>Sidebar Content</Text>
      </View>
      <View style={{ flex: 1 }}>
        <Text>Main Content</Text>
      </View>
    </View>
  );
}

// Responsive sidebar
function ResponsiveSidebar() {
  const screenWidth = useFrameSize(frame => frame.width);
  const sidebarWidth = getDefaultSidebarWidth({ width: screenWidth });
  const showSidebar = screenWidth > 768;
  
  return (
    <View style={{ flexDirection: 'row', flex: 1 }}>
      {showSidebar && (
        <View style={{ 
          width: sidebarWidth,
          backgroundColor: '#ffffff',
          borderRightWidth: 1,
          borderRightColor: '#e0e0e0'
        }}>
          <NavigationSidebar />
        </View>
      )}
      <View style={{ flex: 1 }}>
        <MainContent />
      </View>
    </View>
  );
}

// Animated sidebar
function AnimatedSidebar({ isOpen }) {
  const screenWidth = useFrameSize(frame => frame.width);
  const sidebarWidth = getDefaultSidebarWidth({ width: screenWidth });
  const animatedWidth = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(animatedWidth, {
      toValue: isOpen ? sidebarWidth : 0,
      duration: 300,
      useNativeDriver: false
    }).start();
  }, [isOpen, sidebarWidth]);
  
  return (
    <Animated.View style={{ 
      width: animatedWidth,
      height: '100%',
      backgroundColor: '#f8f9fa'
    }}>
      <SidebarContent />
    </Animated.View>
  );
}

getHeaderTitle

Resolves header title with priority logic, supporting both string titles and function-based title renderers.

/**
 * Resolve header title with priority logic
 * @param options - Object containing title options
 * @param fallback - Fallback title if no options provide a title
 * @returns Resolved title string
 */
function getHeaderTitle(
  options: {
    /** Simple title string */
    title?: string;
    /** Custom title renderer or string */
    headerTitle?: string | ((props: HeaderTitleProps) => React.ReactNode);
  },
  fallback: string
): string;

Usage Examples:

import { getHeaderTitle } from "@react-navigation/elements";

// Basic title resolution
function HeaderComponent({ route, options }) {
  const title = getHeaderTitle(
    {
      title: route.params?.title,
      headerTitle: options.headerTitle
    },
    route.name // fallback to route name
  );
  
  return (
    <View>
      <Text style={styles.headerTitle}>{title}</Text>
    </View>
  );
}

// Priority resolution example
function TitleResolver() {
  // Priority: headerTitle > title > fallback
  
  const title1 = getHeaderTitle(
    { headerTitle: "Custom Header", title: "Basic Title" },
    "Fallback"
  );
  // Result: "Custom Header"
  
  const title2 = getHeaderTitle(
    { title: "Basic Title" },
    "Fallback"
  );
  // Result: "Basic Title"
  
  const title3 = getHeaderTitle(
    {},
    "Fallback"
  );
  // Result: "Fallback"
  
  return (
    <View>
      <Text>Title 1: {title1}</Text>
      <Text>Title 2: {title2}</Text>
      <Text>Title 3: {title3}</Text>
    </View>
  );
}

// Dynamic title resolution
function DynamicHeader({ route, navigation }) {
  const [customTitle, setCustomTitle] = useState("");
  
  const resolvedTitle = getHeaderTitle(
    {
      title: customTitle || route.params?.title,
      headerTitle: route.params?.headerTitle
    },
    route.name
  );
  
  useLayoutEffect(() => {
    navigation.setOptions({
      title: resolvedTitle
    });
  }, [navigation, resolvedTitle]);
  
  return (
    <View>
      <TextInput 
        value={customTitle}
        onChangeText={setCustomTitle}
        placeholder="Enter custom title"
      />
      <Text>Current title: {resolvedTitle}</Text>
    </View>
  );
}

getLabel

Resolves label text with priority-based fallback support, commonly used for button and navigation labels.

/**
 * Resolve label text with priority-based fallback
 * @param options - Object containing label options
 * @param fallback - Fallback label if no options provide a label
 * @returns Resolved label string
 */
function getLabel(
  options: {
    /** Explicit label text (highest priority) */
    label?: string;
    /** Title to use as label (medium priority) */
    title?: string;
  },
  fallback: string
): string;

Usage Examples:

import { getLabel } from "@react-navigation/elements";

// Basic label resolution
function NavigationButton({ route, options }) {
  const label = getLabel(
    {
      label: options.tabBarLabel,
      title: options.title || route.params?.title
    },
    route.name // fallback to route name
  );
  
  return (
    <TouchableOpacity>
      <Text>{label}</Text>
    </TouchableOpacity>
  );
}

// Priority resolution examples
function LabelExamples() {
  // Priority: label > title > fallback
  
  const label1 = getLabel(
    { label: "Custom Label", title: "Screen Title" },
    "Fallback"
  );
  // Result: "Custom Label"
  
  const label2 = getLabel(
    { title: "Screen Title" },
    "Fallback"
  );
  // Result: "Screen Title"
  
  const label3 = getLabel(
    {},
    "Fallback"
  );
  // Result: "Fallback"
  
  return (
    <View>
      <Text>Label 1: {label1}</Text>
      <Text>Label 2: {label2}</Text>
      <Text>Label 3: {label3}</Text>
    </View>
  );
}

// Tab bar implementation
function CustomTabBar({ state, descriptors, navigation }) {
  return (
    <View style={styles.tabBar}>
      {state.routes.map((route, index) => {
        const { options } = descriptors[route.key];
        
        const label = getLabel(
          {
            label: options.tabBarLabel,
            title: options.title
          },
          route.name
        );
        
        const isFocused = state.index === index;
        
        return (
          <TouchableOpacity
            key={route.key}
            onPress={() => navigation.navigate(route.name)}
            style={[
              styles.tabItem,
              isFocused && styles.tabItemFocused
            ]}
          >
            <Text style={[
              styles.tabLabel,
              isFocused && styles.tabLabelFocused
            ]}>
              {label}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

// Button with dynamic label
function ActionButton({ action, title, label }) {
  const buttonLabel = getLabel(
    { label, title },
    "Action" // Default fallback
  );
  
  return (
    <Button onPress={action}>
      {buttonLabel}
    </Button>
  );
}

Platform-Specific Calculations

iOS Header Heights

  • Regular headers: 44pt base + safe area top inset
  • Large titles: Up to 96pt + safe area top inset
  • Modal headers: Reduced height for modal presentation
  • Dynamic Island: Additional height consideration for iPhone 14 Pro models

Android Header Heights

  • Material Design: 56dp base height following Material guidelines
  • Status bar: Automatic status bar height inclusion
  • Dense displays: Adjusted for high-density screens

Web Header Heights

  • Responsive: Adapts to viewport size and user agent
  • Accessibility: Considers user font size preferences
  • Browser chrome: Accounts for browser-specific header space

Sidebar Width Guidelines

The getDefaultSidebarWidth function follows these principles:

  • Mobile (< 480px): No sidebar or overlay
  • Tablet (480-768px): 280px sidebar width
  • Desktop (768px+): 320-360px sidebar width
  • Large screens (1200px+): Up to 400px sidebar width
  • Maximum: Never exceeds 80% of screen width

Usage Patterns

Combining Utility Functions

function AdaptiveLayout() {
  const layout = useFrameSize(frame => frame);
  const { top: topInset } = useSafeAreaInsets();
  
  const headerHeight = getDefaultHeaderHeight(layout, false, topInset);
  const sidebarWidth = getDefaultSidebarWidth(layout);
  const showSidebar = layout.width > 768;
  
  return (
    <View style={{ flex: 1 }}>
      <View style={{ height: headerHeight }}>
        <HeaderComponent />
      </View>
      <View style={{ flex: 1, flexDirection: 'row' }}>
        {showSidebar && (
          <View style={{ width: sidebarWidth }}>
            <SidebarComponent />
          </View>
        )}
        <View style={{ flex: 1 }}>
          <MainContent />
        </View>
      </View>
    </View>
  );
}

Error Handling

All utility functions include proper error handling and default values:

// Safe usage with fallbacks
const headerHeight = getDefaultHeaderHeight(
  layout || { width: 375, height: 812 },
  modalPresentation ?? false,
  topInset ?? 0
);

const sidebarWidth = getDefaultSidebarWidth({
  width: screenWidth || 375
});

const title = getHeaderTitle(
  options || {},
  "Default Title"
);

const label = getLabel(
  options || {},
  "Default Label"
);

Install with Tessl CLI

npx tessl i tessl/npm-react-navigation--elements

docs

header-components.md

hooks-contexts.md

index.md

interactive-components.md

layout-components.md

utility-components.md

utility-functions.md

tile.json