Skip to content

refactor: migrate BadgeStatus to design-system-shared and union types#912

Merged
georgewrmarshall merged 9 commits into
mainfrom
badge-status-enum--union-type-migration
Feb 23, 2026
Merged

refactor: migrate BadgeStatus to design-system-shared and union types#912
georgewrmarshall merged 9 commits into
mainfrom
badge-status-enum--union-type-migration

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Description

Migrates BadgeStatus component from TypeScript enums to string union types with const objects, following ADR-0003 (enum to const object migration) and ADR-0004 (shared types architecture).

What is the reason for the change?

  1. Structural Typing: Enums use nominal typing which prevents using string literals directly. Const objects with union types enable structural typing, allowing 'active' to be used directly where BadgeStatusStatus.Active is expected.

  2. Cross-Platform Type Sharing: Previously, each platform (React/React Native) duplicated type definitions. ADR-0004 centralizes shared types in @metamask/design-system-shared as a single source of truth.

  3. Maintainability: Prop documentation is now defined once in BadgeStatusPropsShared and extended by platform-specific types, eliminating duplicate documentation.

What is the improvement/solution?

Before:

  • TypeScript enums in each platform package
  • Duplicate prop documentation in React and React Native
  • Nominal typing requiring imports for all usage

After:

  • Const objects with derived union types in shared package
  • Single BadgeStatusPropsShared type extended by platforms
  • Structural typing supporting both BadgeStatusStatus.Active and 'active'
  • Direct imports from @metamask/design-system-shared

Related issues

Implements:

Manual testing steps

  1. Import and use BadgeStatus component in React:

    import { BadgeStatus, BadgeStatusStatus } from '@metamask/design-system-react';
    <BadgeStatus status={BadgeStatusStatus.Active} />
  2. Import and use BadgeStatus component in React Native:

    import { BadgeStatus, BadgeStatusStatus } from '@metamask/design-system-react-native';
    <BadgeStatus status={BadgeStatusStatus.Active} />
  3. Verify both approaches still work (structural typing):

    • Using const object: BadgeStatusStatus.Active
    • Using string literal: 'active'
  4. Run Storybook and verify all BadgeStatus stories render correctly:

    • yarn storybook (React web)
    • yarn storybook:ios (React Native)

Screenshots/Recordings

After

Working in react native and react storybook

Screen.Recording.2026-02-17.at.8.20.18.PM.mov

Working in the extension using preview package

Screenshot 2026-02-17 at 3 55 42 PM

Working in mobile using preview package

Screenshot 2026-02-17 at 8 26 13 PMScreenshot 2026-02-17 at 8 27 27 PM

Autocomplete still works

Screenshot 2026-02-23 at 12 06 10 PM Screenshot 2026-02-23 at 12 06 33 PM

No type violation with string union

Screenshot 2026-02-23 at 1 23 16 PM

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 (all existing tests updated and passing)
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR

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
Type-level refactor across multiple packages that changes exported type/value shapes (enums to const objects), which may break downstream imports or Storybook control behavior if any consumers relied on enum semantics.

Overview
Centralizes BadgeStatus typing by introducing shared BadgeStatusStatus/BadgeStatusSize const objects (with derived string-union types) plus a shared BadgeStatusPropsShared in design-system-shared, and re-exporting them from design-system-shared/src/index.ts.

Updates both React and React Native BadgeStatus components, tests, constants, stories, and package exports to import these shared types instead of local enums; the storybook controls are adjusted to work with const-object values via Object.keys(...) + mapping. Removes the old BadgeStatusStatus/BadgeStatusSize enums from each package’s types/index.ts, and tweaks design-system-shared Jest coverage config to ignore .types.ts and types/**/index.ts files.

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

@georgewrmarshall georgewrmarshall requested a review from a team as a code owner February 17, 2026 21:10
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

…ared

Add `.types.ts` and type subdirectory index files to Jest coverage ignore patterns. Type definition files contain only TypeScript types and const objects with no executable runtime code to test.

This aligns with existing patterns in the monorepo where other packages already exclude:
- `.constants.ts` - Constant definitions
- `.dev.ts` - Development-only files
- `.d.ts` - TypeScript declaration files

The types are validated at compile-time by TypeScript and tested indirectly through component integration tests.

Fixes: Jest coverage threshold errors for BadgeStatus.types.ts
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

georgewrmarshall and others added 2 commits February 17, 2026 14:05
Remove intermediate re-export layer in types/index.ts and have BadgeStatus components import directly from @metamask/design-system-shared. This creates a cleaner architecture aligned with ADR-0004 and eliminates coverage issues without needing ignore patterns.

## Changes

**React Package:**
- Remove BadgeStatus re-exports from `src/types/index.ts`
- Update all BadgeStatus files to import from `@metamask/design-system-shared`
- Component `index.ts` now re-exports directly from shared package
- Remove redundant re-exports from `BadgeStatus.types.ts`

**React Native Package:**
- Remove BadgeStatus re-exports from `src/types/index.ts`
- Update all BadgeStatus files to import from `@metamask/design-system-shared`
- Component `index.ts` now re-exports directly from shared package
- Remove redundant re-exports from `BadgeStatus.types.ts`

## Benefits

✅ **Solves coverage issue naturally** - Component index.ts already in coveragePathIgnorePatterns
✅ **No circular dependencies** - Direct imports from external shared package
✅ **Cleaner architecture** - Aligned with ADR-0004 single source of truth
✅ **No test config changes** - No coverage ignore patterns needed
✅ **Migration path** - Can gradually remove types/index.ts as components migrate

## Import Flow

```
Consumer → @metamask/design-system-react → BadgeStatus/index.ts → @metamask/design-system-shared
```

No intermediate types/index.ts layer, no circular dependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

…ared

Add `.types.ts` and type subdirectory index files to Jest coverage ignore patterns for the shared package. The shared package contains the actual type definitions with const objects (ADR-0003) which are executable code, but these are infrastructure files that don't need direct test coverage.

The types are:
- Validated at compile-time by TypeScript
- Tested indirectly through consuming components in React/React Native packages
- Infrastructure code similar to other ignored patterns (.constants.ts, .dev.ts)

React and React Native packages don't need this ignore because their `.types.ts` files now only contain type definitions (no re-exports), which Jest automatically skips.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@georgewrmarshall georgewrmarshall enabled auto-merge (squash) February 17, 2026 22:38
@@ -0,0 +1,77 @@
/**

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.

New shared type infrastructure following ADR-0003 (https://github.com/MetaMask/decisions/pull/128) for enum to const object migration and ADR-0004 (https://github.com/MetaMask/decisions/pull/127) for centralized shared types. This becomes the single source of truth for BadgeStatus types across all platforms.

* BadgeStatus - status
* Convert from enum to const object (ADR-0003)
*/
export const BadgeStatusStatus = {

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 const object with 'as const' instead of enum enables structural typing per ADR-0003. This allows consumers to use literal values directly while maintaining type safety and dot notation access.

* BadgeStatus component shared props (ADR-0004)
* Platform-independent properties shared across React and React Native
*/
export type BadgeStatusPropsShared = {

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.

Shared props type following ADR-0004 eliminates duplicate prop documentation across React and React Native. Platform-specific types extend this to add platform concerns like className or twClassName.

// The display name when running multiple projects
displayName,

// An array of regexp pattern strings used to skip coverage collection

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.

Coverage ignore patterns needed because const objects with 'as const' are executable code that Jest tracks. Type infrastructure files don't need test coverage as they contain no runtime logic.

*/
style?: React.CSSProperties;
};
export type BadgeStatusProps = ComponentProps<'div'> &

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.

Platform-specific type now extends BadgeStatusPropsShared following ADR-0004. This eliminates duplicate prop documentation and ensures consistency across platforms while allowing React-specific extensions like className and style.

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/design-system-react": "0.7.0-preview.3dc70e1",
  "@metamask-previews/design-system-react-native": "0.6.0-preview.3dc70e1",
  "@metamask-previews/design-system-shared": "0.1.2-preview.3dc70e1",
  "@metamask-previews/design-system-tailwind-preset": "0.6.1-preview.3dc70e1",
  "@metamask-previews/design-system-twrnc-preset": "0.3.0-preview.3dc70e1",
  "@metamask-previews/design-tokens": "8.1.1-preview.3dc70e1"
}

@@ -1,3 +1,6 @@
export { BadgeStatusSize, BadgeStatusStatus } from '../../types';
export {

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.

Direct import from shared package following ADR-0004. This avoids circular dependencies that previously required the intermediate types/index.ts layer. Components now import directly from the source of truth.

@@ -91,30 +91,6 @@ export enum BadgeCountSize {
Lg = 'lg',
}

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.

Enum removal per ADR-0003. While this is a breaking change at the types level, consumer imports remain unchanged since types are re-exported from the component index. Migration path: BadgeStatusStatus.Active works with both enum and const object.

*/
twClassName?: string;
} & Omit<ViewProps, 'children'>;
export type BadgeStatusProps = BadgeStatusPropsShared &

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.

Same pattern applied to React Native: type extends BadgeStatusPropsShared with platform-specific twClassName. Note that React Native types don't need coverage ignore patterns since they only contain pure type definitions, not executable const objects.

@georgewrmarshall georgewrmarshall self-assigned this Feb 18, 2026
@georgewrmarshall georgewrmarshall changed the title refactor: migrate BadgeStatus to ADR-0003 and ADR-0004 patterns refactor: migrate BadgeStatus to design-system-shared and union types Feb 18, 2026
georgewrmarshall added a commit that referenced this pull request Feb 18, 2026
BadgeStatus (PR #912) is the proof-of-concept demonstrating all ADR-0003/0004 patterns. Removed references to Box/Button which don't follow these patterns yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
style?: React.CSSProperties;
};
export type BadgeStatusProps = ComponentProps<'div'> &
BadgeStatusPropsShared & {

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.

@brianacnguyen: how do we improve the readability here for engineers and developer?

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.

@@ -1,38 +1,15 @@
// Import shared type for extension

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.

Remove this comment

Suggested change
// Import shared type for extension

@georgewrmarshall georgewrmarshall Feb 23, 2026

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 eeb49e9

/**
* BadgeStatus component props.
* BadgeStatus component props (React Native platform-specific)
* Extends shared props with React Native-specific platform concerns

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.

Suggested change
* Extends shared props with React Native-specific platform concerns
* Extends shared props from @metamask/design-system-shared with React Native specific platform concerns

@georgewrmarshall georgewrmarshall Feb 23, 2026

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 eeb49e9

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

import { View } from 'react-native';

import { BadgeStatusStatus, BadgeStatusSize } from '../../types';

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.

React Native Storybook argTypes missing Object.keys and mapping

Medium Severity

The React Native stories pass BadgeStatusSize and BadgeStatusStatus const objects directly to the options field in argTypes. With the migration from enums to const objects, this should use options: Object.keys(BadgeStatusSize) and mapping: BadgeStatusSize pattern instead, matching the React web implementation. Storybook's options field expects an array of strings, not a const object.

Fix in Cursor Fix in Web

@georgewrmarshall georgewrmarshall Feb 23, 2026

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 12ac484

works on mobile. undefineds will be fixed in a future version of storybook

badgestatus.mov

brianacnguyen
brianacnguyen previously approved these changes Feb 23, 2026
…tive

Update React Native storybook argTypes to use Object.keys() for options
and mapping field, matching the React web implementation. This ensures
Storybook receives an array of strings for options instead of const
objects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

size: {
control: 'select',
options: BadgeStatusSize,
options: Object.keys(BadgeStatusSize),

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.

Storybook requires options to be an array of strings. Object.keys() extracts the keys (Md, Lg) as strings for the control dropdown, while mapping tells Storybook to map those strings to the actual const object values (md, lg) when passing to the component.

control: 'select',
options: BadgeStatusSize,
options: Object.keys(BadgeStatusSize),
mapping: BadgeStatusSize,

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 pattern is required after migrating from enums to const objects. Without mapping, Storybook would pass the key string (Md) instead of the value (md) to the component, causing invalid props.

* BadgeStatus component props.
* BadgeStatus component props (React Native platform-specific)
* Extends shared props from @metamask/design-system-shared with React Native specific platform concerns
*/

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.

Type definitions moved from platform-specific types to shared package. Platform implementations now extend BadgeStatusPropsShared and only add platform-specific concerns (className for React, twClassName for React Native).

displayName,

// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: [

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.

Coverage exclusion added because type definition files contain no executable code to test. This prevents false coverage gaps while still tracking actual component and utility test coverage.

* BadgeStatus - status
* Convert from enum to const object (ADR-0003)
*/
export const BadgeStatusStatus = {

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.

Const object pattern with as const and type extraction follows ADR-0003. This provides better type safety and tree-shaking compared to enums while maintaining similar developer experience.

* BadgeStatus component shared props (ADR-0004)
* Platform-independent properties shared across React and React Native
*/
export type BadgeStatusPropsShared = {

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.

BadgeStatusPropsShared implements ADR-0004 pattern for cross-platform type sharing. All shared props (status, hasBorder, size) are defined once in the shared package, ensuring consistency across React and React Native implementations.

@georgewrmarshall georgewrmarshall merged commit acfa6f0 into main Feb 23, 2026
43 checks passed
@georgewrmarshall georgewrmarshall deleted the badge-status-enum--union-type-migration branch February 23, 2026 23:25
georgewrmarshall added a commit that referenced this pull request Feb 25, 2026
## Release 21.0.0

This release includes breaking changes, new features, and refactoring
improvements across the design system packages.

### 📦 Package Versions

- `@metamask/design-system-react`: **0.9.0**
- `@metamask/design-system-react-native`: **0.8.0**
- `@metamask/design-system-shared`: **0.2.0**

### ⚠️ Breaking Changes

#### BadgeStatus Migration to String Union Types (#912)

The `BadgeStatus` component has been migrated from TypeScript enums to
string union types with const objects, implementing ADR-0003 and
ADR-0004:

**What Changed:**
- `BadgeStatusStatus` and `BadgeStatusSize` enums replaced with const
objects
- Types now centralized in `@metamask/design-system-shared` package
- Enables structural typing - both const values and string literals now
accepted

**Migration Required:**
- ✅ **Recommended**: Import from shared package
  ```typescript
  import { BadgeStatusStatus } from '@metamask/design-system-shared'
  ```
- ⚠️ **Still works** but deprecated: Importing from component packages
will re-export from shared
- Const object values remain the same: `BadgeStatusStatus.Active` still
works
- String literals now supported: `'active'` accepted where
`BadgeStatusStatus.Active` is expected

**Affected Packages:**
- `@metamask/design-system-react@0.9.0`
- `@metamask/design-system-react-native@0.8.0`
- `@metamask/design-system-shared@0.2.0`

### ✨ New Features

#### RadioButton Component (#926)
- Added `RadioButton` component to React Native
- Supports checked, disabled, read-only, and danger states
- Full accessibility support with `role="radio"` and
`accessibilityState`
- Optional label rendering

### 🔨 Refactoring

#### BottomSheetFooter Reorganization (#933)
- Moved `BottomSheetFooter` from `BottomSheets/BottomSheetFooter/` to
`BottomSheetFooter/`
- Updated import paths and Storybook title
- No breaking changes - all package imports continue to work

### 📚 References

- ADR-0003: Enum to String Union Migration
- ADR-0004: Centralized Types Architecture
- [MetaMask Decisions Repository](https://github.com/MetaMask/decisions)

### ✅ Checklist

- [x] All changelogs updated with human-readable descriptions
- [x] Breaking changes clearly documented
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] All packages built successfully
- [x] Migration paths provided for breaking changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Release/version bumps are low risk, but the documented **breaking**
`BadgeStatus` type export change and updated peer dependency ranges can
cause downstream TypeScript/build breakages for consumers.
> 
> **Overview**
> Bumps the monorepo release to `21.0.0` and publishes new package
versions: `@metamask/design-system-react@0.9.0`,
`@metamask/design-system-react-native@0.8.0`, and
`@metamask/design-system-shared@0.2.0`.
> 
> Updates changelogs to document a **breaking** `BadgeStatus` type
migration (enums → const objects + derived string unions, centralized in
`design-system-shared`), plus a React Native `RadioButton` addition and
a `BottomSheetFooter` re-org. Also bumps `design-system-react` peer
dependency ranges for `@metamask/design-system-tailwind-preset` and
`@metamask/design-tokens`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6a887c7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
@georgewrmarshall georgewrmarshall mentioned this pull request Feb 25, 2026
6 tasks
georgewrmarshall added a commit that referenced this pull request Feb 25, 2026
## Release 21.0.0

This release includes breaking changes, new features, and refactoring
improvements across the design system packages.

**Note:** This is a corrected resubmission of #938 without peer
dependency updates that caused yarn.lock modifications in CI.

### 📦 Package Versions

- `@metamask/design-system-react`: **0.9.0**
- `@metamask/design-system-react-native`: **0.8.0**
- `@metamask/design-system-shared`: **0.2.0**

### ⚠️ Breaking Changes

#### BadgeStatus Migration to String Union Types (#912)

The `BadgeStatus` component has been migrated from TypeScript enums to
string union types with const objects:

**What Changed:**
- `BadgeStatusStatus` and `BadgeStatusSize` enums replaced with const
objects
- **No migration required** - continue importing from your current
package
- Const object values remain the same: `BadgeStatusStatus.Active` still
works
- String literals now also accepted: `'active'` works where
`BadgeStatusStatus.Active` is expected
- We are still evaluating best practices for const objects vs string
literals

**Affected Packages:**
- `@metamask/design-system-react@0.9.0`
- `@metamask/design-system-react-native@0.8.0`
- `@metamask/design-system-shared@0.2.0`

**Learn More:**
- [ADR-0003: Enum to String Union
Migration](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0003-enum-to-string-union-migration.md)
- [ADR-0004: Centralized Types
Architecture](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0004-centralized-types-architecture.md)

### ✨ New Features

#### RadioButton Component (#926)
- Added `RadioButton` component to React Native
- Supports checked, disabled, read-only, and danger states
- Full accessibility support with `role="radio"` and
`accessibilityState`
- Optional label rendering

### 🔨 Refactoring

#### BottomSheetFooter Reorganization (#933)
- Moved `BottomSheetFooter` from `BottomSheets/BottomSheetFooter/` to
`BottomSheetFooter/`
- Updated import paths and Storybook title
- No breaking changes - all package imports continue to work

### ✅ Checklist

- [x] All changelogs updated with human-readable descriptions
- [x] Breaking changes clearly documented
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] All packages built successfully
- [x] Migration paths provided for breaking changes
- [x] No peer dependency changes to avoid yarn.lock modifications

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> This PR only changes package versions and changelog entries; no
runtime code is modified.
> 
> **Overview**
> Bumps the monorepo release to `21.0.0` and updates package versions
for `@metamask/design-system-react` to `0.9.0`,
`@metamask/design-system-react-native` to `0.8.0`, and
`@metamask/design-system-shared` to `0.2.0`.
> 
> Updates the React and React Native changelogs to document a
**breaking** `BadgeStatus` type migration (enums → const objects +
derived string unions), plus RN-only release notes for adding
`RadioButton` and a `BottomSheetFooter` location refactor; also updates
changelog compare links to start from the new versions.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
35569fb. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
Provides workflow for refactoring existing monorepo components to
ADR-0003 (const objects) and ADR-0004 (centralized types) patterns.
This is specifically for internal cleanup of components that have
duplicate const object definitions across React and React Native.

**Key workflow steps:**
- Identify components with duplicate const objects across platforms
- Create shared types in @metamask/design-system-shared
- Update platform packages to import (not re-export) from shared
- Remove duplicate exports from platform type indices
- Two-file export pattern: .types.ts imports only, index.ts exports

**Critical mistakes documented:**
- Re-exporting const objects from .types.ts (causes coverage loss)
- Using interface instead of type for shared props (ESLint error)
- Separate type exports causing "Duplicate identifier" errors
- Wrong import ordering (platform before shared)
- Including platform props in shared types

**NOT for new components:**
- This rule is for existing component cleanup only
- For new components, see component-creation.md or component-migration.md

Validated through BadgeCount migration (PR #942) which demonstrated
the exact coverage issue this rule prevents.

References BadgeStatus as the golden path and PR #912 as complete
migration example.
georgewrmarshall added a commit that referenced this pull request Feb 27, 2026
## **Description**

Adds `component-enum-union-migration.md` cursor rule providing workflow
for refactoring existing monorepo components to ADR-0003 (const objects)
and ADR-0004 (centralized types) patterns. This rule is specifically for
internal cleanup of components with duplicate const object definitions
across React and React Native packages.

**When to use this workflow:**
- Component already exists in both React and React Native packages
- Const objects are duplicated across platform packages
- Types are defined separately in each platform (no shared source)
- Component API needs to be unified/modernized

**Key workflow documented:**
1. Identify components with duplicate const objects across platforms
2. Create shared types in `@metamask/design-system-shared` package
3. Update React package to import (not re-export) shared types
4. Update React Native package to import (not re-export) shared types
5. Remove old duplicates from platform type indices
6. Update component index.ts to export directly from shared
7. Verify build/test/lint

**Critical mistakes documented:**
- Re-exporting const objects from `.types.ts` files (causes test
coverage loss)
- Using `interface` instead of `type` for shared props (ESLint error)
- Separate type exports causing "Duplicate identifier" TypeScript errors
- Wrong import ordering (platform imports before shared imports)
- Including platform-specific props in shared types (violates ADR-0004)

**Why this matters:**
The BadgeCount migration (PR #942) revealed that duplicate const object
exports in `.types.ts` files create uncovered code paths, failing Jest
coverage thresholds at 81.81% instead of 100%. This rule documents the
exact two-file pattern needed to prevent this issue.

**Relationship to other rules:**
- Depends on: `component-architecture.md` (foundational ADR patterns)
- Different from: `component-creation.md` (new components) and
`component-migration.md` (extension/mobile migration)
- Use case: Internal monorepo cleanup only

**References:**
- Golden path: BadgeStatus component (SOURCE OF TRUTH for shared types)
- Complete example: PR #912 (full migration with all file changes)
- Validation: BadgeCount PR #942 (demonstrated coverage issue and fix)

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-344

## **Manual testing steps**

1. Review the cursor rule file:
`.cursor/rules/component-enum-union-migration.md`
2. Verify it references BadgeCount PR #942 and PR #912
3. Check workflow steps match the BadgeCount migration pattern
4. Confirm anti-patterns section shows duplicate export mistake
5. Verify it clarifies this is NOT for new components
6. Check golden path examples reference BadgeStatus implementation
7. Confirm verification checklist covers both platforms
8. Verify CLAUDE.md includes reference to this new rule

## **Screenshots/Recordings**

N/A - Documentation only

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable (N/A - documentation)
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable (N/A - markdown)
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.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]
> **Low Risk**
> Documentation-only changes: adds an internal workflow guide and links
it from `CLAUDE.md`, with no runtime or build impact.
> 
> **Overview**
> Adds a new Cursor rule,
`.cursor/rules/component-enum-union-migration.md`, documenting a
step-by-step workflow to migrate existing cross-platform components from
duplicated enums/consts to **shared const-object + string-union types**
per ADR-0003/ADR-0004, including common pitfalls (notably avoiding const
re-exports from `.types.ts` to prevent coverage drops).
> 
> Updates `CLAUDE.md` to reference this new rule in the AI-agent
documentation list.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
690c364. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
georgewrmarshall added a commit that referenced this pull request Feb 28, 2026
## **Description**

Migrated the ButtonHero component from metamask-mobile to
design-system-react-native following the patterns established in PR #912
(ADR-0003 and ADR-0004).

**What is the reason for the change?**
The ButtonHero component was only available in metamask-mobile and
needed to be migrated to the shared design system for consistency and
reusability across platforms.

**What is the improvement/solution?**
- Implemented ButtonHero component with ThemeProvider pattern locking to
light theme colors
- Created comprehensive test suite with accessibility testing  
- Added Storybook stories for all major props (Size, IsFullWidth,
StartIconName, EndIconName, Disabled, Loading)
- Documented component following cross-platform consistency standards
- Added ButtonHeroSize type alias for cross-platform compatibility

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-290

## **Manual testing steps**

1. Run Storybook: `yarn storybook:ios` or `yarn storybook:android`
2. Navigate to Components/ButtonHero
3. Test all stories: Default, Size, IsFullWidth, StartIconName,
EndIconName, Disabled, Loading
4. Verify light theme colors are applied regardless of device theme
setting
5. Test press interactions and verify accessibility features

## **Screenshots/Recordings**

### **Before**

Component only available in metamask-mobile

### **After**

Component now available in design-system-react-native with:
- Light theme lock (ThemeProvider pattern)
- Full prop support matching React web implementation
- Comprehensive accessibility features
- Complete documentation and tests


https://github.com/user-attachments/assets/990c99bb-9a76-4cb2-a0ad-7830a6e11483

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [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-extension/blob/develop/.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.

## Implementation Details

### Key Features
- **ThemeProvider Pattern**: Uses the same pattern from metamask-mobile
with ButtonHeroInner wrapped in ThemeProvider with Theme.Light
- **Design Tokens**: Uses bg-primary-default, text-primary-inverse, and
bg-primary-default-pressed classes
- **Cross-Platform Consistency**: Props and behavior match the React web
implementation
- **Comprehensive Testing**: 11 test cases covering all functionality
including accessibility

### Files Created
- `ButtonHero.tsx` - Main component implementation
- `ButtonHero.types.ts` - Type definitions extending ButtonBaseProps
- `ButtonHero.test.tsx` - Comprehensive test suite
- `ButtonHero.stories.tsx` - Storybook stories
- `README.md` - Component documentation
- `index.ts` - Barrel exports

### Files Modified
- `packages/design-system-react-native/src/types/index.ts` - Added
ButtonHeroSize alias
- `packages/design-system-react-native/src/components/index.ts` - Added
ButtonHero and ButtonHeroSize exports

### Test Results
✅ All linting checks passed
✅ All builds passed  
✅ All tests passed (11/11 ButtonHero tests + all existing tests)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk additive change: introduces a new `ButtonHero` wrapper around
`ButtonBase` plus exports/type alias, with no behavioral changes to
existing components.
> 
> **Overview**
> Adds a new `ButtonHero` component to `design-system-react-native`,
implemented as a `ButtonBase` wrapper that **locks styling to the light
theme** and applies hero-specific background/text/pressed classes.
> 
> Includes Storybook stories covering key states (sizes, full width,
icons, disabled, loading), a comprehensive test suite validating
accessibility/press behavior and pressed-state styling, and component
documentation. Exposes `ButtonHero`, `ButtonHeroProps`, and a new
`ButtonHeroSize` alias via the package component/type exports.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
727c133. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
georgewrmarshall added a commit that referenced this pull request Mar 3, 2026
## Release 21.0.0

This release includes breaking changes, new features, and refactoring
improvements across the design system packages.

### 📦 Package Versions

- `@metamask/design-system-react`: **0.9.0**
- `@metamask/design-system-react-native`: **0.8.0**
- `@metamask/design-system-shared`: **0.2.0**

### ⚠️ Breaking Changes

#### BadgeStatus Migration to String Union Types (#912)

The `BadgeStatus` component has been migrated from TypeScript enums to
string union types with const objects, implementing ADR-0003 and
ADR-0004:

**What Changed:**
- `BadgeStatusStatus` and `BadgeStatusSize` enums replaced with const
objects
- Types now centralized in `@metamask/design-system-shared` package
- Enables structural typing - both const values and string literals now
accepted

**Migration Required:**
- ✅ **Recommended**: Import from shared package
  ```typescript
  import { BadgeStatusStatus } from '@metamask/design-system-shared'
  ```
- ⚠️ **Still works** but deprecated: Importing from component packages
will re-export from shared
- Const object values remain the same: `BadgeStatusStatus.Active` still
works
- String literals now supported: `'active'` accepted where
`BadgeStatusStatus.Active` is expected

**Affected Packages:**
- `@metamask/design-system-react@0.9.0`
- `@metamask/design-system-react-native@0.8.0`
- `@metamask/design-system-shared@0.2.0`

### ✨ New Features

#### RadioButton Component (#926)
- Added `RadioButton` component to React Native
- Supports checked, disabled, read-only, and danger states
- Full accessibility support with `role="radio"` and
`accessibilityState`
- Optional label rendering

### 🔨 Refactoring

#### BottomSheetFooter Reorganization (#933)
- Moved `BottomSheetFooter` from `BottomSheets/BottomSheetFooter/` to
`BottomSheetFooter/`
- Updated import paths and Storybook title
- No breaking changes - all package imports continue to work

### 📚 References

- ADR-0003: Enum to String Union Migration
- ADR-0004: Centralized Types Architecture
- [MetaMask Decisions Repository](https://github.com/MetaMask/decisions)

### ✅ Checklist

- [x] All changelogs updated with human-readable descriptions
- [x] Breaking changes clearly documented
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] All packages built successfully
- [x] Migration paths provided for breaking changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Release/version bumps are low risk, but the documented **breaking**
`BadgeStatus` type export change and updated peer dependency ranges can
cause downstream TypeScript/build breakages for consumers.
> 
> **Overview**
> Bumps the monorepo release to `21.0.0` and publishes new package
versions: `@metamask/design-system-react@0.9.0`,
`@metamask/design-system-react-native@0.8.0`, and
`@metamask/design-system-shared@0.2.0`.
> 
> Updates changelogs to document a **breaking** `BadgeStatus` type
migration (enums → const objects + derived string unions, centralized in
`design-system-shared`), plus a React Native `RadioButton` addition and
a `BottomSheetFooter` re-org. Also bumps `design-system-react` peer
dependency ranges for `@metamask/design-system-tailwind-preset` and
`@metamask/design-tokens`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6a887c7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
georgewrmarshall added a commit that referenced this pull request Mar 3, 2026
## Release 21.0.0

This release includes breaking changes, new features, and refactoring
improvements across the design system packages.

**Note:** This is a corrected resubmission of #938 without peer
dependency updates that caused yarn.lock modifications in CI.

### 📦 Package Versions

- `@metamask/design-system-react`: **0.9.0**
- `@metamask/design-system-react-native`: **0.8.0**
- `@metamask/design-system-shared`: **0.2.0**

### ⚠️ Breaking Changes

#### BadgeStatus Migration to String Union Types (#912)

The `BadgeStatus` component has been migrated from TypeScript enums to
string union types with const objects:

**What Changed:**
- `BadgeStatusStatus` and `BadgeStatusSize` enums replaced with const
objects
- **No migration required** - continue importing from your current
package
- Const object values remain the same: `BadgeStatusStatus.Active` still
works
- String literals now also accepted: `'active'` works where
`BadgeStatusStatus.Active` is expected
- We are still evaluating best practices for const objects vs string
literals

**Affected Packages:**
- `@metamask/design-system-react@0.9.0`
- `@metamask/design-system-react-native@0.8.0`
- `@metamask/design-system-shared@0.2.0`

**Learn More:**
- [ADR-0003: Enum to String Union
Migration](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0003-enum-to-string-union-migration.md)
- [ADR-0004: Centralized Types
Architecture](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0004-centralized-types-architecture.md)

### ✨ New Features

#### RadioButton Component (#926)
- Added `RadioButton` component to React Native
- Supports checked, disabled, read-only, and danger states
- Full accessibility support with `role="radio"` and
`accessibilityState`
- Optional label rendering

### 🔨 Refactoring

#### BottomSheetFooter Reorganization (#933)
- Moved `BottomSheetFooter` from `BottomSheets/BottomSheetFooter/` to
`BottomSheetFooter/`
- Updated import paths and Storybook title
- No breaking changes - all package imports continue to work

### ✅ Checklist

- [x] All changelogs updated with human-readable descriptions
- [x] Breaking changes clearly documented
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] All packages built successfully
- [x] Migration paths provided for breaking changes
- [x] No peer dependency changes to avoid yarn.lock modifications

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> This PR only changes package versions and changelog entries; no
runtime code is modified.
> 
> **Overview**
> Bumps the monorepo release to `21.0.0` and updates package versions
for `@metamask/design-system-react` to `0.9.0`,
`@metamask/design-system-react-native` to `0.8.0`, and
`@metamask/design-system-shared` to `0.2.0`.
> 
> Updates the React and React Native changelogs to document a
**breaking** `BadgeStatus` type migration (enums → const objects +
derived string unions), plus RN-only release notes for adding
`RadioButton` and a `BottomSheetFooter` location refactor; also updates
changelog compare links to start from the new versions.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
35569fb. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
georgewrmarshall added a commit that referenced this pull request Mar 3, 2026
## **Description**

Adds `component-enum-union-migration.md` cursor rule providing workflow
for refactoring existing monorepo components to ADR-0003 (const objects)
and ADR-0004 (centralized types) patterns. This rule is specifically for
internal cleanup of components with duplicate const object definitions
across React and React Native packages.

**When to use this workflow:**
- Component already exists in both React and React Native packages
- Const objects are duplicated across platform packages
- Types are defined separately in each platform (no shared source)
- Component API needs to be unified/modernized

**Key workflow documented:**
1. Identify components with duplicate const objects across platforms
2. Create shared types in `@metamask/design-system-shared` package
3. Update React package to import (not re-export) shared types
4. Update React Native package to import (not re-export) shared types
5. Remove old duplicates from platform type indices
6. Update component index.ts to export directly from shared
7. Verify build/test/lint

**Critical mistakes documented:**
- Re-exporting const objects from `.types.ts` files (causes test
coverage loss)
- Using `interface` instead of `type` for shared props (ESLint error)
- Separate type exports causing "Duplicate identifier" TypeScript errors
- Wrong import ordering (platform imports before shared imports)
- Including platform-specific props in shared types (violates ADR-0004)

**Why this matters:**
The BadgeCount migration (PR #942) revealed that duplicate const object
exports in `.types.ts` files create uncovered code paths, failing Jest
coverage thresholds at 81.81% instead of 100%. This rule documents the
exact two-file pattern needed to prevent this issue.

**Relationship to other rules:**
- Depends on: `component-architecture.md` (foundational ADR patterns)
- Different from: `component-creation.md` (new components) and
`component-migration.md` (extension/mobile migration)
- Use case: Internal monorepo cleanup only

**References:**
- Golden path: BadgeStatus component (SOURCE OF TRUTH for shared types)
- Complete example: PR #912 (full migration with all file changes)
- Validation: BadgeCount PR #942 (demonstrated coverage issue and fix)

## **Related issues**

Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-344

## **Manual testing steps**

1. Review the cursor rule file:
`.cursor/rules/component-enum-union-migration.md`
2. Verify it references BadgeCount PR #942 and PR #912
3. Check workflow steps match the BadgeCount migration pattern
4. Confirm anti-patterns section shows duplicate export mistake
5. Verify it clarifies this is NOT for new components
6. Check golden path examples reference BadgeStatus implementation
7. Confirm verification checklist covers both platforms
8. Verify CLAUDE.md includes reference to this new rule

## **Screenshots/Recordings**

N/A - Documentation only

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable (N/A - documentation)
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable (N/A - markdown)
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.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]
> **Low Risk**
> Documentation-only changes: adds an internal workflow guide and links
it from `CLAUDE.md`, with no runtime or build impact.
> 
> **Overview**
> Adds a new Cursor rule,
`.cursor/rules/component-enum-union-migration.md`, documenting a
step-by-step workflow to migrate existing cross-platform components from
duplicated enums/consts to **shared const-object + string-union types**
per ADR-0003/ADR-0004, including common pitfalls (notably avoiding const
re-exports from `.types.ts` to prevent coverage drops).
> 
> Updates `CLAUDE.md` to reference this new rule in the AI-agent
documentation list.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
690c364. 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