Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
994f818
feat(DSYS-489): migrate Text to ADR-0003 and ADR-0004 shared types
cursoragent Apr 7, 2026
41f5cc0
chore: aligning text color
georgewrmarshall Apr 7, 2026
8367f37
chore: export TextColor from shared in Text component index files
georgewrmarshall Apr 7, 2026
d7682b1
feat(DSYS-489): migrate FontWeight, FontFamily, FontStyle to shared t…
georgewrmarshall Apr 7, 2026
d056c15
refactor(DSYS-489): remove Text shared type re-exports from platform …
georgewrmarshall Apr 7, 2026
aea3ae6
docs: document shared type import rule in component-architecture
georgewrmarshall Apr 7, 2026
b0ffbbd
docs: fix prettier formatting in component-architecture
georgewrmarshall Apr 7, 2026
e07a0a8
fix: update temp-component stories to import shared types from design…
georgewrmarshall Apr 7, 2026
3415877
fix: fix import ordering in temp-component stories
georgewrmarshall Apr 7, 2026
59ebe72
fix(text): scan design-system-shared in storybook tailwind config
georgewrmarshall Apr 7, 2026
c7a5357
docs(text): document TextColor.Inherit as web-only
georgewrmarshall Apr 7, 2026
e2afa45
docs(text): document hover TextColor values as web interaction state
georgewrmarshall Apr 7, 2026
c9371d0
fix: jsdoc formatting
georgewrmarshall Apr 7, 2026
ccbe933
refactor(text): move fontWeight, fontFamily, fontStyle to TextPropsSh…
georgewrmarshall Apr 8, 2026
f0d0155
fix(KeyValueColumn): import FontWeight, TextColor, TextVariant from s…
georgewrmarshall Apr 8, 2026
b39ab31
fix(KeyValueRow): import FontWeight, TextColor, TextVariant from shared
georgewrmarshall Apr 9, 2026
0d90f12
chore: updating linting
georgewrmarshall Apr 9, 2026
8140310
refactor(text): rename classmap constants to TWCLASSMAP_ prefix conve…
georgewrmarshall Apr 9, 2026
fdd2124
chore: updating migration docs
georgewrmarshall Apr 9, 2026
0553ad0
Merge branch 'main' into cursor/enum-shared-type-migration-1f3a
georgewrmarshall Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .cursor/rules/component-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,35 @@ export {
} from '@metamask/design-system-shared';
```

## Where to Import Shared Types

**Always import shared consts and types directly from `@metamask/design-system-shared`**, never through a sibling component's index.

```tsx
// ✅ Correct - import from shared (the owner)
import { TextVariant } from '@metamask/design-system-shared';

// ❌ Wrong - import through a sibling component's index
import { TextVariant } from '../Text';
```

**Why not `../Text`?**

Both `Input` and `Text` are _consumers_ of `TextVariant` — neither owns it. Importing through `../Text` creates:

1. **False semantic coupling** — implies `Input` is built on top of `Text`, which it isn't
2. **Fragile circular-dep risk** — if `Text` ever renders an `Input` internally, you get `Input → Text → Input 💥`
3. **Misleading dependency graph** — obscures that the real source is `@metamask/design-system-shared`

**The rule:**

| What you need | Import from |
| ------------------------------------------------------------------ | -------------------------------------------- |
| 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.


## Cross-Platform Consistency

**Required consistency:**
Expand Down
1 change: 1 addition & 0 deletions apps/storybook-react/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const designSystemPreset = require('@metamask/design-system-tailwind-preset');
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.

'../../packages/design-system-react/src/**/*.{js,jsx,ts,tsx}',
'../../packages/design-tokens/stories/**/*.{js,jsx,ts,tsx,mdx}',
'./src/**/*.{js,jsx,ts,tsx}',
Expand Down
35 changes: 35 additions & 0 deletions packages/design-system-react-native/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This guide provides detailed instructions for migrating your project from one ve
- [Icon Component](#icon-component)
- [Checkbox Component](#checkbox-component)
- [Version Updates](#version-updates)
- [From version 0.16.0 to 0.17.0](#from-version-0160-to-0170)
- [From version 0.15.0 to 0.16.0](#from-version-0150-to-0160)
- [From version 0.13.0 to 0.14.0](#from-version-0130-to-0140)
- [From version 0.12.0 to 0.13.0](#from-version-0120-to-0130)
Expand All @@ -26,6 +27,40 @@ This guide provides detailed instructions for migrating your project from one ve

## Version Updates

### From version 0.16.0 to 0.17.0

#### Text: Typography const values moved to `@metamask/design-system-shared`

`FontWeight`, `FontStyle`, `FontFamily`, `TextVariant`, and `TextColor` are now defined in `@metamask/design-system-shared` and re-exported from `@metamask/design-system-react-native`. All existing import paths through `@metamask/design-system-react-native` continue to work without change.

#### `FontWeight` values changed

**No migration likely needed.** `FontWeight` was a TypeScript `enum` before this release, so the underlying string values were inaccessible via the type system. Idiomatic usage (`fontWeight={FontWeight.Bold}`) continues to work without change — the TWRNC classmap handles the mapping internally.

The values did change to semantic identifiers for cross-platform sharing:

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

If you were comparing against the raw numeric string values directly, update to use the const member instead:

```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) { ... }
```

#### `TextColor` additions

`TextColor` gains four hover-state keys that were previously web-only (`PrimaryDefaultHover`, `ErrorDefaultHover`, `SuccessDefaultHover`, `WarningDefaultHover`). These are non-breaking additions. Their JSDoc notes that hover does not exist as an interaction state on React Native — use the corresponding `*Pressed` variant instead.

---

### From version 0.13.0 to 0.14.0

#### BottomSheet navigation callback change
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FontWeight, TextVariant } from '@metamask/design-system-shared';
import React from 'react';
import { GestureResponderEvent } from 'react-native';

Expand All @@ -6,10 +7,8 @@ import {
BoxBackgroundColor,
ButtonIconSize,
ButtonSize,
FontWeight,
BoxFlexDirection,
IconName,
TextVariant,
} from '../../types';
import { Box } from '../Box';
import { Button } from '../Button';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TextColor } from '@metamask/design-system-shared';
import type { Meta, StoryObj } from '@storybook/react-native';
import React from 'react';
import { ScrollView } from 'react-native';
Expand All @@ -9,7 +10,6 @@ import {
BoxJustifyContent,
BoxBackgroundColor,
BoxBorderColor,
TextColor,
} from '../../types';
import { Text } from '../Text';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { TextVariant } from '@metamask/design-system-shared';
import { typography } from '@metamask/design-tokens';

import { TextVariant } from '../../types';

/**
* Typographic metrics for Input: same tokens as `text-*` utilities but **without** `lineHeight`.
* React Native `TextInput` aligns single-line text more predictably when line height is not set
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { TextVariant } from '@metamask/design-system-shared';
import type { Meta, StoryObj } from '@storybook/react-native';
import { useEffect, useState } from 'react';
import { View } from 'react-native';

import { TextVariant } from '../../types';

import { Input } from './Input';
import type { InputProps } from './Input.types';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TextVariant } from '@metamask/design-system-shared';
import {
Theme,
ThemeProvider,
Expand All @@ -11,8 +12,6 @@ import { Platform, TextInput } from 'react-native';
import type { StyleProp, TextStyle } from 'react-native';
import { create } from 'react-test-renderer';

import { TextVariant } from '../../types';

import { Input } from './Input';

const TEST_ID = 'input';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FontFamily, TextVariant } from '@metamask/design-system-shared';
import {
Theme,
useTailwind,
Expand All @@ -7,9 +8,8 @@ import { darkTheme, lightTheme } from '@metamask/design-tokens';
import { forwardRef, useCallback, useMemo, useState } from 'react';
import { Platform, TextInput } from 'react-native';

import { FontFamily, TextVariant } from '../../types';
import {
MAP_TEXT_VARIANT_FONTWEIGHT,
TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT,
TWCLASSMAP_TEXT_FONTWEIGHT,
} from '../Text/Text.constants';

Expand Down Expand Up @@ -46,7 +46,7 @@ export const Input = forwardRef<TextInput, InputProps>(
[theme],
);

const finalFontWeight = MAP_TEXT_VARIANT_FONTWEIGHT[textVariant];
const finalFontWeight = TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[textVariant];
const fontSuffix = TWCLASSMAP_TEXT_FONTWEIGHT[finalFontWeight];
const fontClass = `font-${FontFamily.Default}${fontSuffix}`;
const hasPlaceholder =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { TextVariant } from '@metamask/design-system-shared';
import type { TextInputProps } from 'react-native';

import type { TextVariant } from '../../types';

export type InputProps = Omit<
TextInputProps,
'editable' | 'value' | 'defaultValue'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react';

import {
ButtonIconSize,
FontWeight,
IconColor,
TextColor,
TextVariant,
} from '../../types';
} from '@metamask/design-system-shared';
import React from 'react';

import { ButtonIconSize, IconColor } from '../../types';
import { BoxColumn } from '../BoxColumn';
import { BoxRow } from '../BoxRow';
import { ButtonIcon } from '../ButtonIcon';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { KeyValueRowVariant } from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import React from 'react';

import {
ButtonIconSize,
FontWeight,
IconColor,
KeyValueRowVariant,
TextColor,
TextVariant,
} from '../../types';
} from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import React from 'react';

import { ButtonIconSize, IconColor } from '../../types';
import { Box } from '../Box';
import { BoxRow } from '../BoxRow';
import { ButtonIcon } from '../ButtonIcon';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { typography } from '@metamask/design-tokens';

import { FontWeight, TextVariant } from '../../types';
import { FontWeight, TextVariant } from '@metamask/design-system-shared';

// Mappings
export const TWCLASSMAP_TEXT_FONTWEIGHT: {
Expand All @@ -11,25 +9,21 @@ export const TWCLASSMAP_TEXT_FONTWEIGHT: {
[FontWeight.Bold]: '-bold',
};

export const MAP_TEXT_VARIANT_FONTWEIGHT: {
export const TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT: {
[key in TextVariant]: FontWeight;
} = {
[TextVariant.DisplayLg]: typography.sDisplayLG.fontWeight as FontWeight,
[TextVariant.DisplayMd]: typography.sDisplayMD.fontWeight as FontWeight,
[TextVariant.HeadingLg]: typography.sHeadingLG.fontWeight as FontWeight,
[TextVariant.HeadingMd]: typography.sHeadingMD.fontWeight as FontWeight,
[TextVariant.HeadingSm]: typography.sHeadingSM.fontWeight as FontWeight,
[TextVariant.BodyLg]: typography.sBodyLGMedium.fontWeight as FontWeight,
[TextVariant.BodyMd]: typography.sBodyMD.fontWeight as FontWeight,
[TextVariant.BodySm]: typography.sBodySM.fontWeight as FontWeight,
[TextVariant.BodyXs]: typography.sBodyXS.fontWeight as FontWeight,
[TextVariant.PageHeading]: typography.sPageHeading.fontWeight as FontWeight,
[TextVariant.SectionHeading]: typography.sSectionHeading
.fontWeight as FontWeight,
[TextVariant.ButtonLabelMd]: typography.sButtonLabelMd
.fontWeight as FontWeight,
[TextVariant.ButtonLabelLg]: typography.sButtonLabelLg
.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.

[TextVariant.DisplayMd]: FontWeight.Bold,
[TextVariant.HeadingLg]: FontWeight.Bold,
[TextVariant.HeadingMd]: FontWeight.Bold,
[TextVariant.HeadingSm]: FontWeight.Bold,
[TextVariant.BodyLg]: FontWeight.Medium,
[TextVariant.BodyMd]: FontWeight.Regular,
[TextVariant.BodySm]: FontWeight.Regular,
[TextVariant.BodyXs]: FontWeight.Regular,
[TextVariant.PageHeading]: FontWeight.Bold,
[TextVariant.SectionHeading]: FontWeight.Bold,
[TextVariant.ButtonLabelMd]: FontWeight.Medium,
[TextVariant.ButtonLabelLg]: FontWeight.Medium,
[TextVariant.AmountDisplayLg]: FontWeight.Bold,
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {
FontFamily,
FontStyle,
FontWeight,
TextColor,
TextVariant,
} from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import type { Meta, StoryObj } from '@storybook/react-native';
import React from 'react';
import { View, ScrollView } from 'react-native';

import {
TextVariant,
TextColor,
FontWeight,
FontFamily,
FontStyle,
} from '../../types';

import { Text } from './Text';
import type { TextProps } from './Text.types';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { render } from '@testing-library/react-native';
import React from 'react';

import {
TextVariant,
TextColor,
FontWeight,
FontFamily,
FontStyle,
} from '../../types';
FontWeight,
TextColor,
TextVariant,
} from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { render } from '@testing-library/react-native';
import React from 'react';

import { Text } from './Text';
import {
TWCLASSMAP_TEXT_FONTWEIGHT,
MAP_TEXT_VARIANT_FONTWEIGHT,
TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT,
} from './Text.constants';

function buildTextStyleArgs({
Expand All @@ -31,7 +30,7 @@ function buildTextStyleArgs({
fontStyle: FontStyle;
twClassName: string;
}> = {}) {
const fw = fontWeight ?? MAP_TEXT_VARIANT_FONTWEIGHT[variant];
const fw = fontWeight ?? TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant];
const isItalic = fontStyle === FontStyle.Italic;
const fontSuffix = `${TWCLASSMAP_TEXT_FONTWEIGHT[fw]}${
isItalic && fontFamily === FontFamily.Default ? '-italic' : ''
Expand Down Expand Up @@ -104,7 +103,7 @@ describe('Text', () => {
expectedStyles = tw.style(
...buildTextStyleArgs({
variant,
fontWeight: MAP_TEXT_VARIANT_FONTWEIGHT[variant],
fontWeight: TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant],
}),
);
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {
FontFamily,
FontStyle,
TextColor,
TextVariant,
} from '@metamask/design-system-shared';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import React, { useMemo } from 'react';
import { Text as RNText } from 'react-native';

import { FontFamily, FontStyle, TextVariant, TextColor } from '../../types';

import {
MAP_TEXT_VARIANT_FONTWEIGHT,
TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT,
TWCLASSMAP_TEXT_FONTWEIGHT,
} from './Text.constants';
import type { TextProps } from './Text.types';
Expand All @@ -22,7 +26,8 @@ export const Text: React.FC<TextProps> = ({
...props
}) => {
const tw = useTailwind();
const finalFontWeight = fontWeight ?? MAP_TEXT_VARIANT_FONTWEIGHT[variant];
const finalFontWeight =
fontWeight ?? TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant];

const textStyle = useMemo(() => {
const isItalic = fontStyle === FontStyle.Italic;
Expand Down
Loading
Loading