Skip to content

chore: Added HeaderBase to DSR and DSRN#908

Open
brianacnguyen wants to merge 11 commits intomainfrom
migrate/header-base
Open

chore: Added HeaderBase to DSR and DSRN#908
brianacnguyen wants to merge 11 commits intomainfrom
migrate/header-base

Conversation

@brianacnguyen
Copy link
Contributor

@brianacnguyen brianacnguyen commented Feb 17, 2026

Description

This PR adds the HeaderBase component to both @metamask/design-system-react (web) and @metamask/design-system-react-native (mobile).

Reason for the change: A shared, reusable header component is needed across MetaMask products to keep a consistent layout (title with optional start/end accessories) and to avoid duplicating header logic.

What’s included:

  • HeaderBase in both packages: flexible header with an optional start accessory, center title (children), and optional end accessory. Supports custom content via startAccessory/endAccessory or built-in ButtonIcon usage via startButtonIconProps/endButtonIconProps.
  • Web (DSR): Uses Box, Text, ButtonIcon; supports className, style, includesTopInset (padding), and data-testid via testID.
  • Mobile (DSRN): Uses Box, Text, ButtonIcon, useSafeAreaInsets for optional top safe area inset (includesTopInset); supports twClassName, style, and layout balancing so the title stays centered when accessories have different widths.
  • Exports added in both packages; Storybook (React Native) updated to include HeaderBase stories. Tests and documentation (README/README.mdx) added for both implementations.

Related issues

Fixes: N/A (or add ticket link if applicable)

Manual testing steps

  1. Web: Run yarn storybook and open HeaderBase under the design-system-react components. Confirm default, with start/end ButtonIcons, with custom accessories, and with top inset variants.
  2. React Native: Run yarn storybook:ios or yarn storybook:android and open HeaderBase under the design-system-react-native components. Confirm the same variants and that the header respects safe area when includesTopInset is true.
  3. Confirm existing tests pass: yarn jest (or run tests for the two packages).

Screenshots/Recordings

Before

N/A — new component; no prior header in the design system packages.

After

DSR

Screen.Recording.2026-02-16.at.5.01.38.PM.mov

DSRN

Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2026-02-16.at.15.59.59.mov

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I've included tests if applicable
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR (see labeling guidelines). 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

Medium Risk
Adds a new cross-package UI component and updates RN Jest transforms/mocks and testing-library versions, which can cause subtle test or layout regressions despite being largely additive.

Overview
Adds a new HeaderBase component to both the web (design-system-react) and React Native (design-system-react-native) design system packages, providing a centered title area with optional start/end accessories or ButtonIcon-driven actions, optional safe-area top inset handling, and standard test IDs.

Includes Storybook stories and docs for both implementations, adds comprehensive unit tests, exports the component from each package’s components/index.ts, and updates the RN testing setup (Jest transform/mocks) plus bumps @testing-library/react-native to ^13.2.0 (and corresponding lockfile changes) to support the new safe-area dependency usage.

Written by Cursor Bugbot for commit a957f35. This will update automatically on new commits. Configure here.

@brianacnguyen brianacnguyen self-assigned this Feb 17, 2026
@brianacnguyen brianacnguyen requested a review from a team as a code owner February 17, 2026 00:10
@github-actions
Copy link
Contributor

📖 Storybook Preview

<View
style={[
tw.style(resolvedTwClassName),
includesTopInset && { marginTop: insets.top },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top inset uses outer margin

Medium Severity

When includesTopInset is enabled, HeaderBase applies marginTop: insets.top instead of internal top padding. This moves the whole header down rather than expanding it, which breaks safe-area behavior and can create unintended spacing gaps in parent layouts.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@socket-security
Copy link

socket-security bot commented Feb 17, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​testing-library/​react-native@​12.9.0 ⏵ 13.3.39910010098 +1100

View full report

@brianacnguyen brianacnguyen mentioned this pull request Feb 17, 2026
7 tasks
@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

@github-actions
Copy link
Contributor

📖 Storybook Preview

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

>
{renderEndContent()}
</View>
</View>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapper style overridden by spread props

Medium Severity

startAccessoryWrapperProps and endAccessoryWrapperProps are spread after the wrapper View’s own style, so a consumer-provided style in those props overwrites the internal width/alignment styles used to keep the title centered, causing layout balancing to break when wrapper styles are customized.

Fix in Cursor Fix in Web

Copy link
Contributor

@georgewrmarshall georgewrmarshall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. Left some comments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove image as it can become out dated

Comment on lines +41 to +47
export const TwClassName: Story = {
render: () => (
<HeaderBase twClassName="bg-info-default px-4">
Header with Custom Styles
</HeaderBase>
),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need this story for the twClassName prop just let the readme template explain it. If this was created by the cursor rule, let's update the rule to exclude these props that are taken care of by readme

),
};

export const EndButtonIconProps: Story = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can be a bit more selective on stories and improve the cursor rule here to exclude prop object, twClassName and style props

Comment on lines +31 to +36
const START_ACCESSORY_TEST_ID = 'start-accessory-wrapper';
const END_ACCESSORY_TEST_ID = 'end-accessory-wrapper';
const BUTTON_ICON_TEST_ID = 'button-icon';
const CUSTOM_CONTENT_TEST_ID = 'custom-content';
const START_CONTENT_TEST_ID = 'start-content';
const END_CONTENT_TEST_ID = 'end-content';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this over keeping the test id consts in the const file.

includesTopInset = false,
startAccessoryWrapperProps,
endAccessoryWrapperProps,
testID = HEADERBASE_TEST_ID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never set a default testID, will add this to the cursor rules and check the component script

Comment on lines +145 to +157
<Box twClassName="flex-1 items-center">
{typeof children === 'string' ? (
<Text
variant={HEADERBASE_TITLE_TEXT_VARIANT}
testID={HEADERBASE_TITLE_TEST_ID}
style={tw.style('text-center')}
>
{children}
</Text>
) : (
children
)}
</Box>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure where we got to on this pattern but I think it's likely better to provide a title prop than do this type checking and rendering.

*
* @default false
*/
includesTopInset?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this in react?

/**
* Optional test ID for the header container (maps to data-testid).
*/
testID?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use 'data-testid'?: string; here

georgewrmarshall added a commit that referenced this pull request Feb 18, 2026
Added "Anti-Patterns to Avoid" section with 3 key patterns identified in PR reviews:

1. SAMPLE_PROPS constants - Use component default parameters instead
2. Default test IDs - No defaults, consumer provides when needed, React uses data-testid
3. String children type checking - Use explicit props or require Text composition

Also condensed Benefits and Quick Decision Rules sections to keep file at 398 lines (within 200-400 target).

References:
- PR #909 review: #909 (review)
- PR #908 discussion: #908 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants