Skip to content

feat: migrate BannerBase to MMDS react#961

Merged
georgewrmarshall merged 2 commits into
mainfrom
banner-base-react
Mar 6, 2026
Merged

feat: migrate BannerBase to MMDS react#961
georgewrmarshall merged 2 commits into
mainfrom
banner-base-react

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Description

  • Migrates BannerBase into MMDS for @metamask/design-system-react.
  • Adds shared cross-platform props in @metamask/design-system-shared via BannerBasePropsShared (ADR-0004 layering).
  • Implements BannerBase using MMDS primitives (Box, Text, Button, ButtonIcon) with default primary action button behavior.
  • Adds React stories, README documentation, tests, and exports.
  • Aligns Storybook/docs conventions with component-documentation rules (prop-focused stories, handler-specific action story, prop-object guidance).

Scope note:

  • This PR is intentionally scoped to React + shared types to make review easier.
  • React Native parity is tracked in the corresponding RN PR.

Related issues

Fixes:

  • DSYS-321
  • DSYS-299

Manual testing steps

  1. Run yarn build from repo root.
  2. Run yarn test from repo root.
  3. Run yarn lint from repo root.
  4. Open React Storybook and verify BannerBase stories:
    • Default, Title, Description, Children, ActionButtonOnClick, OnClose, StartAccessory.
  5. Confirm only Default and ActionButtonOnClick render an action button.

Screenshots/Recordings

Before

BannerBase in extension

bannerbase.ext720.mov

BannerBase in mobile

bannerbasemobile.720.mov

After

  • BannerBase is available and documented in @metamask/design-system-react.
  • Shared BannerBase props are available in @metamask/design-system-shared.
  • Reusing the same parity/reference assets as the RN migration PR.

Stories

storybook720.mov

READMES

docs720.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

Low Risk
Additive change that introduces a new exported component plus stories/tests/docs; low risk beyond potential minor API/export or styling regressions in consumers that adopt it.

Overview
Introduces a new BannerBase component built from MMDS primitives (Box, Text, Button, ButtonIcon), including optional title/description/body content, a start accessory slot, an optional close affordance, and a conditional action button that only renders when actionButtonOnClick is provided.

Adds BannerBaseProps (layered on shared BannerBasePropsShared), Storybook stories + MDX README docs, and a unit test suite covering rendering variants and click handling. Exports BannerBase from the component barrel (components/index.ts).

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

@github-actions

github-actions Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

>
{startAccessory}

<Box className="min-w-0 flex-1">

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can probably improve some nesting here in the polish stages

@georgewrmarshall georgewrmarshall self-assigned this Mar 5, 2026
@georgewrmarshall georgewrmarshall marked this pull request as ready for review March 5, 2026 22:05
@georgewrmarshall georgewrmarshall requested a review from a team as a code owner March 5, 2026 22:05

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Shared package imports react without declaring dependency
    • Added react as a peerDependency in @metamask/design-system-shared/package.json to satisfy ReactNode type references.

Create PR

Or push these changes by commenting:

@cursor push 71fe057fff
Preview (71fe057fff)
diff --git a/packages/design-system-shared/package.json b/packages/design-system-shared/package.json
--- a/packages/design-system-shared/package.json
+++ b/packages/design-system-shared/package.json
@@ -57,6 +57,9 @@
     "ts-jest": "^29.2.5",
     "typescript": "~5.2.2"
   },
+  "peerDependencies": {
+    "react": "^17.0.0 || ^18.0.0"
+  },
   "engines": {
     "node": "^18.18 || >=20"
   },

diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -3473,6 +3473,8 @@
     jest: "npm:^29.7.0"
     ts-jest: "npm:^29.2.5"
     typescript: "npm:~5.2.2"
+  peerDependencies:
+    react: ^17.0.0 || ^18.0.0
   languageName: unknown
   linkType: soft

} = closeButtonProps ?? {};

const shouldShowCloseButton = Boolean(onClose || closeButtonProps);
const shouldShowActionButton = Boolean(actionButtonOnClick);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Action rendering is intentionally keyed off the handler rather than label presence so a label-only prop does not create an inert button. This keeps runtime behavior aligned with the conditional requirement documented in the API.

))}

{hasContent(description) && (
<Box className={hasContent(title) ? 'mt-1' : undefined}>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Using hasContent for both rendering and spacing avoids the numeric zero edge case where title exists but is falsy. This keeps text wrapping and vertical rhythm consistent for number-based content.

/**
* Optional test id for the close button element.
*/
'data-testid'?: string;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The close-button test selector is explicit on closeButtonProps so consumers can opt in to testing hooks without BannerBase baking in a fixed id. This keeps the base component less opinionated while still supporting stable tests.

closeButtonProps?: BannerBaseCloseButtonProps;
};

type BannerBaseActionPropsWithHandler = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This discriminated union encodes the runtime rule that a label is required when the click handler exists, so misuse is surfaced at compile time instead of silently producing incomplete UI.

@georgewrmarshall georgewrmarshall enabled auto-merge (squash) March 6, 2026 00:54
@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit c6f1e5a into main Mar 6, 2026
43 checks passed
@georgewrmarshall georgewrmarshall deleted the banner-base-react branch March 6, 2026 01:02
expect(screen.getByText('Body copy')).toBeInTheDocument();
});

it('renders numeric title, description, and children', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These numeric content assertions protect the non-obvious edge case where 0 can bypass truthy checks and skip Text wrapping. Keeping this in tests prevents regressions that are easy to miss in visual review.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

onClick={onClose ?? closeButtonPropsOnClick}
{...resolvedCloseButtonProps}
/>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Close button spread can override explicit size prop

Low Severity

The {...resolvedCloseButtonProps} spread on ButtonIcon comes after the explicit size={ButtonIconSize.Sm} prop. Since BannerBaseCloseButtonProps only omits iconName and onClick from ButtonIconProps, properties like size, variant, and isDisabled remain in resolvedCloseButtonProps and silently override the component's hardcoded defaults. The same pattern applies to the action Button where {...resolvedActionButtonProps} follows size={ButtonSize.Md} and onClick={actionButtonOnClick} — while onClick is type-omitted, size is not and can override the default.

Fix in Cursor Fix in Web

@georgewrmarshall georgewrmarshall mentioned this pull request Mar 10, 2026
6 tasks
georgewrmarshall added a commit that referenced this pull request Mar 10, 2026
## Release 25.0.0

This release includes new components migrated from Extension and Mobile,
breaking API improvements to ButtonIcon and Input components, and
continued ADR-0003/0004 type migrations.

### 📦 Package Versions

- `@metamask/design-system-shared`: **0.4.0**
- `@metamask/design-system-react`: **0.11.0**
- `@metamask/design-system-react-native`: **0.11.0**

### 🔄 Shared Type Updates (0.4.0)

#### Component Type Additions (#964, #955)

Added shared types for newly migrated components following ADR-0003 and
ADR-0004 patterns:

**What Changed:**
- Added `ButtonFilter` shared types with `ButtonFilterVariant` const
object and `ButtonFilterPropsShared`
- Added `BannerBase` shared types with `BannerBaseSeverity` const object
and `BannerBasePropsShared`

**Impact:**
- Enables consistent ButtonFilter and BannerBase implementations across
React and React Native
- Continues ADR-0003/0004 const-object + string-union pattern adoption

### 🌐 React Web Updates (0.11.0)

#### Added
- Added `ButtonFilter` component for filter button functionality (#964)
- Added `BannerBase` component for creating custom banner notifications
(#961)

#### Changed
- **BREAKING:** Updated `ButtonIcon` API to use `variant` prop instead
of `isInverse` and `isFloating` boolean props (#948)
- Updated `Ai` icon to filled version for visual consistency (#970)

### 📱 React Native Updates (0.11.0)

#### Added
- Added `ButtonFilter` component (#964)
- Added `BottomSheet` component for modal interactions (#963)
- Added `MainActionButton` component for primary CTAs (#952)
- Added `BannerBase` component for custom banners (#955)
- Added `ListItem` component for standardized list rows (#958)
- Added `TabEmptyState` component for empty states (#949)
- Added `BottomSheetDialog` component for dialogs (#905)

#### Changed
- **BREAKING:** Updated `ButtonIcon` API to use `variant` prop instead
of `isInverse` and `isFloating` boolean props (#948)
- **BREAKING:** `Input` component now requires `value` prop and is
controlled-only (#960)
- Updated `Ai` icon to filled version (#970)

#### Fixed
- Fixed iOS placeholder alignment issue in `Input` component (#960)
- Fixed missing component exports (#967)

### ⚠️ Breaking Changes

#### ButtonIcon Variant Prop (Both Platforms)

**What Changed:**
- Removed `isInverse` and `isFloating` boolean props
- Added `variant` prop with three options:
  - `ButtonIconVariant.Default` (transparent background - default)
- `ButtonIconVariant.Filled` (muted background with rounded corners -
new)
- `ButtonIconVariant.Floating` (colored background with inverse icon -
replaces `isFloating`)

**Migration:**
```tsx
// Before
<ButtonIcon name={IconName.Add} isFloating />

// After
<ButtonIcon name={IconName.Add} variant={ButtonIconVariant.Floating} />
```

See migration guides for complete instructions:
- [React Migration
Guide](./packages/design-system-react/MIGRATION.md#from-version-0100-to-0110)
- [React Native Migration
Guide](./packages/design-system-react-native/MIGRATION.md#from-version-0100-to-0110)

#### Input Controlled-Only (React Native Only)

**What Changed:**
- Removed `defaultValue` prop
- `value` prop is now required
- All Input instances must be controlled with state management

**Migration:**
```tsx
// Before
<Input placeholder="Enter text" defaultValue="Initial" onChange={handleChange} />

// After
const [text, setText] = useState('Initial');
<Input placeholder="Enter text" value={text} onChange={setText} />
```

**Impact:**
- Affects `Input` and `TextField` components
- Provides consistent behavior and fixes iOS placeholder alignment

See [React Native Migration
Guide](./packages/design-system-react-native/MIGRATION.md#from-version-0100-to-0110)
for complete instructions.

### ✅ Checklist

- [x] Changelogs updated with human-readable descriptions
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] Version bumps follow semantic versioning
  - design-system-shared: minor (0.3.0 → 0.4.0) - new shared types
- design-system-react: minor (0.10.0 → 0.11.0) - new components +
breaking ButtonIcon change
- design-system-react-native: minor (0.10.0 → 0.11.0) - new components +
breaking ButtonIcon and Input changes
- [x] Breaking changes documented with migration guidance
- [x] Migration guides updated with before/after examples
- [x] PR references included in changelog entries

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Although this PR is mostly versioning/docs, it formalizes breaking
public API changes (`ButtonIcon` and RN `Input`) that will require
downstream updates and could cause consumer build failures if missed.
> 
> **Overview**
> Bumps the monorepo release to `25.0.0` and publishes new package
versions (`@metamask/design-system-react@0.11.0`,
`@metamask/design-system-react-native@0.11.0`,
`@metamask/design-system-shared@0.4.0`) with updated changelogs.
> 
> Documents newly added components/types (`ButtonFilter`, `BannerBase`,
plus several React Native-only additions like `BottomSheet`, `ListItem`,
etc.) and **breaking API changes**: `ButtonIcon` replaces
`isInverse`/`isFloating` with a `variant` prop (React + RN), and React
Native `Input` becomes controlled-only (requires `value`, removes
`defaultValue`). Migration guides are updated with before/after examples
and new compare links.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0148a27. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants