or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

header-components.mdhooks-contexts.mdindex.mdinteractive-components.mdlayout-components.mdutility-components.mdutility-functions.md

utility-functions.mddocs/

0

# Utility Functions

1

2

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

3

4

## Capabilities

5

6

### getDefaultHeaderHeight

7

8

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

9

10

```typescript { .api }

11

/**

12

* Calculate platform-specific default header height

13

* @param layout - Screen layout dimensions

14

* @param modalPresentation - Whether header is in modal presentation

15

* @param topInset - Top safe area inset (for status bar, notch, etc.)

16

* @returns Calculated header height in pixels

17

*/

18

function getDefaultHeaderHeight(

19

layout: Layout,

20

modalPresentation: boolean,

21

topInset: number

22

): number;

23

```

24

25

**Usage Examples:**

26

27

```typescript

28

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

29

30

// Basic header height calculation

31

function MyScreen() {

32

const [layout, setLayout] = useState({ width: 375, height: 812 });

33

const { top: topInset } = useSafeAreaInsets();

34

35

const headerHeight = getDefaultHeaderHeight(

36

layout,

37

false, // Not modal

38

topInset

39

);

40

41

return (

42

<View style={{ paddingTop: headerHeight }}>

43

<Text>Content below header</Text>

44

</View>

45

);

46

}

47

48

// Modal header height

49

function ModalScreen() {

50

const layout = useFrameSize(frame => frame);

51

const { top: topInset } = useSafeAreaInsets();

52

53

const modalHeaderHeight = getDefaultHeaderHeight(

54

layout,

55

true, // Modal presentation

56

topInset

57

);

58

59

return (

60

<View>

61

<View style={{ height: modalHeaderHeight }}>

62

<Text>Modal Header</Text>

63

</View>

64

<View style={{ flex: 1 }}>

65

<Text>Modal Content</Text>

66

</View>

67

</View>

68

);

69

}

70

71

// Dynamic header height based on orientation

72

function OrientationAwareHeader() {

73

const [layout, setLayout] = useState({ width: 375, height: 812 });

74

const { top: topInset } = useSafeAreaInsets();

75

76

useEffect(() => {

77

const subscription = Dimensions.addEventListener('change', ({ window }) => {

78

setLayout({ width: window.width, height: window.height });

79

});

80

81

return subscription?.remove;

82

}, []);

83

84

const headerHeight = getDefaultHeaderHeight(layout, false, topInset);

85

const isLandscape = layout.width > layout.height;

86

87

return (

88

<View style={{ height: headerHeight, backgroundColor: '#f8f9fa' }}>

89

<Text>

90

{isLandscape ? 'Landscape' : 'Portrait'} Header ({headerHeight}px)

91

</Text>

92

</View>

93

);

94

}

95

```

96

97

### getDefaultSidebarWidth

98

99

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

100

101

```typescript { .api }

102

/**

103

* Calculate optimal sidebar width based on screen width

104

* @param dimensions - Object containing screen width

105

* @returns Calculated sidebar width in pixels

106

*/

107

function getDefaultSidebarWidth({ width }: { width: number }): number;

108

109

/** Default drawer width constant */

110

const DEFAULT_DRAWER_WIDTH = 360;

111

112

/** Approximate app bar height constant */

113

const APPROX_APP_BAR_HEIGHT = 56;

114

```

115

116

**Usage Examples:**

117

118

```typescript

119

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

120

121

// Basic sidebar width calculation

122

function DrawerNavigator() {

123

const screenWidth = useFrameSize(frame => frame.width);

124

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

125

126

return (

127

<View style={{ flexDirection: 'row' }}>

128

<View style={{ width: sidebarWidth, backgroundColor: '#f5f5f5' }}>

129

<Text>Sidebar Content</Text>

130

</View>

131

<View style={{ flex: 1 }}>

132

<Text>Main Content</Text>

133

</View>

134

</View>

135

);

136

}

137

138

// Responsive sidebar

139

function ResponsiveSidebar() {

140

const screenWidth = useFrameSize(frame => frame.width);

141

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

142

const showSidebar = screenWidth > 768;

143

144

return (

145

<View style={{ flexDirection: 'row', flex: 1 }}>

146

{showSidebar && (

147

<View style={{

148

width: sidebarWidth,

149

backgroundColor: '#ffffff',

150

borderRightWidth: 1,

151

borderRightColor: '#e0e0e0'

152

}}>

153

<NavigationSidebar />

154

</View>

155

)}

156

<View style={{ flex: 1 }}>

157

<MainContent />

158

</View>

159

</View>

160

);

161

}

162

163

// Animated sidebar

164

function AnimatedSidebar({ isOpen }) {

165

const screenWidth = useFrameSize(frame => frame.width);

166

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

167

const animatedWidth = useRef(new Animated.Value(0)).current;

168

169

useEffect(() => {

170

Animated.timing(animatedWidth, {

171

toValue: isOpen ? sidebarWidth : 0,

172

duration: 300,

173

useNativeDriver: false

174

}).start();

175

}, [isOpen, sidebarWidth]);

176

177

return (

178

<Animated.View style={{

179

width: animatedWidth,

180

height: '100%',

181

backgroundColor: '#f8f9fa'

182

}}>

183

<SidebarContent />

184

</Animated.View>

185

);

186

}

187

```

188

189

### getHeaderTitle

190

191

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

192

193

```typescript { .api }

194

/**

195

* Resolve header title with priority logic

196

* @param options - Object containing title options

197

* @param fallback - Fallback title if no options provide a title

198

* @returns Resolved title string

199

*/

200

function getHeaderTitle(

201

options: {

202

/** Simple title string */

203

title?: string;

204

/** Custom title renderer or string */

205

headerTitle?: string | ((props: HeaderTitleProps) => React.ReactNode);

206

},

207

fallback: string

208

): string;

209

```

210

211

**Usage Examples:**

212

213

```typescript

214

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

215

216

// Basic title resolution

217

function HeaderComponent({ route, options }) {

218

const title = getHeaderTitle(

219

{

220

title: route.params?.title,

221

headerTitle: options.headerTitle

222

},

223

route.name // fallback to route name

224

);

225

226

return (

227

<View>

228

<Text style={styles.headerTitle}>{title}</Text>

229

</View>

230

);

231

}

232

233

// Priority resolution example

234

function TitleResolver() {

235

// Priority: headerTitle > title > fallback

236

237

const title1 = getHeaderTitle(

238

{ headerTitle: "Custom Header", title: "Basic Title" },

239

"Fallback"

240

);

241

// Result: "Custom Header"

242

243

const title2 = getHeaderTitle(

244

{ title: "Basic Title" },

245

"Fallback"

246

);

247

// Result: "Basic Title"

248

249

const title3 = getHeaderTitle(

250

{},

251

"Fallback"

252

);

253

// Result: "Fallback"

254

255

return (

256

<View>

257

<Text>Title 1: {title1}</Text>

258

<Text>Title 2: {title2}</Text>

259

<Text>Title 3: {title3}</Text>

260

</View>

261

);

262

}

263

264

// Dynamic title resolution

265

function DynamicHeader({ route, navigation }) {

266

const [customTitle, setCustomTitle] = useState("");

267

268

const resolvedTitle = getHeaderTitle(

269

{

270

title: customTitle || route.params?.title,

271

headerTitle: route.params?.headerTitle

272

},

273

route.name

274

);

275

276

useLayoutEffect(() => {

277

navigation.setOptions({

278

title: resolvedTitle

279

});

280

}, [navigation, resolvedTitle]);

281

282

return (

283

<View>

284

<TextInput

285

value={customTitle}

286

onChangeText={setCustomTitle}

287

placeholder="Enter custom title"

288

/>

289

<Text>Current title: {resolvedTitle}</Text>

290

</View>

291

);

292

}

293

```

294

295

### getLabel

296

297

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

298

299

```typescript { .api }

300

/**

301

* Resolve label text with priority-based fallback

302

* @param options - Object containing label options

303

* @param fallback - Fallback label if no options provide a label

304

* @returns Resolved label string

305

*/

306

function getLabel(

307

options: {

308

/** Explicit label text (highest priority) */

309

label?: string;

310

/** Title to use as label (medium priority) */

311

title?: string;

312

},

313

fallback: string

314

): string;

315

```

316

317

**Usage Examples:**

318

319

```typescript

320

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

321

322

// Basic label resolution

323

function NavigationButton({ route, options }) {

324

const label = getLabel(

325

{

326

label: options.tabBarLabel,

327

title: options.title || route.params?.title

328

},

329

route.name // fallback to route name

330

);

331

332

return (

333

<TouchableOpacity>

334

<Text>{label}</Text>

335

</TouchableOpacity>

336

);

337

}

338

339

// Priority resolution examples

340

function LabelExamples() {

341

// Priority: label > title > fallback

342

343

const label1 = getLabel(

344

{ label: "Custom Label", title: "Screen Title" },

345

"Fallback"

346

);

347

// Result: "Custom Label"

348

349

const label2 = getLabel(

350

{ title: "Screen Title" },

351

"Fallback"

352

);

353

// Result: "Screen Title"

354

355

const label3 = getLabel(

356

{},

357

"Fallback"

358

);

359

// Result: "Fallback"

360

361

return (

362

<View>

363

<Text>Label 1: {label1}</Text>

364

<Text>Label 2: {label2}</Text>

365

<Text>Label 3: {label3}</Text>

366

</View>

367

);

368

}

369

370

// Tab bar implementation

371

function CustomTabBar({ state, descriptors, navigation }) {

372

return (

373

<View style={styles.tabBar}>

374

{state.routes.map((route, index) => {

375

const { options } = descriptors[route.key];

376

377

const label = getLabel(

378

{

379

label: options.tabBarLabel,

380

title: options.title

381

},

382

route.name

383

);

384

385

const isFocused = state.index === index;

386

387

return (

388

<TouchableOpacity

389

key={route.key}

390

onPress={() => navigation.navigate(route.name)}

391

style={[

392

styles.tabItem,

393

isFocused && styles.tabItemFocused

394

]}

395

>

396

<Text style={[

397

styles.tabLabel,

398

isFocused && styles.tabLabelFocused

399

]}>

400

{label}

401

</Text>

402

</TouchableOpacity>

403

);

404

})}

405

</View>

406

);

407

}

408

409

// Button with dynamic label

410

function ActionButton({ action, title, label }) {

411

const buttonLabel = getLabel(

412

{ label, title },

413

"Action" // Default fallback

414

);

415

416

return (

417

<Button onPress={action}>

418

{buttonLabel}

419

</Button>

420

);

421

}

422

```

423

424

## Platform-Specific Calculations

425

426

### iOS Header Heights

427

- **Regular headers**: 44pt base + safe area top inset

428

- **Large titles**: Up to 96pt + safe area top inset

429

- **Modal headers**: Reduced height for modal presentation

430

- **Dynamic Island**: Additional height consideration for iPhone 14 Pro models

431

432

### Android Header Heights

433

- **Material Design**: 56dp base height following Material guidelines

434

- **Status bar**: Automatic status bar height inclusion

435

- **Dense displays**: Adjusted for high-density screens

436

437

### Web Header Heights

438

- **Responsive**: Adapts to viewport size and user agent

439

- **Accessibility**: Considers user font size preferences

440

- **Browser chrome**: Accounts for browser-specific header space

441

442

## Sidebar Width Guidelines

443

444

The `getDefaultSidebarWidth` function follows these principles:

445

446

- **Mobile** (< 480px): No sidebar or overlay

447

- **Tablet** (480-768px): 280px sidebar width

448

- **Desktop** (768px+): 320-360px sidebar width

449

- **Large screens** (1200px+): Up to 400px sidebar width

450

- **Maximum**: Never exceeds 80% of screen width

451

452

## Usage Patterns

453

454

### Combining Utility Functions

455

456

```typescript

457

function AdaptiveLayout() {

458

const layout = useFrameSize(frame => frame);

459

const { top: topInset } = useSafeAreaInsets();

460

461

const headerHeight = getDefaultHeaderHeight(layout, false, topInset);

462

const sidebarWidth = getDefaultSidebarWidth(layout);

463

const showSidebar = layout.width > 768;

464

465

return (

466

<View style={{ flex: 1 }}>

467

<View style={{ height: headerHeight }}>

468

<HeaderComponent />

469

</View>

470

<View style={{ flex: 1, flexDirection: 'row' }}>

471

{showSidebar && (

472

<View style={{ width: sidebarWidth }}>

473

<SidebarComponent />

474

</View>

475

)}

476

<View style={{ flex: 1 }}>

477

<MainContent />

478

</View>

479

</View>

480

</View>

481

);

482

}

483

```

484

485

### Error Handling

486

487

All utility functions include proper error handling and default values:

488

489

```typescript

490

// Safe usage with fallbacks

491

const headerHeight = getDefaultHeaderHeight(

492

layout || { width: 375, height: 812 },

493

modalPresentation ?? false,

494

topInset ?? 0

495

);

496

497

const sidebarWidth = getDefaultSidebarWidth({

498

width: screenWidth || 375

499

});

500

501

const title = getHeaderTitle(

502

options || {},

503

"Default Title"

504

);

505

506

const label = getLabel(

507

options || {},

508

"Default Label"

509

);

510

```