Skip to content

feat(rn): add SelectButtonSize and align SelectButton with Figma#1177

Merged
georgewrmarshall merged 9 commits into
mainfrom
feat/dsys-742-select-button-size-alignment
May 20, 2026
Merged

feat(rn): add SelectButtonSize and align SelectButton with Figma#1177
georgewrmarshall merged 9 commits into
mainfrom
feat/dsys-742-select-button-size-alignment

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented May 19, 2026

Copy link
Copy Markdown
Contributor

Description

Adds SelectButtonSize as a first-class const object in @metamask/design-system-shared following ADR-0003/ADR-0004, aligning the SelectButton component's size API with its Figma counterpart.

Previously, SelectButton used ButtonBaseSize directly and defaulted to ButtonBaseSize.Sm. This PR:

  1. Introduces SelectButtonSize (Sm/Md/Lg) in the shared package so consumers have a semantically correct type
  2. Moves size into SelectButtonPropsShared (ADR-0004 centralised types)
  3. Changes the default from Sm to Md to match the Figma design's "Md (default)" label
  4. Updates the Figma component set to reflect the code API (see Figma changes below)

Figma branch: SelectButton — branch 8KPwHulIorYFJiTWK8MapC

Figma changes made on the branch:

  • Added variant property (Primary / Secondary / Tertiary) to the component set
  • Renamed sizes Large / Md (default) / SmallLg / Md / Sm to match code naming
  • Reorganised layout from single vertical column to 3×3 grid (sizes across, variants down)
  • Secondary / Tertiary: cleared background fills (transparent)
  • Tertiary: applied text/alternative design token to label text and arrow icon

Related issues

Fixes: DSYS-742

Manual testing steps

  1. Run yarn workspace @metamask/design-system-react-native test --testPathPattern="SelectButton" — all 36 tests should pass
  2. Run yarn storybook:ios, navigate to Components → SelectButton → Variant and verify three visual variants render correctly (Primary with muted bg, Secondary transparent, Tertiary with alternative text colour)
  3. Verify SelectButtonSize is importable: import { SelectButtonSize } from '@metamask/design-system-react-native'

Screenshots/Recordings

After

  • size defaults to SelectButtonSize.Md (40px / rounded-xl)
  • SelectButtonSize.Sm | Md | Lg exported from both @metamask/design-system-shared and @metamask/design-system-react-native
  • Figma: size × variant 3×3 grid matching code API exactly
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2026-05-19.at.15.00.02.mov
Screen.Recording.2026-05-19.at.3.15.34.PM.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
Medium risk due to public API/type changes around SelectButton sizing (new exported SelectButtonSize, size moved into shared props, and ButtonBase size omitted), which may require consumer updates but has limited runtime impact.

Overview
Adds a first-class SelectButtonSize option set (aliased to ButtonBaseSize) in @metamask/design-system-shared, and re-exports it through design-system-react-native so consumers can use a semantically named size type.

Updates SelectButton’s API/types to use SelectButtonSize (including defaulting size via this new type) and prevents prop-type collisions by omitting ButtonBase’s size from SelectButtonProps. Storybook and docs are refreshed to demonstrate and control the new size prop, and the stories’ wrapper layout is migrated from View+Tailwind to Box props.

Reviewed by Cursor Bugbot for commit 916d358. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds SelectButtonSize (Sm/Md/Lg) as a first-class const object in
design-system-shared following ADR-0003/ADR-0004, moves size into
SelectButtonPropsShared, and changes the default from ButtonBaseSize.Sm
to SelectButtonSize.Md to match the Figma component's "Md (default)" label.

Figma branch 8KPwHulIorYFJiTWK8MapC was updated in parallel:
- Added variant property (Primary/Secondary/Tertiary)
- Renamed sizes Large/Md(default)/Small → Lg/Md/Sm
- Reorganised layout to 3×3 grid
- Secondary/Tertiary: transparent fills; Tertiary: text/alternative token

Relates to DSYS-742
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall self-assigned this May 19, 2026
* SelectButton size — alias of ButtonBaseSize.
* Sm (32px) | Md (40px, default) | Lg (48px).
*/
export const SelectButtonSize = ButtonBaseSize;

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 alias approach mirrors the existing pattern used by ButtonSize and ButtonHeroSize in the same file — both are direct assignments from ButtonBaseSize. Since SelectButton maps 1:1 to ButtonBase sizes with no additional values, aliasing avoids duplicating string literals and guarantees the two types stay in sync automatically.

Omit<
ButtonBaseProps,
'children' | 'endIconName' | 'endIconProps' | 'disabled'
'children' | 'endIconName' | 'endIconProps' | 'disabled' | 'size'

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.

'size' is explicitly omitted from the ButtonBaseProps passthrough here because it is now declared in SelectButtonPropsShared. Without this omission, TypeScript sees two definitions of 'size' in the intersection — one typed as SelectButtonSize and one as ButtonBaseSize — which can cause subtle type widening. The omit keeps the prop owned by exactly one layer.

title: 'Components/SelectButton',
component: SelectButton,
argTypes: {
size: {

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.

Object.keys gives the human-readable label (Sm/Md/Lg) shown in the controls panel, while mapping resolves each label back to the string value ('sm'/'md'/'lg') passed to the component. Using Object.values alone would show raw strings in the panel dropdown, which is less readable on a small device screen.

@georgewrmarshall georgewrmarshall marked this pull request as ready for review May 19, 2026 22:17
@georgewrmarshall georgewrmarshall requested a review from a team as a code owner May 19, 2026 22:17
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@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: SelectButtonSize duplicates literals instead of aliasing ButtonBaseSize
    • Replaced the duplicated SelectButtonSize object with an alias to ButtonBaseSize and added the proper import to keep them in sync.

Create PR

Or push these changes by commenting:

@cursor push 75021c769b
Preview (75021c769b)
diff --git a/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts b/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
--- a/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
+++ b/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
@@ -1,16 +1,12 @@
 import type { ReactNode } from 'react';
+import { ButtonBaseSize } from '../ButtonBase';
 
 /**
  * SelectButton size options (ADR-0003).
  * Sm (32px) | Md (40px, default) | Lg (48px).
  */
-export const SelectButtonSize = {
-  Sm: 'sm',
-  Md: 'md',
-  Lg: 'lg',
-} as const;
-export type SelectButtonSize =
-  (typeof SelectButtonSize)[keyof typeof SelectButtonSize];
+export const SelectButtonSize = ButtonBaseSize;
+export type SelectButtonSize = ButtonBaseSize;
 
 /**
  * SelectButton — trailing arrow direction (maps to platform arrow icons).

You can send follow-ups to the cloud agent here.

Comment thread packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

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

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

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

  • ✅ Fixed: Default size remains Sm instead of intended Md
    • Updated default size to SelectButtonSize.Md in component, and aligned README and JSDoc defaults to Md.

Create PR

Or push these changes by commenting:

@cursor push 80c81b1b3c
Preview (80c81b1b3c)
diff --git a/packages/design-system-react-native/src/components/SelectButton/README.md b/packages/design-system-react-native/src/components/SelectButton/README.md
--- a/packages/design-system-react-native/src/components/SelectButton/README.md
+++ b/packages/design-system-react-native/src/components/SelectButton/README.md
@@ -305,7 +305,7 @@
 
 | TYPE               | REQUIRED | DEFAULT               |
 | ------------------ | -------- | --------------------- |
-| `SelectButtonSize` | No       | `SelectButtonSize.Sm` |
+| `SelectButtonSize` | No       | `SelectButtonSize.Md` |
 
 ```tsx
 import {

diff --git a/packages/design-system-react-native/src/components/SelectButton/SelectButton.tsx b/packages/design-system-react-native/src/components/SelectButton/SelectButton.tsx
--- a/packages/design-system-react-native/src/components/SelectButton/SelectButton.tsx
+++ b/packages/design-system-react-native/src/components/SelectButton/SelectButton.tsx
@@ -24,7 +24,7 @@
   endArrowDirectionIconProps,
   variant = SelectButtonVariant.Primary,
   isLoading = false,
-  size = SelectButtonSize.Sm,
+  size = SelectButtonSize.Md,
   twClassName = '',
   style,
   ...buttonBaseRest

diff --git a/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts b/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
--- a/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
+++ b/packages/design-system-shared/src/types/SelectButton/SelectButton.types.ts
@@ -48,7 +48,7 @@
   /**
    * Height size of the button. Maps 1:1 to ButtonBaseSize values.
    *
-   * @default Sm
+   * @default Md
    */
   size?: SelectButtonSize;
   /**

You can send follow-ups to the cloud agent here.


export const Size: Story = {
render: () => (
<SelectButtonStoryWrapper style={{ gap: 16 }}>

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.

@cursoragent update SelectButtonStoryWrapper to use the Box component and spread props so we can use gap={4} prop here

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.

Taking a look!

Open in Web Open in Cursor 

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.

Updated dad4323

…o enable gap prop (gap={4})

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

…iteral duplication

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

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

Fix All in Cursor

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

  • ✅ Fixed: SelectButtonSize missing from package barrel export
    • Added SelectButtonSize to packages/design-system-react-native/src/components/index.ts so it’s re-exported from the package entry.

Create PR

Or push these changes by commenting:

@cursor push a8d891fc82
Preview (a8d891fc82)
diff --git a/packages/design-system-react-native/src/components/index.ts b/packages/design-system-react-native/src/components/index.ts
--- a/packages/design-system-react-native/src/components/index.ts
+++ b/packages/design-system-react-native/src/components/index.ts
@@ -200,6 +200,7 @@
 export {
   SelectButton,
   SelectButtonEndArrow,
+  SelectButtonSize,
   SelectButtonVariant,
 } from './SelectButton';
 export type { SelectButtonProps } from './SelectButton';

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 2fc8a92. Configure here.

@georgewrmarshall georgewrmarshall enabled auto-merge (squash) May 20, 2026 18:33
@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

@cursor push a8d891f

…tch README and package entry

Applied via @cursor push command
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit 14ea20a into main May 20, 2026
44 checks passed
@georgewrmarshall georgewrmarshall deleted the feat/dsys-742-select-button-size-alignment branch May 20, 2026 19:36
@georgewrmarshall georgewrmarshall mentioned this pull request May 29, 2026
18 tasks
georgewrmarshall added a commit that referenced this pull request May 29, 2026
## Release 42.0.0

This release curates the changelogs for three published packages into
consumer-facing Keep a Changelog entries. It adds the `FlashFilled` icon
and `SelectButtonSize` across platforms, adds the `TextField` component
to React web, and ships two React Native breaking changes: the
`panGestureHandlerProps` removal (part of the
`react-native-gesture-handler` v2 migration) and the removal of the
variant-based title API from `HeaderBase`/`BottomSheetHeader`.

### 📦 Package Versions

- `@metamask/design-system-shared`: **0.20.0** (was 0.19.0)
- `@metamask/design-system-react`: **0.24.0** (was 0.23.1)
- `@metamask/design-system-react-native`: **0.27.0** (was 0.26.0)

> `@metamask/design-tokens`, `@metamask/design-system-tailwind-preset`,
and `@metamask/design-system-twrnc-preset` are unchanged in this
release.

### 🔄 Shared Type Updates (0.20.0)

#### Component Type Additions (#1191, #1177, #1170)

**What Changed:**

- Added `FlashFilled` to the `IconName` const so the filled lightning
bolt is available on both platforms.
- Added `SelectButtonSize` so `SelectButton` exposes a semantic size
type shared across platforms.
- Added `TextFieldPropsShared` for the cross-platform text field input
contract.

**Impact:**

- Additive only — no breaking changes to the shared package.
- Continues the ADR-0003/0004 const-object + centralized-types pattern.

### 🌐 React Web Updates (0.24.0)

#### Added

- Added `TextField` for labeled text entry with optional helper and
validation text, exposing `TextFieldSize` and `TextFieldType` (#1170)
- Added `FlashFilled` icon (filled lightning bolt) to `IconName` (#1191)

### 📱 React Native Updates (0.27.0)

#### Added

- Added `FlashFilled` icon (filled lightning bolt) to `IconName` (#1191)
- Added `SelectButtonSize` so `SelectButton` exposes a semantic size
type (#1177)

#### Changed

- **BREAKING:** Removed `panGestureHandlerProps` from `BottomSheet` and
`BottomSheetDialog` following the migration to the
`react-native-gesture-handler` v2 `GestureDetector`/`Gesture.Pan()` API
(#1165)
- Migration: [From version 0.26.0 to
0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270)
- **BREAKING:** Removed the variant-based title API from `HeaderBase`
and `BottomSheetHeader` — `variant`, `HeaderBaseVariant`,
`BottomSheetHeaderVariant`, and `HeaderBase`'s `titleTestID` (#1103)
- String titles now render with a centered `HeadingSm` treatment; pass
custom `children` for bespoke title layouts and use `textProps.testID`
in place of `titleTestID`
- Migration: [From version 0.26.0 to
0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270)
- Reduced the default `SegmentGroup` segment spacing from `gap-3` to
`gap-1` for tighter grouped segments (#1194)

#### Fixed

- Fixed a `HeaderStandardAnimated` runtime crash under React Native
Reanimated 4 by inlining the scroll-handler worklet (#1185)
- Fixed React Native Web rendering for `BottomSheet`,
`BottomSheetOverlay`, `Icon`, and the animated `ButtonAnimated` and
`Spinner` components (#1187)

### ⚠️ Breaking Changes

#### Removed `panGestureHandlerProps` from `BottomSheet` /
`BottomSheetDialog` (React Native Only)

**What Changed:**

- Removed the `panGestureHandlerProps` prop. The components migrated
from the deprecated RNGH v1 `PanGestureHandler` JSX component to the v2
`GestureDetector` + `Gesture.Pan()` API.
- `simultaneousHandlers` (the only real-world use case) was never wired
up under the old shim, so no working behavior is lost.

**Migration:**

```tsx
// Before (0.26.0)
<BottomSheet
  goBack={goBack}
  panGestureHandlerProps={{ simultaneousHandlers: scrollViewRef }}
>
  {children}
</BottomSheet>

// After (0.27.0)
<BottomSheet goBack={goBack}>{children}</BottomSheet>
```

#### Removed variant-based title API from `HeaderBase` /
`BottomSheetHeader` (React Native Only)

**What Changed:**

- Removed `variant`, `HeaderBaseVariant`, `BottomSheetHeaderVariant`,
and `HeaderBase`'s `titleTestID`.
- String titles now render with a centered `HeadingSm` treatment; custom
layouts use `children`, and `titleTestID` is replaced by
`textProps.testID`.

**Migration:**

```tsx
// Before (0.26.0)
import { HeaderBase, HeaderBaseVariant } from '@metamask/design-system-react-native';
<HeaderBase variant={HeaderBaseVariant.Display} titleTestID="title">
  Account details
</HeaderBase>

// After (0.27.0)
import { HeaderBase } from '@metamask/design-system-react-native';
<HeaderBase textProps={{ testID: 'title' }}>Account details</HeaderBase>
```

See migration guide for complete instructions:

- [React Native Migration Guide — 0.26.0 to
0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270)

### 🔗 Consumer note: MetaMask Mobile

MetaMask Mobile currently consumes
`@metamask/design-system-react-native@0.20.0` and applies a local yarn
patch —
`.yarn/patches/@metamask-design-system-react-native-npm-0.20.0-2ae4d6f1dd.patch`
— to migrate `BottomSheetDialog` from `PanGestureHandler` to
`GestureDetector` for its React Native Reanimated 4 upgrade
([MetaMask/metamask-mobile#29470](MetaMask/metamask-mobile#29470)).

This release **upstreams that exact migration natively** (#1165). Once
mobile bumps to `0.27.0`, it can **drop that yarn patch** — the package
now uses the RNGH v2 `GestureDetector`/`Gesture.Pan()` API on its own.

Compatibility note: the package keeps `react-native-reanimated` at
`peerDependencies: >=3.17.0` (unchanged). It was validated against
mobile's current Reanimated 3.19 and is forward-compatible with the
incoming Reanimated 4.1.x (the #1185 worklet fix works on both), so no
peer-dependency bump is required.

### ✅ Checklist

- [x] Changelogs updated with human-readable descriptions
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] Version bumps follow semantic versioning
- [x] design-system-shared: minor (0.19.0 → 0.20.0) - additive type
exports
- [x] design-system-react: minor (0.23.1 → 0.24.0) - new `TextField`
component + icon
- [x] design-system-react-native: minor (0.26.0 → 0.27.0) - pre-1.0
minor with breaking changes
- [x] Breaking changes documented with migration guidance
- [x] Migration guides updated with before/after examples (if breaking
changes)
- [x] PR references included in changelog entries

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [x] I've reviewed the [Release
Workflow](./.cursor/rules/release-workflow.md) cursor rule
- [ ] All tests pass (`yarn build && yarn test && yarn lint`)
- [x] Changelog validation passes (`yarn changelog:validate`)

## **Pre-merge reviewer checklist**

- [ ] I've reviewed the [Reviewing Release
PRs](./docs/reviewing-release-prs.md) guide
- [ ] Package versions follow semantic versioning
- [ ] Changelog entries are consumer-facing (not commit message
regurgitation)
- [ ] Breaking changes are documented in MIGRATION.md with examples
- [ ] All unreleased changes are accounted for in changelogs

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> No runtime code in the diff, but the published React Native 0.27.0
changelog documents breaking BottomSheet and Header API changes that
require consumer migrations.
> 
> **Overview**
> **Release 42.0.0** bumps the monorepo to **42.0.0** and publishes
**@metamask/design-system-shared@0.20.0**,
**@metamask/design-system-react@0.24.0**, and
**@metamask/design-system-react-native@0.27.0** with finalized Keep a
Changelog entries and compare links.
> 
> The diff is mostly **release packaging**: version fields in root and
package `package.json` files, new changelog sections for those versions,
and doc updates. **React Native** docs drop **`panGestureHandlerProps`**
from `BottomSheet` / `BottomSheetDialog` READMEs; **`MIGRATION.md`**
adds a **0.26.0 → 0.27.0** section (bottom-sheet gesture prop removal,
header title API) and moves **BannerBase** guidance under **0.24.0 →
0.25.0**.
> 
> What consumers get in this release (documented in changelogs, not new
code in this PR): shared **`FlashFilled`**, **`SelectButtonSize`**,
**`TextFieldPropsShared`**; web **`TextField`** and **`FlashFilled`**;
native **`FlashFilled`**, **`SelectButtonSize`**, two **breaking** API
removals (`panGestureHandlerProps`, header **`variant`** /
**`titleTestID`**), tighter **`SegmentGroup`** spacing, Reanimated 4 /
RN Web fixes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6710621. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/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.

3 participants