Skip to content

refactor: migrate BadgeCount to union and shared types#942

Merged
georgewrmarshall merged 4 commits into
mainfrom
refactor/badgecount-adr-migration
Mar 4, 2026
Merged

refactor: migrate BadgeCount to union and shared types#942
georgewrmarshall merged 4 commits into
mainfrom
refactor/badgecount-adr-migration

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Description

BREAKING CHANGE

This PR migrates the BadgeCount component to follow ADR-0003 (const objects instead of enums) and ADR-0004 (centralized shared types) patterns. This migration validates the effectiveness of our condensed cursor rules for component migrations.

What is the reason for the change?
To align BadgeCount with our architectural decision records and create a consistent pattern across all design system components.

What is the improvement/solution?

  • Converted BadgeCountSize enum to const object pattern with as const
  • Centralized types in @metamask/design-system-shared package
  • Platform packages (React/React Native) now re-export and extend shared types
  • Maintains platform-specific props (className for React, twClassName for React Native)

Related issues

Fixes: DSYS-469

Manual testing steps

  1. Run yarn build to build all packages
  2. Run yarn storybook to start Storybook
  3. Navigate to React Components > BadgeCount
  4. Verify all stories render correctly (Default, Size, Max)
  5. Test size variants (Md, Lg) work as expected
  6. Test max prop functionality (displays "99+" when count exceeds max)

Screenshots/Recordings

Before

BadgeCount used platform-local enums and types exported from each package's src/types/index.ts.

React Native

Screenshot 2026-03-03 at 8 13 50 AMScreenshot 2026-03-03 at 8 13 45 AM

React

Screenshot 2026-03-03 at 8 17 20 AM

After

BadgeCount Storybook stories demonstrating the component works correctly after migration:

React Native

Screenshot 2026-03-03 at 8 25 08 AM

React

Screenshot 2026-03-03 at 8 17 03 AM

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 a breaking type export migration (enum -> const-object/union) that can affect downstream imports, plus small styling/classname changes that could subtly alter BadgeCount layout.

Overview
BadgeCount is migrated to shared, ADR-aligned types. BadgeCountSize is removed from the React and React Native src/types/index.ts files and replaced by a new shared BadgeCountSize const-object + BadgeCountPropsShared in @metamask/design-system-shared, which both platform packages now re-export and extend.

BadgeCount styling/tests/stories are updated to match the new API. Storybook controls now use Object.keys(BadgeCountSize) with mapping, RN stories switch layout wrappers and rename Sizes -> Size, and RN drops per-size line-height mapping in favor of a fixed leading-0 text class; container tailwind classes are adjusted accordingly (and React’s Lg container class is updated but appears to have a typo: h-).

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

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

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall force-pushed the refactor/badgecount-adr-migration branch from 9ead452 to a4c2e1b Compare February 26, 2026 01:11
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

Comment thread packages/design-system-react-native/src/components/BadgeCount/BadgeCount.types.ts Outdated
georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
Removed duplicate export of BadgeCountSize from React Native BadgeCount.types.ts
to match React package and BadgeStatus golden path pattern. Enum is exported only
from index.ts, ensuring cross-platform consistency and 100% test coverage.

Addresses cursor bot feedback: #942 (comment)
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
Establishes foundational architectural patterns for components:
- ADR-0003: Const objects over TypeScript enums
- ADR-0004: Centralized types in shared package
- Layered architecture with one-way dependencies
- Two-file export pattern preventing test coverage loss

This rule provides the foundation that other cursor rules reference.
Validated through BadgeCount migration (PR #942) which demonstrated
the coverage issue when duplicate exports exist.

References BadgeStatus as the golden path proof-of-concept for
these patterns.
georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
Establishes foundational architectural patterns for components:
- ADR-0003: Const objects over TypeScript enums
- ADR-0004: Centralized types in shared package
- Layered architecture with one-way dependencies
- Two-file export pattern preventing test coverage loss

This rule provides the foundation that other cursor rules reference.
Validated through BadgeCount migration (PR #942) which demonstrated
the coverage issue when duplicate exports exist.

References BadgeStatus as the golden path proof-of-concept for
these patterns.
georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
Provides step-by-step HOW-TO guide for creating components following
ADR-0003 and ADR-0004 patterns. This rule is the technical implementation
guide referenced by component-migration.md workflow.

**Key guidance:**
- Using create-component scripts (never manual file creation)
- Two-file export pattern to prevent test coverage loss
- Shared types in @metamask/design-system-shared
- Cross-platform consistency between React and React Native
- Transforming generated templates to ADR-compliant code

**Critical anti-patterns documented:**
- Re-exporting const objects from .types.ts files
- Using TypeScript enums instead of const objects
- Not creating shared types (violates ADR-0004)
- Including platform props in shared types

Validated through BadgeCount migration (PR #942) which exposed the
duplicate export coverage issue this rule prevents.

References BadgeStatus as the golden path proof-of-concept.
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 26, 2026
Provides priority workflow for bringing components from MetaMask
extension or mobile codebases into the design system monorepo.
This is the PRIMARY workflow AI agents should use for component work,
as most components originate from consumer applications.

**Complete migration workflow:**
1. Assess component in extension/mobile for suitability
2. Scaffold using create-component scripts
3. Extract implementation from source (preserving logic and tests)
4. Create shared types in @metamask/design-system-shared
5. Transform to use Box/Text primitives and design tokens
6. Implement in BOTH React and React Native for consistency
7. Create Storybook stories and documentation
8. Verify build/test/lint before PR

**Critical patterns:**
- Two-file export structure: .types.ts imports only, index.ts exports
- Shared types in @metamask/design-system-shared (ADR-0004)
- Const objects with as const (ADR-0003)
- Cross-platform consistency between implementations
- References component-creation.md for technical scaffolding steps

**When to use:**
- Bringing component from extension or mobile (PRIORITY)
- Component exists in consumer app but not in design system
- User references extension or mobile component

**NOT for:**
- Brand new components with no prior implementation (use component-creation.md)
- Internal monorepo refactoring (use component-enum-union-migration.md)

Validated through BadgeCount migration (PR #942) which demonstrated
the importance of correct export patterns for test coverage.

References BadgeStatus as the golden path proof-of-concept.
@georgewrmarshall georgewrmarshall self-assigned this Feb 26, 2026
@georgewrmarshall georgewrmarshall changed the title refactor: migrate BadgeCount to ADR-0003/0004 patterns refactor: migrate BadgeCount to union and shared types Feb 26, 2026
@georgewrmarshall georgewrmarshall marked this pull request as draft February 26, 2026 19:20
georgewrmarshall added a commit that referenced this pull request Feb 26, 2026
- Remove Box/Button examples (align with PR #943 - these components don't follow ADR-0003/0004 yet)
- Add Step 11: Verify Coverage section with critical warning about duplicate exports
- Add coverage check to verification checklist
- Clarify type vs interface rule with composition/intersection rationale
- Reference BadgeCount PR #942 coverage issue as real-world example

Addresses review feedback from PR #944 code review
georgewrmarshall added a commit that referenced this pull request Feb 27, 2026
## **Description**

Adds `component-architecture.md` cursor rule establishing foundational
architectural patterns for design system components. This rule documents
[ADR-0003](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0003-enum-to-string-union-migration.md)
(const objects over TypeScript enums) and
[ADR-0004](https://github.com/MetaMask/decisions/blob/main/decisions/design-system/0004-centralized-types-architecture.md)
(centralized types architecture) patterns.

**Key patterns documented:**
- Two-file export structure: `.types.ts` for imports only, `index.ts`
for exports only
- Const objects with `as const` instead of TypeScript enums
- Centralized types in `@metamask/design-system-shared` package
- Layered architecture with one-way dependencies (shared → platform
packages)
- Cross-platform consistency between React and React Native

**Why this matters:**
We need to ensure our components are consistent across platforms and use
the most up to date typescript conventions

**References:**
- Golden path: BadgeStatus component implementation
- Validation: BadgeCount PR #942 was created using
component-architecture.md, component-creation.md, component-migration.md

## **Related issues**

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

## **Manual testing steps**

1. Review the cursor rule file:
`.cursor/rules/component-architecture.md`
2. Check out the BadgeCount PR #942
3. 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**
> Low risk documentation-only change that doesn’t affect runtime
behavior. Risk is limited to developers/agents following the new
guidance incorrectly.
> 
> **Overview**
> Adds a new Cursor rule, `.cursor/rules/component-architecture.md`,
documenting the design-system component architecture standards (ADR-0003
const-object string unions over enums, ADR-0004 centralized shared
types, and layered shared vs platform props), including export/re-export
patterns and a cross-platform verification checklist.
> 
> Updates AI-agent docs to reference the new rule (`CLAUDE.md`,
`docs/ai-agents.md`) and clarifies Storybook story naming conventions in
`.cursor/rules/component-documentation.md` with explicit prop-to-story
examples.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
bc55943. 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 27, 2026
## **Description**

Adds `component-creation.md` cursor rule providing step-by-step
technical guidance for creating design system components. This HOW-TO
guide ensures AI agents follow ADR-0003 (const objects) and ADR-0004
(centralized types) patterns when scaffolding new components.

**Key guidance provided:**
- Using `create-component:react` and `create-component:react-native`
scripts
- Two-file export structure: `.types.ts` imports only, `index.ts`
exports only
- Creating shared types in `@metamask/design-system-shared` package
- Transforming generated templates to use Box/Text primitives and design
tokens generated tailwind classnames
- Cross-platform consistency between React and React Native
implementations

**Critical anti-patterns documented:**
- Using TypeScript enums instead of const objects with `as const`
- Not creating shared types (violates ADR-0004 layered architecture)
- Including platform-specific props in shared types
- Leaving template code unchanged (raw div/View elements)

**Why this matters:**
We need to ensure our components are consistent across platforms and use
the most up to date typescript conventions

**Relationship to other rules:**
- Depends on: `component-architecture.md` (foundational patterns)
- Used by: `component-migration.md` (references this for scaffolding
steps)

**References:**
- Golden path: BadgeStatus component (complete implementation)
- Validation: BadgeCount PR #942 (demonstrated coverage issue and fix)
- Scripts: `create-component:react` and `create-component:react-native`

## **Related issues**

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

## **Manual testing steps**

1. Review the cursor rule file: `.cursor/rules/component-creation.md`
2. Verify changes in BadgeCount PR #942 align with most up to date
coventions
3. Verify golden path examples reference BadgeStatus implementation
4. Check that 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 that affect developer/agent guidance, not
runtime behavior. Risk is limited to potential confusion if guidance is
inaccurate or drifts from actual scripts/architecture.
> 
> **Overview**
> Adds a new Cursor rule `component-creation.md` that standardizes the
step-by-step process for creating cross-platform design-system
components, including shared-types-first (ADR-0003/0004), Box/Text +
token usage, and a strict export pattern (`.types.ts` imports only;
`index.ts` is the single const-object export point to avoid coverage
gaps).
> 
> Updates existing AI-agent docs to reference this new rule, expands
Storybook story naming guidance with concrete examples, and documents
the `create-component:react-native` scaffolding command alongside the
React one.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3d3ae57. 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 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 -->
* Convert from enum to const object (ADR-0003)
*/
export const BadgeCountSize = {
/**

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 with derived string union type following ADR-0003. This replaces the TypeScript enum to enable better tree-shaking and avoid runtime overhead.

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

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 interface (ADR-0004) defines platform-independent properties. Both React and React Native will extend this with their platform-specific concerns like className/twClassName and textProps.

*/
style?: React.CSSProperties;
};
export type BadgeCountProps = 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 extension layer following ADR-0004. Extends BadgeCountPropsShared with React-specific concerns: ComponentProps for HTML attributes, className for Tailwind styling, textProps for Text component customization.

*/
style?: StyleProp<ViewStyle>;
} & Omit<ViewProps, 'children'>;
export type BadgeCountProps = BadgeCountPropsShared &

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 extension layer for React Native following ADR-0004. Extends BadgeCountPropsShared with React Native-specific concerns: ViewProps for native attributes, twClassName for TWRNC styling, textProps for Text component customization.

@@ -1,3 +1,3 @@
export { BadgeCountSize } from '../../types';
export { BadgeCountSize } from '@metamask/design-system-shared';

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 export from shared package following ADR-0004 pattern. Component index files should import directly from design-system-shared rather than re-exporting through src/types/index.ts to maintain clear dependency relationships.

@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 2 potential issues.

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Truncated Tailwind class h- missing height value
    • Updated the large size container classes to use 'h-5' instead of the invalid truncated 'h-'.
  • ✅ Fixed: Test assertion mismatches component's actual twClassName output
    • Changed the test to assert .toContain('custom') to match the component’s 'leading-0 custom' output.

Create PR

Or push these changes by commenting:

@cursor push fbd98e7e88
Preview (fbd98e7e88)
diff --git a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.test.tsx b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.test.tsx
--- a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.test.tsx
+++ b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.test.tsx
@@ -140,7 +140,7 @@
       expectedTextProps.fontWeight,
     );
     // Custom twClassName should be applied
-    expect(textElement.props.twClassName).toBe('custom');
+    expect(textElement.props.twClassName).toContain('custom');
   });
 
   it('applies additional container style and forwards extra props', () => {

diff --git a/packages/design-system-react/src/components/BadgeCount/BadgeCount.constants.ts b/packages/design-system-react/src/components/BadgeCount/BadgeCount.constants.ts
--- a/packages/design-system-react/src/components/BadgeCount/BadgeCount.constants.ts
+++ b/packages/design-system-react/src/components/BadgeCount/BadgeCount.constants.ts
@@ -16,5 +16,5 @@
   string
 > = {
   [BadgeCountSize.Md]: 'min-w-4 h-4 px-1', // min-width 16px, height 14px, padding-horizontal 4
-  [BadgeCountSize.Lg]: 'min-w-6 h- px-1.5', // min-width 24px, height 20px, padding-horizontal 6
+  [BadgeCountSize.Lg]: 'min-w-6 h-5 px-1.5', // min-width 24px, height 20px, padding-horizontal 6
 };

[BadgeCountSize.Md]: 'min-w-4 h-3.5 py-0 px-1', // min-width 16px, height 14px, padding-vertical 0, padding-horizontal 4
[BadgeCountSize.Lg]: 'min-w-6 h-5 py-0.5 px-1.5', // min-width 24px, height 20px, padding-vertical 2, padding-horizontal 6
[BadgeCountSize.Md]: 'min-w-4 h-4 px-1', // min-width 16px, height 14px, padding-horizontal 4
[BadgeCountSize.Lg]: 'min-w-6 h- px-1.5', // min-width 24px, height 20px, padding-horizontal 6

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.

Truncated Tailwind class h- missing height value

High Severity

The Lg size container class string 'min-w-6 h- px-1.5' contains a truncated h- Tailwind utility with no value. The React Native counterpart correctly uses h-5 for 20px height. This invalid class means the large BadgeCount variant in the React package will have no explicit height, breaking its visual appearance.

Fix in Cursor Fix in Web

Comment thread packages/design-system-react-native/src/components/BadgeCount/BadgeCount.test.tsx Outdated
@cursor

cursor Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

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

  • ✅ Fixed: Invalid leading-0 replaces size-specific line-heights
    • Replaced invalid leading-0 with size-specific line-heights and ensured internal class precedence while preserving custom overrides.

Create PR

Or push these changes by commenting:

@cursor push c33a17570b
Preview (c33a17570b)
diff --git a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.constants.ts b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.constants.ts
--- a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.constants.ts
+++ b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.constants.ts
@@ -18,3 +18,12 @@
   [BadgeCountSize.Md]: 'min-w-4 h-3.5 py-0 px-1', // min-width 16px, height 14px, padding-vertical 0, padding-horizontal 4
   [BadgeCountSize.Lg]: 'min-w-6 h-5 py-0.5 px-1.5', // min-width 24px, height 20px, padding-vertical 2, padding-horizontal 6
 };
+
+export const TWCLASSMAP_BADGECOUNT_SIZE_LINEHEIGHT: Record<
+  BadgeCountSize,
+  string
+> = {
+  // Match text line-height to container height
+  [BadgeCountSize.Md]: 'leading-[14px]',
+  [BadgeCountSize.Lg]: 'leading-[16px]',
+};

diff --git a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.tsx b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.tsx
--- a/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.tsx
+++ b/packages/design-system-react-native/src/components/BadgeCount/BadgeCount.tsx
@@ -8,6 +8,7 @@
 import {
   MAP_BADGECOUNT_SIZE_TEXTVARIANT,
   TWCLASSMAP_BADGECOUNT_SIZE_CONTAINER,
+  TWCLASSMAP_BADGECOUNT_SIZE_LINEHEIGHT,
 } from './BadgeCount.constants';
 import type { BadgeCountProps } from './BadgeCount.types';
 
@@ -38,8 +39,12 @@
         variant={MAP_BADGECOUNT_SIZE_TEXTVARIANT[size]}
         color={TextColor.ErrorInverse}
         fontWeight={FontWeight.Medium}
-        twClassName="leading-0"
         {...textProps}
+        twClassName={
+          textProps?.twClassName
+            ? textProps.twClassName
+            : TWCLASSMAP_BADGECOUNT_SIZE_LINEHEIGHT[size]
+        }
       >
         {count > max ? `${max}+` : `${count}`}
       </Text>

@@ -0,0 +1,41 @@
/**

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 location following ADR-0004 centralized types architecture. This is the single source of truth for BadgeCount types across both React and React Native platforms.

* BadgeCount - size
* Convert from enum to const object (ADR-0003)
*/
export const BadgeCountSize = {

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 following ADR-0003. Using const object with derived union type instead of TypeScript enum enables better tree-shaking and type safety.

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

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.

SharedPropsShared suffix indicates platform-independent properties. These props are shared across React and React Native implementations, with platform-specific extensions added in each package.

*/
style?: StyleProp<ViewStyle>;
} & Omit<ViewProps, 'children'>;
export type BadgeCountProps = BadgeCountPropsShared &

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 extension pattern. React Native adds ViewProps, twClassName, and style to shared props. Notice textProps is platform-specific since Text component APIs differ between web and native.

@@ -1,3 +1,3 @@
export { BadgeCountSize } from '../../types';
export { BadgeCountSize } from '@metamask/design-system-shared';

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. Component index.ts is the single export location for const objects, preventing duplicate exports that would reduce test coverage.

@@ -77,20 +77,6 @@ export enum AvatarIconSeverity {
Warning = 'warning',

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 from platform type index. Old enum definition removed since shared package is now the source of truth. Platform type indices no longer re-export shared types to prevent duplicate exports.

variant={MAP_BADGECOUNT_SIZE_TEXTVARIANT[size]}
color={TextColor.ErrorInverse}
fontWeight={FontWeight.Medium}
{...textProps}

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.

Simplified line-height implementation. Using leading-0 directly in the component eliminates the need for size-specific line-height mappings while maintaining visual alignment.

export const TWCLASSMAP_BADGECOUNT_SIZE_CONTAINER: Record<
BadgeCountSize,
string
> = {

@georgewrmarshall georgewrmarshall Mar 3, 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.

Container sizing simplification. Removed explicit vertical padding py-0 and py-0.5, using only height constraints. This improves the visual appearance while simplifying the implementation.

Before (with fixes it was actually completely broken)

Image

After

Alignment is fixed

Image

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

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 argTypes improvement. Using Object.keys with mapping enables proper select control in Storybook UI while maintaining type safety with the const object pattern.

<BadgeCount key={size} size={size} count={100} />
))}
</View>
<Box flexDirection={BoxFlexDirection.Row} gap={2}>

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.

Component-first refactoring. Replaced raw View with Box component following design system styling conventions. This ensures consistent spacing and layout patterns.

MAP_BADGECOUNT_SIZE_LINEHEIGHT[BadgeCountSize.Md],
);
expect(textElement.props.twClassName).toContain('custom');
// Custom twClassName should be applied

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.

Test assertion cleanup. Removed line-height test assertions since MAP_BADGECOUNT_SIZE_LINEHEIGHT constant was eliminated. The component now uses a consistent leading-0 approach.

*/
style?: React.CSSProperties;
};
export type BadgeCountProps = 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.

Cross-platform consistency. React implementation matches React Native changes - same shared type imports, same container simplification, same const object pattern.

georgewrmarshall added a commit that referenced this pull request Mar 3, 2026
## **Description**

Adds `component-migration.md` cursor rule providing the PRIORITY
workflow for bringing components from MetaMask extension or mobile
codebases into the design system monorepo. This is the primary workflow
AI agents should use for component work, as most components originate
from consumer applications rather than being created from scratch.

**Complete migration workflow documented:**
1. **Assess component** - Determine if extension/mobile component is
suitable for design system
2. **Scaffold** - Use `create-component:react` and
`create-component:react-native` scripts
3. **Extract implementation** - Preserve logic, behavior, and tests from
source
4. **Create shared types** - Move to `@metamask/design-system-shared`
(ADR-0004)
5. **Transform code** - Replace raw elements with Box/Text primitives
and design tokens
6. **Implement both platforms** - Ensure cross-platform consistency
7. **Create documentation** - Storybook stories and README files
8. **Verify quality** - Build, test, lint must pass

**Critical patterns:**
- Two-file export structure: `.types.ts` imports only, `index.ts`
exports only
- Shared types in `@metamask/design-system-shared` package (ADR-0004)
- Const objects with `as const` instead of TypeScript enums (ADR-0003)
- Cross-platform consistency between React and React Native
- References `component-creation.md` for technical scaffolding details

**When to use this workflow:**
✅ Bringing component from extension or mobile (PRIORITY)
✅ Component exists in consumer app but not in design system
✅ User references extension or mobile component

**NOT for:**
❌ Brand new components with no prior implementation (use
`component-creation.md`)
❌ Internal monorepo refactoring (use
`component-enum-union-migration.md`)

**Why this matters:**
Most design system components originate from MetaMask extension or
mobile applications. This workflow ensures proper migration that
maintains functionality while adopting design system patterns. The
BadgeCount migration (PR #942) validated these patterns, particularly
the importance of correct export structure for test coverage.

**Relationship to other rules:**
- Depends on: `component-architecture.md` (foundational patterns)
- Uses: `component-creation.md` (references for technical scaffolding
steps)
- Different from: `component-enum-union-migration.md` (internal
refactoring only)

**References:**
- Golden path: BadgeStatus component (complete implementation)
- Validation: BadgeCount PR #942 (demonstrated coverage issue and fix)
- Scripts: `create-component:react` and `create-component:react-native`

## **Related issues**

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

## **Manual testing steps**

1. Review the cursor rule file: `.cursor/rules/component-migration.md`
2. Verify it clearly marks this as PRIORITY workflow for
extension/mobile components
3. Check workflow steps are comprehensive (assess → scaffold → extract →
transform → implement)
4. Confirm it references `component-creation.md` for scaffolding details
5. Verify it clarifies when NOT to use this workflow
6. Check golden path examples reference BadgeStatus
7. Confirm verification checklist covers assessment, implementation, and
quality
8. Verify CLAUDE.md includes reference to this new rule
9. Confirm it references BadgeCount PR #942

## **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**
> Low risk; changes are documentation-only, updating AI-agent guidance
without affecting runtime code or build artifacts.
> 
> **Overview**
> Adds a new `.cursor/rules/component-migration.md` rule that documents
the *primary* workflow for migrating components from MetaMask
Extension/Mobile into the monorepo, including a conservative vs unified
strategy, phased checklist, and example patterns for shared types,
deprecations, and platform layering.
> 
> Updates `CLAUDE.md` to include this new rule in the repository’s
AI-agent documentation index.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
03c1bb5. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@github-actions

github-actions Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall force-pushed the refactor/badgecount-adr-migration branch from 62940b3 to e882119 Compare March 3, 2026 23:14
"./../../packages/design-system-react-native/src/components/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/variants/ButtonSecondary/ButtonSecondary.stories.tsx"),
"./../../packages/design-system-react-native/src/components/Button/variants/ButtonTertiary/ButtonTertiary.stories.tsx": require("../../../packages/design-system-react-native/src/components/Button/variants/ButtonTertiary/ButtonTertiary.stories.tsx"),
"./../../packages/design-system-react-native/src/components/ButtonBase/ButtonBase.stories.tsx": require("../../../packages/design-system-react-native/src/components/ButtonBase/ButtonBase.stories.tsx"),
"./../../packages/design-system-react-native/src/components/ButtonHero/ButtonHero.stories.tsx": require("../../../packages/design-system-react-native/src/components/ButtonHero/ButtonHero.stories.tsx"),

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.

Adds missing ButtonHero story

@github-actions

github-actions Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit 2404631 into main Mar 4, 2026
43 checks passed
@georgewrmarshall georgewrmarshall deleted the refactor/badgecount-adr-migration branch March 4, 2026 00:04
@georgewrmarshall georgewrmarshall mentioned this pull request Mar 4, 2026
5 tasks
georgewrmarshall added a commit that referenced this pull request Mar 4, 2026
## Release 24.0.0

This release includes BadgeCount type migration updates and new React
Native components.

### 📦 Package Versions

- `@metamask/design-system-shared`: **0.3.0**
- `@metamask/design-system-react`: **0.10.0**
- `@metamask/design-system-react-native`: **0.10.0**

### 🔄 Shared + React Type Updates

#### BadgeCount ADR Migration (#942)

Updated `BadgeCount` types to follow ADR-0003 and ADR-0004 patterns
across shared, React, and React Native packages.

**What Changed:**
- `BadgeCountSize` now uses const-object + string-union typing instead
of enum-based typing
- Shared `BadgeCount` props/types are centralized in
`@metamask/design-system-shared`
- Platform packages consume and re-export shared `BadgeCount` types

**Impact:**
- Consistent type architecture across packages
- Better alignment with design-system ADRs
- Potentially breaking for enum-specific consumer type usage

### 📱 React Native Updates (0.10.0)

#### Added
- Added `ActionListItem` component (#951)
- Added `SensitiveText` component (#922)
- Added `ButtonSemantic` component (#950)
- Added `BottomSheetHeader` component (#927)
- Added `ButtonHero` component to React Native package (#934)

### ⚠️ Breaking Changes

- `BadgeCount` type exports were migrated from enum-style to
const-object/union style (#942)
- Continue importing from package entrypoints, but update enum-specific
type assumptions in consuming code

### ✅ 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.2.0 → 0.3.0)
  - design-system-react: minor (0.9.0 → 0.10.0)
  - design-system-react-native: minor (0.9.0 → 0.10.0)
- [x] Breaking changes documented with migration guidance
- [x] PR references included in changelog entries

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Changes are limited to version bumps and changelog updates; no runtime
code is modified. The main risk is downstream impact from the documented
breaking `BadgeCount` type export migration when consumers upgrade.
> 
> **Overview**
> Bumps the monorepo and package versions for the `24.0.0` release
(`@metamask/design-system-react`/`react-native` to `0.10.0`,
`@metamask/design-system-shared` to `0.3.0`).
> 
> Updates changelogs to publish release notes, including a **breaking**
`BadgeCount` type export migration to the const-object + string-union
pattern and documenting newly added React Native components in `0.10.0`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6c194fe. 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