Skip to content

feat(DSYS-489): Migrate Text to ADR-0003 and ADR-0004#1047

Merged
georgewrmarshall merged 20 commits intomainfrom
cursor/enum-shared-type-migration-1f3a
Apr 9, 2026
Merged

feat(DSYS-489): Migrate Text to ADR-0003 and ADR-0004#1047
georgewrmarshall merged 20 commits intomainfrom
cursor/enum-shared-type-migration-1f3a

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor bot commented Apr 7, 2026

Description

This PR migrates the Text component to align with ADR-0003 (String Unions) and ADR-0004 (Centralized Types Architecture) as part of epic DSYS-468.

Changes

New shared types (@metamask/design-system-shared)

  • TextVariant — converted from duplicated enum TextVariant in both platform packages to a single const object with string union type (ADR-0003). Values are identical across React and React Native.
  • TextColor — new shared const object containing all common color values across both platforms (ADR-0003). React extends this with hover states (PrimaryDefaultHover, ErrorDefaultHover, SuccessDefaultHover, WarningDefaultHover) and Inherit. React Native extends with PrimaryAlternative.
  • TextPropsShared — new shared props type (ADR-0004) containing cross-platform Text props: variant, children, and color (typed as string to allow platform-specific extensions).

Updated platform packages (design-system-react and design-system-react-native)

  • Removed enum TextVariant from both src/types/index.ts — now re-exported from shared
  • Removed enum TextColor from both src/types/index.ts — replaced with platform-extended const objects
  • Removed enum FontWeight, enum FontFamily, enum FontStyle from both src/types/index.ts — converted to const objects (platform-specific values preserved)
  • Removed enum TextAlign, enum OverflowWrap, enum TextTransform from React src/types/index.ts — converted to const objects (React/Tailwind-specific)
  • Text.types.ts in both packages now extends TextPropsShared from shared
  • Text/index.ts in both packages exports TextVariant and TextPropsShared from shared

Platform differences preserved

  • FontWeight: React uses Tailwind classes (font-bold, font-medium, font-regular); RN uses numeric values (600, 500, 400)
  • FontFamily: React uses Tailwind classes (font-default, font-accent, font-hero); RN uses plain names (default, accent, hero)
  • FontStyle: React uses Tailwind values (italic, not-italic); RN uses CSS values (italic, normal)
  • TextAlign, OverflowWrap, TextTransform: React-only (Tailwind-specific)

Related issues

Fixes: DSYS-489

Manual testing steps

  1. Run yarn build — all packages build successfully
  2. Run yarn test — all tests pass with 100% coverage on changed components
  3. Verify TextVariant is importable from @metamask/design-system-shared
  4. Verify TextColor is importable from @metamask/design-system-shared
  5. Verify TextPropsShared is importable from @metamask/design-system-shared

Screenshots/Recordings

Before

No visual changes.

before.text.720.mov

After

No visual changes. Text component in storybook works as expected and no visual regressions picked up in Chromatic run

text.migration.after.720.mov
Screenshot 2026-04-07 at 3 57 11 PM

Testing preview packages in mobile works with no typescript or visual issues

Test PR: MetaMask/metamask-mobile#28575

Mobile running with MMDS Text

design-tokens.pure.black.before720.mov

Testing preview packages in extension works with no typescript or visual issues

Test PR: MetaMask/metamask-extension#41582

Extension running with MMDS Text

Screenshot 2026-04-08 at 2 13 00 PM

Extension storybook running with MMDS Text

Screenshot 2026-04-08 at 2 14 09 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
  • 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
Moves Text-related enums to shared const-object/string-union types and updates both React and React Native implementations to map these semantic values to platform-specific class names, which could cause subtle styling or build regressions if any mapping/import is missed. Also changes Tailwind content scanning requirements by relocating class-name strings into @metamask/design-system-shared.

Overview
Migrates Text typography primitives (TextVariant, TextColor, FontWeight, FontFamily, FontStyle) to @metamask/design-system-shared as const objects + string unions, and updates both design-system-react and design-system-react-native to import/re-export and to base their TextProps on the new shared TextPropsShared.

Updates React and RN components/tests/stories (including Input, BannerBase, TextButton, KeyValueRow/Column) to use the shared types, and refactors Text.constants to map semantic shared values to Tailwind/RN font class behavior. Adds migration notes and a Cursor architecture rule clarifying direct imports from @metamask/design-system-shared, plus updates Storybook React Tailwind content scanning to include shared sources.

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@georgewrmarshall georgewrmarshall force-pushed the cursor/enum-shared-type-migration-1f3a branch from b23ac6d to 25b28ad Compare April 7, 2026 21:46
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

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.

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

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 ebc9dd5. Configure here.

@georgewrmarshall georgewrmarshall self-assigned this Apr 7, 2026
@georgewrmarshall georgewrmarshall force-pushed the cursor/enum-shared-type-migration-1f3a branch from 116713a to dff9dce Compare April 7, 2026 23:03
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@georgewrmarshall
Copy link
Copy Markdown
Contributor

@metamaskbot publish-preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

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.14.0-preview.dff9dce",
  "@metamask-previews/design-system-react-native": "0.14.0-preview.dff9dce",
  "@metamask-previews/design-system-shared": "0.7.0-preview.dff9dce",
  "@metamask-previews/design-system-tailwind-preset": "0.6.1-preview.dff9dce",
  "@metamask-previews/design-system-twrnc-preset": "0.4.1-preview.dff9dce",
  "@metamask-previews/design-tokens": "8.3.0-preview.dff9dce"
}

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📖 Storybook Preview

@georgewrmarshall
Copy link
Copy Markdown
Contributor

@metamaskbot publish-preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

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.14.0-preview.4ce710b",
  "@metamask-previews/design-system-react-native": "0.14.0-preview.4ce710b",
  "@metamask-previews/design-system-shared": "0.7.0-preview.4ce710b",
  "@metamask-previews/design-system-tailwind-preset": "0.6.1-preview.4ce710b",
  "@metamask-previews/design-system-twrnc-preset": "0.4.1-preview.4ce710b",
  "@metamask-previews/design-tokens": "8.3.0-preview.4ce710b"
}

*/
export const FontWeight = {
/** Weight - 600 */
Bold: 'bold',
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.

Why semantic values instead of platform values?

FontWeight uses semantic strings ('bold'/'medium'/'regular') rather than Tailwind classes ('font-bold') or React Native numeric weights ('600'). This keeps the shared contract platform-neutral — each platform maps these to its own representation via constants (e.g. MAP_FONTWEIGHT_CLASS in React, TWCLASSMAP_TEXT_FONTWEIGHT in React Native). The same applies to FontFamily and FontStyle.

*
* @platform web — CSS cascade has no equivalent in React Native; fails silently on mobile.
*/
Inherit: 'text-inherit',
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.

Why is Inherit in shared if it's web-only?

text-inherit is a CSS concept with no React Native equivalent — it fails silently on mobile. We chose to keep it in shared rather than create a web-only extension type (TextColorWeb) because that would cause React's TextColor to diverge from shared's, undermining ADR-0004's single-source-of-truth intent. The @platform web JSDoc communicates the limitation without the architectural overhead. Confirmed via manual testing: silent failure, no crash.

TextVariant,
} from '@metamask/design-system-shared';

export const MAP_FONTWEIGHT_CLASS: Record<FontWeight, string> = {
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.

Why are these mapping constants needed?

The shared FontWeight const uses semantic values ('bold'/'medium'/'regular') that can't be passed directly to twMerge — Tailwind expects utility class strings like 'font-bold'. These maps translate the shared semantic contract to Tailwind classes at the React layer. The React Native equivalent (TWCLASSMAP_TEXT_FONTWEIGHT) maps to twrnc font-name suffixes instead.

Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall Apr 9, 2026

Choose a reason for hiding this comment

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

Still a valid comment just updated the const to follow TWCLASSMAP prefix

fontStyle,
fontFamily,
fontWeight
? MAP_FONTWEIGHT_CLASS[fontWeight]
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.

Why the conditional pattern for fontWeight but not fontFamily?

fontWeight is optional and falls back to the variant's default weight (CLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant]) when not provided, so it needs a conditional. fontFamily always has a default value (FontFamily.Default) applied at the component level, so it's always defined here and the map can be called unconditionally.

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.

Still a valid comment just updated the const to follow TWCLASSMAP prefix

.fontWeight as FontWeight,
[TextVariant.AmountDisplayLg]: typography.sAmountDisplayLg
.fontWeight as FontWeight,
[TextVariant.DisplayLg]: FontWeight.Bold,
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.

Why replace @metamask/design-tokens lookups with explicit values?

Previously, MAP_TEXT_VARIANT_FONTWEIGHT derived values by casting from the typography token object (e.g. typography.sDisplayLG.fontWeight as FontWeight), which created a runtime dependency on the tokens package and required unsafe type casts. The shared FontWeight const now uses semantic strings that don't match the numeric values in @metamask/design-tokens ('600'/'500'/'400'), making the cast invalid. Explicit values are clearer, type-safe, and remove the dependency.

module.exports = {
presets: [designSystemPreset],
content: [
'../../packages/design-system-shared/src/**/*.{js,jsx,ts,tsx}',
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.

Why does design-system-shared need to be in the content paths?

Before this migration, TextColor and other const values were defined as literal strings inside design-system-react/src/types/index.ts, which was already scanned. After moving to design-system-shared, Tailwind JIT can no longer find those literals during scanning, so utility classes like text-primary-default-hover were never generated — causing hover/pressed text colors to silently render as unstyled. Adding the shared package to content paths restores scanning of the literal strings.

export type TextProps = {
import type { OverflowWrap, TextAlign, TextTransform } from '../../types';

export type TextProps = TextPropsShared & {
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.

Why are TextAlign, TextTransform, OverflowWrap still imported from ../../types while FontWeight/FontFamily/FontStyle moved to shared?

TextAlign, TextTransform, and OverflowWrap are React-only props (web CSS concepts) with no equivalent in the React Native TextProps. They belong in the platform extension layer. FontWeight, FontFamily, and FontStyle apply identically on both platforms and have been promoted to TextPropsShared accordingly.

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.

Will do a follow up PR to add this functionality to React Native Text out of scope of this PR

georgewrmarshall added a commit to MetaMask/metamask-extension that referenced this pull request Apr 8, 2026
Temporarily swaps @metamask/design-system-react, design-system-shared,
design-system-tailwind-preset, and design-tokens to their
@metamask-previews builds to test MetaMask/metamask-design-system#1047.
georgewrmarshall added a commit to MetaMask/metamask-mobile that referenced this pull request Apr 8, 2026
…-design-system#1047)

Temporarily updates @metamask/design-system-react-native,
@metamask/design-system-shared, @metamask/design-system-twrnc-preset,
and @metamask/design-tokens to their @metamask-previews builds in order
to test the Text component changes in MetaMask/metamask-design-system#1047.

Do not merge. This branch is for testing purposes only.
@georgewrmarshall georgewrmarshall marked this pull request as ready for review April 8, 2026 22:27
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

📖 Storybook Preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

📖 Storybook Preview

| Shared const/type (`TextVariant`, `TextColor`, `FontWeight`, etc.) | `@metamask/design-system-shared` |
| A sibling component to render it | `'../ComponentName'` |
| A sibling component's platform-specific props type | `'../ComponentName'` |
| A sibling component's mapping constants | `'../ComponentName/ComponentName.constants'` |
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.

Why can't you import TextVariant from '../Text'?

Both Input and Text are consumers of TextVariant — neither owns it. Importing through '../Text' creates a false semantic coupling (implying Input is built on top of Text), a latent circular-dependency risk (Input → Text → Input if Text ever renders an Input), and obscures that the real source is @metamask/design-system-shared. The table here codifies the rule into a quick reference that scales to every shared type, not just TextVariant.

* Optional prop to add twrnc overriding classNames.
*/
twClassName?: string;
};
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.

Why is twClassName the only prop left in TextProps?

Everything else — variant, color, fontWeight, fontFamily, fontStyle, children, style — is now covered by TextPropsShared (from shared) intersected with RNTextProps (from react-native). The only genuinely React Native-specific addition is twClassName, which controls TWRNC class overrides and has no web equivalent. This 52 → 13 line reduction is the ADR-0004 payoff: platform-agnostic prop documentation lives once in the shared package, and platform files declare only what's unique to their surface.

@@ -271,116 +271,6 @@ export enum ButtonIconVariant {
Floating = 'floating',
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.

Why delete 116 lines instead of just deprecating them?

These enums (TextVariant, TextColor, FontWeight, FontStyle, FontFamily) were internal implementation details never meant to be imported directly from ../../types by consumers — they were only there because the shared package didn't exist yet. Now that the authoritative definitions live in @metamask/design-system-shared (with const objects per ADR-0003), keeping a second copy here would mean two sources of truth diverging silently. Hard deletion forces all import sites to point at the canonical source, which is the right behaviour for a migration that's meant to be complete.

export type TextTransform = (typeof TextTransform)[keyof typeof TextTransform];

/**
* TextButton - size
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.

Why does the React types/index.ts diff look different from the RN one?

React had 155 lines removed vs 116 in RN because it carried extra hover/pressed pseudo-state variants (PrimaryDefaultHover, ErrorDefaultHover, etc.) that RN never had — hover states are a web-only interaction model. Those values now live in TextColor in @metamask/design-system-shared with @note React Native: Not applicable JSDoc, making the platform distinction explicit in the shared source rather than implicit in platform-specific files.

The three web-only const objects that remain here (TextAlign, OverflowWrap, TextTransform) have been converted from enum to ADR-0003 const objects — they're web-only layout utilities with no RN equivalent, so they stay in this file rather than moving to shared.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

📖 Storybook Preview

georgewrmarshall added a commit that referenced this pull request Apr 9, 2026
Moves the full IconColor const object (union of all React and React
Native values) into @metamask/design-system-shared following the same
pattern as TextColor in PR #1047.

Both platforms now re-export IconColor directly from shared — the
platform-local definition and the IconColorBase alias are removed.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit 8b8f9b4 into main Apr 9, 2026
44 checks passed
@georgewrmarshall georgewrmarshall deleted the cursor/enum-shared-type-migration-1f3a branch April 9, 2026 22:31
georgewrmarshall added a commit that referenced this pull request Apr 9, 2026
Moves the full IconColor const object (union of all React and React
Native values) into @metamask/design-system-shared following the same
pattern as TextColor in PR #1047.

Both platforms now re-export IconColor directly from shared — the
platform-local definition and the IconColorBase alias are removed.
@georgewrmarshall georgewrmarshall mentioned this pull request Apr 9, 2026
17 tasks
georgewrmarshall added a commit that referenced this pull request Apr 9, 2026
## Release 32.0.0

This release migrates `Text` typography types to
`@metamask/design-system-shared`, continuing the ADR-0003/0004
const-object + string-union pattern adoption.

### 📦 Package Versions

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

### 🔄 Shared Type Updates (0.10.0)

#### Text typography types added
([#1047](#1047))

**What Changed:**

- Added `TextVariant`, `TextColor`, and `TextPropsShared` shared types
for cross-platform use

**Impact:**

- Enables consistent `Text` type definitions across React and React
Native
- Continues ADR-0003/0004 const-object + string-union pattern adoption

### 📱 React Native Updates (0.17.0)

#### Changed

- **BREAKING:** Migrated `Text` typography types (`TextVariant`,
`TextColor`, `FontWeight`, `FontStyle`, `FontFamily`) to
`@metamask/design-system-shared`; all imports through
`@metamask/design-system-react-native` continue to work without change
([#1047](#1047))
- `FontWeight` underlying string values changed from numeric strings
(`'600'`, `'500'`, `'400'`) to semantic identifiers (`'bold'`,
`'medium'`, `'regular'`); idiomatic usage (e.g. `FontWeight.Bold`) is
unaffected

### ⚠️ Breaking Changes

#### `FontWeight` string values changed (React Native Only)

**What Changed:**

- `FontWeight` was previously a TypeScript `enum` with numeric string
values; it is now a `const` object with semantic string values

| Key                  | Before (0.16.0) | After (0.17.0) |
| -------------------- | --------------- | -------------- |
| `FontWeight.Bold`    | `'600'`         | `'bold'`       |
| `FontWeight.Medium`  | `'500'`         | `'medium'`     |
| `FontWeight.Regular` | `'400'`         | `'regular'`    |

**Migration:**

```tsx
// ❌ Rare: comparing against raw numeric string
if (fontWeight === '600') { ... }

// ✅ Use const member (works in both 0.16.0 and 0.17.0)
if (fontWeight === FontWeight.Bold) { ... }
```

**Impact:**

- Only affects code that compared against raw `FontWeight` numeric
string values directly
- Idiomatic usage (`fontWeight={FontWeight.Bold}`) is unaffected

See migration guide for complete instructions:

- [React Native Migration
Guide](./packages/design-system-react-native/MIGRATION.md#from-version-0160-to-0170)

### ✅ Checklist

- [x] Changelogs updated with human-readable descriptions
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] Version bumps follow semantic versioning
- [x] design-system-shared: minor (0.9.0 → 0.10.0) - new Text shared
types added
- [x] design-system-react-native: minor (0.16.0 → 0.17.0) - breaking
FontWeight value change
- [x] Breaking changes documented with migration guidance
- [x] Migration guides updated with before/after examples
- [x] PR references included in changelog entries

## **Pre-merge author checklist**

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

## **Pre-merge reviewer checklist**

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because this is a release bump that documents a *breaking*
underlying `FontWeight` runtime string-value change; consumers comparing
raw string values may break even though import paths remain stable.
> 
> **Overview**
> Bumps the monorepo to `32.0.0` and releases
`@metamask/design-system-react-native` `0.17.0` and
`@metamask/design-system-shared` `0.10.0`.
> 
> Updates changelogs to reflect moving `Text` typography types
(`TextVariant`, `TextColor`, `FontWeight`, `FontStyle`, `FontFamily`,
`TextPropsShared`) into `@metamask/design-system-shared`, including a
**breaking** note that `FontWeight` raw string values change from
numeric strings to semantic identifiers.
> 
> Adds a release-workflow step to run `yarn changelog:validate` after
editing changelogs.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a05e001. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants