From 1a45cbd386bf3001340602a269bf8d3c6193f32c Mon Sep 17 00:00:00 2001 From: Brian August Nguyen Date: Wed, 17 Dec 2025 18:06:00 -0800 Subject: [PATCH] chore: Added header utility functions (#24123) ## **Description** This PR introduces utility functions that wrap the new header components (`HeaderCenter`, `HeaderWithTitleLeft`, `HeaderWithTitleLeftScrollable`) to return React Navigation-compatible screen options. These utilities will enable gradual deprecation of the `Navbar/index.js` file by providing a cleaner, more consistent API for navigation headers. **Motivation:** - The current `Navbar/index.js` contains 40+ functions with inconsistent patterns and mixed concerns - The new header components provide a more composable and maintainable approach - These utility functions bridge the gap between the new components and React Navigation's expected format **Changes:** - Added `getHeaderCenterNavbarOptions` - for centered titles with optional back/close buttons - Added `getHeaderWithTitleLeftNavbarOptions` - for left-aligned large titles with optional back button - Added `getHeaderWithTitleLeftScrollableNavbarOptions` - for collapsing headers that respond to scroll - Added unit tests for all utility functions - Updated each component's `index.ts` to export the new utilities ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MDP-652 ## **Manual testing steps** ```gherkin Feature: Header Navigation Options Utilities Scenario: Developer uses getHeaderCenterNavbarOptions Given a React Navigation stack screen When developer passes getHeaderCenterNavbarOptions({ title: "Settings", onBack: () => navigation.goBack() }) to screen options Then the screen displays a HeaderCenter component with the title and back button Scenario: Developer uses getHeaderWithTitleLeftNavbarOptions Given a React Navigation stack screen When developer passes getHeaderWithTitleLeftNavbarOptions({ onBack, titleLeftProps: { title: "NFT", topLabel: "Collection" } }) to screen options Then the screen displays a HeaderWithTitleLeft component with large left-aligned title Scenario: Developer uses getHeaderWithTitleLeftScrollableNavbarOptions Given a React Navigation stack screen with a ScrollView When developer uses the hook and utility together Then the header collapses smoothly as the user scrolls ``` ## **Screenshots/Recordings** ### **Before** N/A - New utility functions, no visual changes ### **After** N/A - These are developer-facing utilities that wrap existing components ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Add utilities to wrap header components into React Navigation `header` options, with tests and updated exports. > > - **Component Library**: > - **New utilities**: Add `getHeaderCenterNavbarOptions`, `getHeaderWithTitleLeftNavbarOptions`, and `getHeaderWithTitleLeftScrollableNavbarOptions` that return React Navigation-compatible `header` options. > - **Tests**: Unit tests for each utility covering rendering, callbacks, and prop forwarding. > - **Exports**: Update `index.ts` files to export the new utilities. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5c9b9d8b35d3ffb81dc4059f15a8f6a91836f92e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../getHeaderCenterNavbarOptions.test.tsx | 115 ++++++++++++ .../getHeaderCenterNavbarOptions.tsx | 32 ++++ .../components-temp/HeaderCenter/index.ts | 1 + ...tHeaderWithTitleLeftNavbarOptions.test.tsx | 99 +++++++++++ .../getHeaderWithTitleLeftNavbarOptions.tsx | 34 ++++ .../HeaderWithTitleLeft/index.ts | 1 + ...hTitleLeftScrollableNavbarOptions.test.tsx | 167 ++++++++++++++++++ ...erWithTitleLeftScrollableNavbarOptions.tsx | 38 ++++ .../HeaderWithTitleLeftScrollable/index.ts | 1 + 9 files changed, 488 insertions(+) create mode 100644 app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.test.tsx create mode 100644 app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.tsx create mode 100644 app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.test.tsx create mode 100644 app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.tsx create mode 100644 app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.test.tsx create mode 100644 app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.tsx diff --git a/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.test.tsx b/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.test.tsx new file mode 100644 index 00000000000..924722f4345 --- /dev/null +++ b/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.test.tsx @@ -0,0 +1,115 @@ +// Third party dependencies. +import React from 'react'; +import { render, fireEvent } from '@testing-library/react-native'; + +// Internal dependencies. +import getHeaderCenterNavbarOptions from './getHeaderCenterNavbarOptions'; + +const TITLE_TEST_ID = 'header-center-title'; +const BACK_BUTTON_TEST_ID = 'header-center-back-button'; +const CLOSE_BUTTON_TEST_ID = 'header-center-close-button'; + +describe('getHeaderCenterNavbarOptions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('return value', () => { + it('returns object with header function', () => { + const options = getHeaderCenterNavbarOptions({ title: 'Test' }); + + expect(options).toHaveProperty('header'); + expect(typeof options.header).toBe('function'); + }); + + it('returns React element when header function is called', () => { + const options = getHeaderCenterNavbarOptions({ title: 'Test' }); + + const headerElement = options.header(); + + expect(React.isValidElement(headerElement)).toBe(true); + }); + }); + + describe('rendering', () => { + it('renders HeaderCenter with title', () => { + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + titleProps: { testID: TITLE_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId(TITLE_TEST_ID)).toBeOnTheScreen(); + }); + + it('renders HeaderCenter with back button', () => { + const onBack = jest.fn(); + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId(BACK_BUTTON_TEST_ID)).toBeOnTheScreen(); + }); + + it('renders HeaderCenter with close button', () => { + const onClose = jest.fn(); + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + onClose, + closeButtonProps: { testID: CLOSE_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId(CLOSE_BUTTON_TEST_ID)).toBeOnTheScreen(); + }); + }); + + describe('props forwarding', () => { + it('forwards onBack callback to HeaderCenter', () => { + const onBack = jest.fn(); + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + fireEvent.press(getByTestId(BACK_BUTTON_TEST_ID)); + + expect(onBack).toHaveBeenCalledTimes(1); + }); + + it('forwards onClose callback to HeaderCenter', () => { + const onClose = jest.fn(); + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + onClose, + closeButtonProps: { testID: CLOSE_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + fireEvent.press(getByTestId(CLOSE_BUTTON_TEST_ID)); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('forwards testID to HeaderCenter container', () => { + const options = getHeaderCenterNavbarOptions({ + title: 'Settings', + testID: 'custom-header', + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId('custom-header')).toBeOnTheScreen(); + }); + }); +}); diff --git a/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.tsx b/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.tsx new file mode 100644 index 00000000000..5df40dcd7f9 --- /dev/null +++ b/app/component-library/components-temp/HeaderCenter/getHeaderCenterNavbarOptions.tsx @@ -0,0 +1,32 @@ +// Third party dependencies. +import React from 'react'; + +// Internal dependencies. +import HeaderCenter from './HeaderCenter'; +import { HeaderCenterProps } from './HeaderCenter.types'; + +/** + * Returns React Navigation screen options with a HeaderCenter component. + * + * @example + * ```tsx + * const options = getHeaderCenterNavbarOptions({ + * title: 'Settings', + * onBack: () => navigation.goBack(), + * onClose: () => navigation.pop(), + * includesTopInset: true, + * }); + * + * + * ``` + * + * @param options - Props to pass to the HeaderCenter component. + * @returns React Navigation screen options object with header property. + */ +const getHeaderCenterNavbarOptions = ( + options: HeaderCenterProps, +): { header: () => React.ReactElement } => ({ + header: () => , +}); + +export default getHeaderCenterNavbarOptions; diff --git a/app/component-library/components-temp/HeaderCenter/index.ts b/app/component-library/components-temp/HeaderCenter/index.ts index dc7c94a28e5..f6d9784b349 100644 --- a/app/component-library/components-temp/HeaderCenter/index.ts +++ b/app/component-library/components-temp/HeaderCenter/index.ts @@ -1,2 +1,3 @@ export { default } from './HeaderCenter'; +export { default as getHeaderCenterNavbarOptions } from './getHeaderCenterNavbarOptions'; export type { HeaderCenterProps } from './HeaderCenter.types'; diff --git a/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.test.tsx b/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.test.tsx new file mode 100644 index 00000000000..4337a234617 --- /dev/null +++ b/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.test.tsx @@ -0,0 +1,99 @@ +// Third party dependencies. +import React from 'react'; +import { render, fireEvent } from '@testing-library/react-native'; + +// Internal dependencies. +import getHeaderWithTitleLeftNavbarOptions from './getHeaderWithTitleLeftNavbarOptions'; + +const CONTAINER_TEST_ID = 'header-with-title-left-container'; +const BACK_BUTTON_TEST_ID = 'header-with-title-left-back-button'; + +describe('getHeaderWithTitleLeftNavbarOptions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('return value', () => { + it('returns object with header function', () => { + const options = getHeaderWithTitleLeftNavbarOptions({}); + + expect(options).toHaveProperty('header'); + expect(typeof options.header).toBe('function'); + }); + + it('returns React element when header function is called', () => { + const options = getHeaderWithTitleLeftNavbarOptions({}); + + const headerElement = options.header(); + + expect(React.isValidElement(headerElement)).toBe(true); + }); + }); + + describe('rendering', () => { + it('renders HeaderWithTitleLeft with titleLeftProps', () => { + const options = getHeaderWithTitleLeftNavbarOptions({ + testID: CONTAINER_TEST_ID, + titleLeftProps: { + title: 'NFT Name', + topLabel: 'Collection', + }, + }); + const Header = options.header; + const { getByTestId, getByText } = render(
); + + expect(getByTestId(CONTAINER_TEST_ID)).toBeOnTheScreen(); + expect(getByText('NFT Name')).toBeOnTheScreen(); + expect(getByText('Collection')).toBeOnTheScreen(); + }); + + it('renders HeaderWithTitleLeft with back button', () => { + const onBack = jest.fn(); + const options = getHeaderWithTitleLeftNavbarOptions({ + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId(BACK_BUTTON_TEST_ID)).toBeOnTheScreen(); + }); + }); + + describe('props forwarding', () => { + it('forwards onBack callback to HeaderWithTitleLeft', () => { + const onBack = jest.fn(); + const options = getHeaderWithTitleLeftNavbarOptions({ + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + const { getByTestId } = render(
); + + fireEvent.press(getByTestId(BACK_BUTTON_TEST_ID)); + + expect(onBack).toHaveBeenCalledTimes(1); + }); + + it('forwards testID to HeaderWithTitleLeft container', () => { + const options = getHeaderWithTitleLeftNavbarOptions({ + testID: 'custom-header', + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId('custom-header')).toBeOnTheScreen(); + }); + + it('forwards titleSectionTestID to title section', () => { + const options = getHeaderWithTitleLeftNavbarOptions({ + titleLeftProps: { title: 'Title' }, + titleSectionTestID: 'title-section', + }); + const Header = options.header; + const { getByTestId } = render(
); + + expect(getByTestId('title-section')).toBeOnTheScreen(); + }); + }); +}); diff --git a/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.tsx b/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.tsx new file mode 100644 index 00000000000..1dc2c033474 --- /dev/null +++ b/app/component-library/components-temp/HeaderWithTitleLeft/getHeaderWithTitleLeftNavbarOptions.tsx @@ -0,0 +1,34 @@ +// Third party dependencies. +import React from 'react'; + +// Internal dependencies. +import HeaderWithTitleLeft from './HeaderWithTitleLeft'; +import { HeaderWithTitleLeftProps } from './HeaderWithTitleLeft.types'; + +/** + * Returns React Navigation screen options with a HeaderWithTitleLeft component. + * + * @example + * ```tsx + * const options = getHeaderWithTitleLeftNavbarOptions({ + * onBack: () => navigation.goBack(), + * titleLeftProps: { + * title: 'NFT Name', + * topLabel: 'Collection', + * }, + * includesTopInset: true, + * }); + * + * + * ``` + * + * @param options - Props to pass to the HeaderWithTitleLeft component. + * @returns React Navigation screen options object with header property. + */ +const getHeaderWithTitleLeftNavbarOptions = ( + options: HeaderWithTitleLeftProps, +): { header: () => React.ReactElement } => ({ + header: () => , +}); + +export default getHeaderWithTitleLeftNavbarOptions; diff --git a/app/component-library/components-temp/HeaderWithTitleLeft/index.ts b/app/component-library/components-temp/HeaderWithTitleLeft/index.ts index 6a5de15f41f..4ab67645150 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeft/index.ts +++ b/app/component-library/components-temp/HeaderWithTitleLeft/index.ts @@ -1,2 +1,3 @@ export { default } from './HeaderWithTitleLeft'; +export { default as getHeaderWithTitleLeftNavbarOptions } from './getHeaderWithTitleLeftNavbarOptions'; export type { HeaderWithTitleLeftProps } from './HeaderWithTitleLeft.types'; diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.test.tsx b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.test.tsx new file mode 100644 index 00000000000..34d61ee5a49 --- /dev/null +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.test.tsx @@ -0,0 +1,167 @@ +// Third party dependencies. +import React from 'react'; +import { render, fireEvent } from '@testing-library/react-native'; +import { useSharedValue, SharedValue } from 'react-native-reanimated'; + +// Internal dependencies. +import getHeaderWithTitleLeftScrollableNavbarOptions from './getHeaderWithTitleLeftScrollableNavbarOptions'; + +// Mock react-native-reanimated +jest.mock('react-native-reanimated', () => ({ + useSharedValue: jest.fn((initial) => ({ value: initial })), + useAnimatedStyle: jest.fn(() => ({})), + useDerivedValue: jest.fn((fn) => ({ value: fn() })), + withTiming: jest.fn((value) => value), + interpolate: jest.fn((_value, _inputRange, outputRange) => outputRange[0]), + Extrapolation: { CLAMP: 'clamp' }, + default: { + View: 'Animated.View', + }, +})); + +const CONTAINER_TEST_ID = 'header-scrollable-container'; +const BACK_BUTTON_TEST_ID = 'header-scrollable-back-button'; + +// Test wrapper component that provides scrollY +const TestWrapper: React.FC<{ + children: (scrollYValue: SharedValue) => React.ReactNode; +}> = ({ children }) => { + const scrollYValue = useSharedValue(0); + return <>{children(scrollYValue)}; +}; + +describe('getHeaderWithTitleLeftScrollableNavbarOptions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('return value', () => { + it('returns object with header function', () => { + const mockScrollY = { value: 0 }; + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: mockScrollY as SharedValue, + }); + + expect(options).toHaveProperty('header'); + expect(typeof options.header).toBe('function'); + }); + + it('returns React element when header function is called', () => { + const mockScrollY = { value: 0 }; + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: mockScrollY as SharedValue, + }); + + const headerElement = options.header(); + + expect(React.isValidElement(headerElement)).toBe(true); + }); + }); + + describe('rendering', () => { + it('renders HeaderWithTitleLeftScrollable with title', () => { + const { getAllByText, getByTestId } = render( + + {(scrollYValue) => { + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: scrollYValue, + testID: CONTAINER_TEST_ID, + }); + const Header = options.header; + return
; + }} + , + ); + + expect(getByTestId(CONTAINER_TEST_ID)).toBeOnTheScreen(); + expect(getAllByText('Send').length).toBeGreaterThan(0); + }); + + it('renders HeaderWithTitleLeftScrollable with back button', () => { + const onBack = jest.fn(); + const { getByTestId } = render( + + {(scrollYValue) => { + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: scrollYValue, + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + return
; + }} + , + ); + + expect(getByTestId(BACK_BUTTON_TEST_ID)).toBeOnTheScreen(); + }); + + it('renders HeaderWithTitleLeftScrollable with titleLeftProps', () => { + const { getByText } = render( + + {(scrollYValue) => { + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: scrollYValue, + titleLeftProps: { + topLabel: 'Sending', + bottomLabel: 'To 0x123...', + }, + }); + const Header = options.header; + return
; + }} + , + ); + + expect(getByText('Sending')).toBeOnTheScreen(); + expect(getByText('To 0x123...')).toBeOnTheScreen(); + }); + }); + + describe('props forwarding', () => { + it('forwards onBack callback to HeaderWithTitleLeftScrollable', () => { + const onBack = jest.fn(); + const { getByTestId } = render( + + {(scrollYValue) => { + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: scrollYValue, + onBack, + backButtonProps: { testID: BACK_BUTTON_TEST_ID }, + }); + const Header = options.header; + return
; + }} + , + ); + + fireEvent.press(getByTestId(BACK_BUTTON_TEST_ID)); + + expect(onBack).toHaveBeenCalledTimes(1); + }); + + it('forwards testID to HeaderWithTitleLeftScrollable container', () => { + const { getByTestId } = render( + + {(scrollYValue) => { + const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + title: 'Send', + scrollY: scrollYValue, + testID: 'custom-scrollable-header', + }); + const Header = options.header; + return
; + }} + , + ); + + expect(getByTestId('custom-scrollable-header')).toBeOnTheScreen(); + }); + }); +}); diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.tsx b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.tsx new file mode 100644 index 00000000000..50d1f7f6d3b --- /dev/null +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/getHeaderWithTitleLeftScrollableNavbarOptions.tsx @@ -0,0 +1,38 @@ +// Third party dependencies. +import React from 'react'; + +// Internal dependencies. +import HeaderWithTitleLeftScrollable from './HeaderWithTitleLeftScrollable'; +import { HeaderWithTitleLeftScrollableProps } from './HeaderWithTitleLeftScrollable.types'; + +/** + * Returns React Navigation screen options with a HeaderWithTitleLeftScrollable component. + * + * @example + * ```tsx + * const { scrollY, onScroll, expandedHeight, setExpandedHeight } = useHeaderWithTitleLeftScrollable(); + * + * const options = getHeaderWithTitleLeftScrollableNavbarOptions({ + * title: 'Send', + * scrollY, + * onBack: () => navigation.goBack(), + * onExpandedHeightChange: setExpandedHeight, + * titleLeftProps: { + * title: '$4.42', + * topLabel: 'Send', + * }, + * }); + * + * + * ``` + * + * @param options - Props to pass to the HeaderWithTitleLeftScrollable component. + * @returns React Navigation screen options object with header property. + */ +const getHeaderWithTitleLeftScrollableNavbarOptions = ( + options: HeaderWithTitleLeftScrollableProps, +): { header: () => React.ReactElement } => ({ + header: () => , +}); + +export default getHeaderWithTitleLeftScrollableNavbarOptions; diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/index.ts b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/index.ts index 3277b02bcb7..faf40fcc134 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/index.ts +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/index.ts @@ -1,5 +1,6 @@ export { default } from './HeaderWithTitleLeftScrollable'; export { default as useHeaderWithTitleLeftScrollable } from './useHeaderWithTitleLeftScrollable'; +export { default as getHeaderWithTitleLeftScrollableNavbarOptions } from './getHeaderWithTitleLeftScrollableNavbarOptions'; export type { HeaderWithTitleLeftScrollableProps, UseHeaderWithTitleLeftScrollableOptions,