Skip to content

Commit f7fb00d

Browse files
chore: Added all headers to components temp (#25751)
## **Description** This PR adds a comprehensive set of header components to `components-temp` as part of the ongoing header component library development. These components provide flexible, reusable header patterns for various screen layouts including collapsible scroll-linked headers and static stacked headers. **Components added:** 1. **HeaderCollapsible** - Base collapsing header that smoothly transitions between expanded and compact states based on scroll position using Reanimated for performant scroll-linked animations. Includes a custom hook (`useHeaderCollapsible`) for easy ScrollView integration. 2. **HeaderCollapsibleStandard** - Extends HeaderCollapsible with TitleStandard content in the expanded section, suitable for main/top-level screens. 3. **HeaderCollapsibleSubpage** - Extends HeaderCollapsible with TitleSubpage content in the expanded section, suitable for secondary/nested screens. 4. **HeaderCompactSearch** - A compact search header with two variants: - `Screen` variant: Renders a back button (ArrowLeft) on the left side - `Inline` variant: Renders a cancel button on the right side 5. **HeaderStackedStandard** - A static (non-collapsible) header that combines HeaderBase with TitleStandard below, including React Navigation integration helper (`getHeaderStackedStandardNavbarOptions`). 6. **HeaderStackedSubpage** - A static header that combines HeaderBase with TitleSubpage below, suitable for secondary/nested screens. All components include full TypeScript support, comprehensive unit tests, and Storybook stories for documentation and visual testing. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/jira/software/c/projects/MDP/boards/2972?assignee=62afb43d33a882e2be47c36f&quickFilter=3325&selectedIssue=MDP-702 ## **Manual testing steps** ```gherkin Feature: Header components in Storybook Scenario: User views HeaderCollapsible in Storybook Given the app is running in Storybook mode When user navigates to HeaderCollapsible stories Then user should see the collapsible header expand and collapse on scroll Scenario: User views HeaderStackedStandard in Storybook Given the app is running in Storybook mode When user navigates to HeaderStackedStandard stories Then user should see a static stacked header with back button and title section Scenario: User views HeaderCompactSearch in Storybook Given the app is running in Storybook mode When user navigates to HeaderCompactSearch stories Then user should see both Screen and Inline search header variants ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A - New components ### **After** HeaderCollapsible https://github.com/user-attachments/assets/e54baf08-d3d7-463d-9f9c-38cfc37f5ad3 HeaderCollapsibleStandard https://github.com/user-attachments/assets/015f7c71-9b41-4671-b016-ca1193761807 HeaderCollapsibleSubpage https://github.com/user-attachments/assets/5bc9dc72-a65d-4cf2-baf1-f8ee50af2f14 HeaderCompactSearch https://github.com/user-attachments/assets/06787417-a2fe-41c3-ac57-520f79ba4003 HeaderStackedStandard https://github.com/user-attachments/assets/0de09449-8be8-41a1-bc0d-47219eecdd54 HeaderStackedSubpage https://github.com/user-attachments/assets/b3446d48-bb02-4ae0-b0df-043e7fddd258 <!-- [screenshots/recordings] --> ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Mostly additive UI work, but introduces new scroll-linked Reanimated behavior and dynamic layout measurement that could cause visual/interaction regressions if adopted by screens. > > **Overview** > Introduces a new set of reusable header components under `components-temp`, including `HeaderCollapsible` (scroll-linked collapsing header with dynamic height measurement + safe-area positioning) and a companion `useHeaderCollapsible` hook. > > Adds wrapper variants `HeaderCollapsibleStandard`/`HeaderCollapsibleSubpage` and static `HeaderStackedStandard`/`HeaderStackedSubpage`, plus a `HeaderCompactSearch` header supporting *screen* (back button) and *inline* (cancel button) modes. Each new component is wired into Storybook and covered by dedicated unit tests. > > Also normalizes `twClassName` handling by defaulting it to `''` in `HeaderCompactStandard`, `TitleStandard`, and `TitleSubpage` and simplifying class concatenation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ed7c2fd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4ba008a commit f7fb00d

35 files changed

Lines changed: 4173 additions & 6 deletions

.storybook/storybook.requires.js

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/* eslint-disable no-console */
2+
import React, { useState, useCallback } from 'react';
3+
import {
4+
View,
5+
ScrollView,
6+
NativeSyntheticEvent,
7+
NativeScrollEvent,
8+
} from 'react-native';
9+
10+
import {
11+
Box,
12+
Text,
13+
TextVariant,
14+
IconName,
15+
} from '@metamask/design-system-react-native';
16+
import { useTailwind } from '@metamask/design-system-twrnc-preset';
17+
import { useSharedValue, SharedValue } from 'react-native-reanimated';
18+
19+
import HeaderCollapsible from './HeaderCollapsible';
20+
import TitleStandard from '../TitleStandard';
21+
22+
const HeaderCollapsibleMeta = {
23+
title: 'Components Temp / HeaderCollapsible',
24+
component: HeaderCollapsible,
25+
argTypes: {
26+
title: {
27+
control: 'text',
28+
},
29+
subtitle: {
30+
control: 'text',
31+
},
32+
twClassName: {
33+
control: 'text',
34+
},
35+
},
36+
};
37+
38+
export default HeaderCollapsibleMeta;
39+
40+
const SampleContent = ({ itemCount = 20 }: { itemCount?: number }) => (
41+
<>
42+
{Array.from({ length: itemCount }).map((_, index) => (
43+
<Box key={index} twClassName="p-4 mb-2 bg-muted rounded-lg mx-4">
44+
<Text variant={TextVariant.BodyMd}>Item {index + 1}</Text>
45+
<Text variant={TextVariant.BodySm}>
46+
This is sample content to demonstrate scrolling behavior.
47+
</Text>
48+
</Box>
49+
))}
50+
</>
51+
);
52+
53+
interface ScrollableStoryContainerProps {
54+
children: (props: {
55+
scrollYValue: SharedValue<number>;
56+
setExpandedHeight: (h: number) => void;
57+
}) => React.ReactNode;
58+
}
59+
60+
const ScrollableStoryContainer = ({
61+
children,
62+
}: ScrollableStoryContainerProps) => {
63+
const tw = useTailwind();
64+
const scrollYValue = useSharedValue(0);
65+
const [expandedHeight, setExpandedHeight] = useState(140);
66+
67+
const onScroll = useCallback(
68+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
69+
scrollYValue.value = event.nativeEvent.contentOffset.y;
70+
},
71+
[scrollYValue],
72+
);
73+
74+
return (
75+
<View style={tw.style('flex-1 bg-default')}>
76+
{children({ scrollYValue, setExpandedHeight })}
77+
<ScrollView
78+
onScroll={onScroll}
79+
scrollEventThrottle={16}
80+
contentContainerStyle={{ paddingTop: expandedHeight }}
81+
showsVerticalScrollIndicator={false}
82+
>
83+
<SampleContent />
84+
</ScrollView>
85+
</View>
86+
);
87+
};
88+
89+
export const Default = {
90+
render: () => (
91+
<ScrollableStoryContainer>
92+
{({ scrollYValue, setExpandedHeight }) => (
93+
<HeaderCollapsible
94+
title="Send"
95+
onBack={() => console.log('Back pressed')}
96+
expandedContent={
97+
<TitleStandard
98+
topLabel="Send"
99+
title="$4.42"
100+
twClassName="px-4 pt-1 pb-3 "
101+
/>
102+
}
103+
scrollY={scrollYValue}
104+
onExpandedHeightChange={setExpandedHeight}
105+
/>
106+
)}
107+
</ScrollableStoryContainer>
108+
),
109+
};
110+
111+
export const WithBottomLabel = {
112+
render: () => (
113+
<ScrollableStoryContainer>
114+
{({ scrollYValue, setExpandedHeight }) => (
115+
<HeaderCollapsible
116+
title="Send"
117+
onBack={() => console.log('Back pressed')}
118+
expandedContent={
119+
<TitleStandard
120+
topLabel="Send"
121+
title="$4.42"
122+
bottomLabel="0.002 ETH"
123+
twClassName="px-4 pt-1 pb-3 "
124+
/>
125+
}
126+
scrollY={scrollYValue}
127+
onExpandedHeightChange={setExpandedHeight}
128+
/>
129+
)}
130+
</ScrollableStoryContainer>
131+
),
132+
};
133+
134+
export const WithSubtitle = {
135+
render: () => (
136+
<ScrollableStoryContainer>
137+
{({ scrollYValue, setExpandedHeight }) => (
138+
<HeaderCollapsible
139+
title="Send"
140+
subtitle="0.002 ETH"
141+
onBack={() => console.log('Back pressed')}
142+
expandedContent={
143+
<TitleStandard
144+
topLabel="Send"
145+
title="$4.42"
146+
twClassName="px-4 pt-1 pb-3 "
147+
/>
148+
}
149+
scrollY={scrollYValue}
150+
onExpandedHeightChange={setExpandedHeight}
151+
/>
152+
)}
153+
</ScrollableStoryContainer>
154+
),
155+
};
156+
157+
export const OnClose = {
158+
render: () => (
159+
<ScrollableStoryContainer>
160+
{({ scrollYValue, setExpandedHeight }) => (
161+
<HeaderCollapsible
162+
title="Send"
163+
onClose={() => console.log('Close pressed')}
164+
expandedContent={
165+
<TitleStandard
166+
topLabel="Send"
167+
title="$4.42"
168+
twClassName="px-4 pt-1 pb-3 "
169+
/>
170+
}
171+
scrollY={scrollYValue}
172+
onExpandedHeightChange={setExpandedHeight}
173+
/>
174+
)}
175+
</ScrollableStoryContainer>
176+
),
177+
};
178+
179+
export const BackAndClose = {
180+
render: () => (
181+
<ScrollableStoryContainer>
182+
{({ scrollYValue, setExpandedHeight }) => (
183+
<HeaderCollapsible
184+
title="Send"
185+
onBack={() => console.log('Back pressed')}
186+
onClose={() => console.log('Close pressed')}
187+
expandedContent={
188+
<TitleStandard
189+
topLabel="Send"
190+
title="$4.42"
191+
twClassName="px-4 pt-1 pb-3 "
192+
/>
193+
}
194+
scrollY={scrollYValue}
195+
onExpandedHeightChange={setExpandedHeight}
196+
/>
197+
)}
198+
</ScrollableStoryContainer>
199+
),
200+
};
201+
202+
export const EndButtonIconProps = {
203+
render: () => (
204+
<ScrollableStoryContainer>
205+
{({ scrollYValue, setExpandedHeight }) => (
206+
<HeaderCollapsible
207+
title="Send"
208+
onBack={() => console.log('Back pressed')}
209+
endButtonIconProps={[
210+
{
211+
iconName: IconName.Close,
212+
onPress: () => console.log('Close pressed'),
213+
},
214+
]}
215+
expandedContent={
216+
<TitleStandard
217+
topLabel="Send"
218+
title="$4.42"
219+
twClassName="px-4 pt-1 pb-3 "
220+
/>
221+
}
222+
scrollY={scrollYValue}
223+
onExpandedHeightChange={setExpandedHeight}
224+
/>
225+
)}
226+
</ScrollableStoryContainer>
227+
),
228+
};
229+
230+
export const BackButtonProps = {
231+
render: () => (
232+
<ScrollableStoryContainer>
233+
{({ scrollYValue, setExpandedHeight }) => (
234+
<HeaderCollapsible
235+
title="Receive"
236+
backButtonProps={{
237+
onPress: () => console.log('Custom back pressed'),
238+
}}
239+
expandedContent={
240+
<TitleStandard
241+
topLabel="Receive"
242+
title="$1,234.56"
243+
twClassName="px-4 pt-1 pb-3 "
244+
/>
245+
}
246+
scrollY={scrollYValue}
247+
onExpandedHeightChange={setExpandedHeight}
248+
/>
249+
)}
250+
</ScrollableStoryContainer>
251+
),
252+
};
253+
254+
export const CustomExpandedContent = {
255+
render: () => (
256+
<ScrollableStoryContainer>
257+
{({ scrollYValue, setExpandedHeight }) => (
258+
<HeaderCollapsible
259+
title="Custom"
260+
onBack={() => console.log('Back pressed')}
261+
expandedContent={
262+
<Box twClassName="px-4 py-2">
263+
<Text variant={TextVariant.HeadingMd}>Custom Title Section</Text>
264+
<Text variant={TextVariant.BodySm}>
265+
This is a completely custom expanded content section
266+
</Text>
267+
</Box>
268+
}
269+
scrollY={scrollYValue}
270+
onExpandedHeightChange={setExpandedHeight}
271+
/>
272+
)}
273+
</ScrollableStoryContainer>
274+
),
275+
};

0 commit comments

Comments
 (0)