Skip to content

feat(DSYS-484): migrate ButtonBase and Button to ADR-0003 and ADR-0004#1074

Closed
cursor[bot] wants to merge 8 commits intomainfrom
cursor/-bc-a6f75f80-ffb2-470e-adba-eb0cab8c1f39-e500
Closed

feat(DSYS-484): migrate ButtonBase and Button to ADR-0003 and ADR-0004#1074
cursor[bot] wants to merge 8 commits intomainfrom
cursor/-bc-a6f75f80-ffb2-470e-adba-eb0cab8c1f39-e500

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor Bot commented Apr 12, 2026

Description

Migrates ButtonBase and Button components to follow ADR-0003 (String Unions) and ADR-0004 (Centralized Types Architecture) as part of the DSYS-468 epic.

What changed:

  • Created ButtonBaseSize const object (replaces enum ButtonBaseSize) and ButtonBasePropsShared in @metamask/design-system-shared
  • Created ButtonVariant const object (replaces enum ButtonVariant) and ButtonPropsShared in @metamask/design-system-shared
  • Exported new types from shared package index
  • Replaced enum definitions in both platform types/index.ts files with re-exports from shared package (backwards compatible — all existing imports still work)
  • Updated ButtonBase.types.ts, ButtonBase.tsx, ButtonBase.constants.ts in both React and React Native platforms to import from @metamask/design-system-shared
  • Updated Button.tsx, Button.types.ts in both platforms to import from @metamask/design-system-shared
  • Updated all component index.ts files (ButtonBase, Button, ButtonHero, ButtonPrimary, ButtonSecondary, ButtonTertiary) to re-export const objects from @metamask/design-system-shared

Migration details:

  • ButtonBaseSize values are identical across platforms (sm/md/lg) → moved to shared
  • ButtonVariant values are identical across platforms (primary/secondary/tertiary) → moved to shared
  • ButtonBasePropsShared contains: children, size, isLoading, loadingText, isDisabled, isFullWidth
  • ButtonPropsShared extends ButtonBasePropsShared with variant
  • All size aliases (ButtonSize, ButtonPrimarySize, ButtonSecondarySize, ButtonTertiarySize, ButtonHeroSize) are preserved as re-exports from shared for backwards compatibility
  • Platform-specific props (React: className, asChild, style, textProps, a11y props; RN: twClassName, textClassName, iconClassName, spinnerProps, style) remain in platform packages

Related issues

Fixes: DSYS-484

Manual testing steps

  1. Build the packages: yarn build
  2. Run tests: yarn test
  3. Verify ButtonBase and Button components work with existing size/variant props
  4. Verify existing imports of ButtonBaseSize, ButtonVariant, ButtonSize, ButtonPrimarySize, etc. continue to work (re-exported from shared via types/index.ts)

Screenshots/Recordings

Before

Enums defined locally in each platform's src/types/index.ts:

export enum ButtonBaseSize { Sm = 'sm', Md = 'md', Lg = 'lg' }
export enum ButtonVariant { Primary = 'primary', Secondary = 'secondary', Tertiary = 'tertiary' }

After

Const objects in @metamask/design-system-shared:

export const ButtonBaseSize = { Sm: 'sm', Md: 'md', Lg: 'lg' } as const;
export const ButtonVariant = { Primary: 'primary', Secondary: 'secondary', Tertiary: 'tertiary' } as const;

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.
Open in Web View Automation 

Note

Medium Risk
Touches core ButtonBase/Button APIs across both React and React Native by replacing local enums with shared const-union types, which could introduce subtle typing/import breakages for downstream consumers despite runtime behavior being largely unchanged.

Overview
Migrates ButtonBase sizing and Button variant types from platform-local enums to centralized, ADR-aligned shared types in @metamask/design-system-shared (new ButtonBaseSize/ButtonVariant const objects plus ButtonBasePropsShared/ButtonPropsShared).

Updates React and React Native components, stories/tests, and component barrel exports to import/re-export these shared types (including preserved aliases like ButtonSize/ButtonPrimarySize/etc.), and removes the duplicated button enum definitions from each platform’s types/index.ts.

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

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@AndyMBridges AndyMBridges marked this pull request as ready for review April 14, 2026 08:32
@AndyMBridges AndyMBridges requested a review from a team as a code owner April 14, 2026 08:32
- Create ButtonBaseSize const object and ButtonBasePropsShared in shared package
- Create ButtonVariant const object and ButtonPropsShared in shared package
- Export new types from @metamask/design-system-shared index
- Replace ButtonBaseSize and ButtonVariant enums in both platform type indices
  with re-exports from shared package (backwards compatible)
- Update ButtonBase.types.ts, ButtonBase.tsx, ButtonBase.constants.ts in both platforms
- Update Button.types.ts, Button.tsx in both platforms
- Update ButtonBase/index.ts and Button/index.ts in both platforms to export from shared
- Update ButtonHero/index.ts and all ButtonPrimary/Secondary/Tertiary variant index files
  to use ButtonBaseSize aliases from shared package

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@georgewrmarshall georgewrmarshall force-pushed the cursor/-bc-a6f75f80-ffb2-470e-adba-eb0cab8c1f39-e500 branch from 13de5dd to ff43317 Compare April 14, 2026 20:18
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

…ed and update internal imports

- Export ButtonSize, ButtonPrimarySize, ButtonSecondarySize, ButtonTertiarySize, and ButtonHeroSize aliases from @metamask/design-system-shared
- Update all component index.ts files to re-export named aliases from shared (no more `as` redirection)
- Update all stories and tests to import Button types directly from @metamask/design-system-shared instead of ../../types
- Remove Button-related re-exports from src/types/index.ts in both platform packages
Comment thread packages/design-system-react-native/src/types/index.ts
Copy link
Copy Markdown
Contributor Author

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

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

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 97c2365. Configure here.

Comment thread packages/design-system-shared/src/types/ButtonBase/ButtonBase.types.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

export { ButtonBaseSize as ButtonPrimarySize } from './types/ButtonBase';
export { ButtonBaseSize as ButtonSecondarySize } from './types/ButtonBase';
export { ButtonBaseSize as ButtonTertiarySize } from './types/ButtonBase';
export { ButtonBaseSize as ButtonHeroSize } from './types/ButtonBase';
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.

Size aliases like ButtonPrimarySize, ButtonSecondarySize, ButtonTertiarySize, and ButtonHeroSize are all the same underlying ButtonBaseSize const object — the variants share identical sizing tokens. Exporting these named aliases from the shared package means consumers can write import { ButtonPrimarySize } from '@metamask/design-system-shared' without needing an as redirect at the call site, matching the ergonomics of the old enum-based API.

Lg: 'lg',
} as const;
export type ButtonBaseSize =
(typeof ButtonBaseSize)[keyof typeof ButtonBaseSize];
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.

ADR-0003 pattern: the const object + derived union type (export type ButtonBaseSize = (typeof ButtonBaseSize)[keyof typeof ButtonBaseSize]) replaces the TypeScript enum that previously lived in both platform src/types/index.ts files. The const object is tree-shakable and has no runtime overhead beyond a plain object — the enum compiled to an IIFE in JS output.

* @default false
*/
isFullWidth?: boolean;
};
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.

ButtonBasePropsShared collects the six props that are truly cross-platform (children, size, isLoading, loadingText, isDisabled, isFullWidth) so they are defined exactly once in the shared package. Platform-specific concerns — className/twClassName, asChild, icon props, aria attributes, and the PressableProps/ComponentProps<'button'> base — stay in their respective platform .types.ts files.

} & (
| (Omit<ButtonPrimaryProps, 'ref'> & {
variant?: ButtonVariant.Primary;
variant?: Extract<ButtonVariant, 'primary'>;
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.

Extract<ButtonVariant, 'primary'> is needed here because ButtonVariant is now a string union ('primary' | 'secondary' | 'tertiary') rather than an enum. With enums you could narrow a discriminated union member using ButtonVariant.Primary; with a string union the equivalent narrowing requires Extract. The external-facing API is unchanged — callers still pass ButtonVariant.Primary or the string 'primary'.

*/
style?: React.CSSProperties;
export type ButtonBaseProps = ButtonBasePropsShared &
ComponentProps<'button'> & {
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.

Intersecting ButtonBasePropsShared here (and in the RN equivalent) rather than inlining the props means any future change to the shared interface — adding a prop, tightening a type — is automatically reflected in both platforms without touching these files. The props removed from the local definition (children, size, isLoading, loadingText, isDisabled, isFullWidth) are now inherited through the intersection.

@@ -211,47 +211,6 @@ export enum BoxBorderColor {
Transparent = 'border-transparent',
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.

These removals are the payoff of ADR-0004: ButtonBaseSize, all five named aliases (ButtonSize, ButtonPrimarySize, etc.), and ButtonVariant no longer need to be defined here as TypeScript enums. They were duplicated across both platform src/types/index.ts files; now there is a single const-object definition in design-system-shared. ButtonIconSize and ButtonVariant for ButtonIcon remain as enums here because those components haven't been migrated yet.

ButtonSize,
FontWeight,
TextVariant,
} from '@metamask/design-system-shared';
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.

BannerBase wasn't part of the original ButtonBase/Button migration scope, but removing ButtonSize from src/types/index.ts surfaced it as a broken consumer. Moving ButtonSize into the @metamask/design-system-shared import alongside FontWeight and TextVariant is the correct fix — the same pattern applies identically in both the React and React Native versions of this file.

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

} & (
| (Omit<ButtonPrimaryProps, 'ref'> & {
variant?: ButtonVariant.Primary;
variant?: typeof ButtonVariant.Primary;
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.

typeof ButtonVariant.Primary is the idiomatic way to narrow a discriminated union member when working with ADR-0003 const objects. It resolves to the string literal type 'primary' at compile time, but unlike a bare string literal it keeps a live reference to the const object — so if the value ever changes, this type stays in sync automatically. Extract<ButtonVariant, 'primary'> was an earlier workaround that works but obscures intent; typeof ButtonVariant.X makes the narrowing intent explicit. Switching from import type to import is required so the const object value is available for typeof to reference.

@georgewrmarshall
Copy link
Copy Markdown
Contributor

Duplicate #1034

@georgewrmarshall georgewrmarshall deleted the cursor/-bc-a6f75f80-ffb2-470e-adba-eb0cab8c1f39-e500 branch April 30, 2026 19:20
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