diff --git a/.cursor/rules/component-architecture.md b/.cursor/rules/component-architecture.md index a81298a2c..7fdf18e37 100644 --- a/.cursor/rules/component-architecture.md +++ b/.cursor/rules/component-architecture.md @@ -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'` | + ## Cross-Platform Consistency **Required consistency:** diff --git a/apps/storybook-react/tailwind.config.js b/apps/storybook-react/tailwind.config.js index 35b4c3063..babe54180 100644 --- a/apps/storybook-react/tailwind.config.js +++ b/apps/storybook-react/tailwind.config.js @@ -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}', '../../packages/design-system-react/src/**/*.{js,jsx,ts,tsx}', '../../packages/design-tokens/stories/**/*.{js,jsx,ts,tsx,mdx}', './src/**/*.{js,jsx,ts,tsx}', diff --git a/packages/design-system-react-native/MIGRATION.md b/packages/design-system-react-native/MIGRATION.md index 6b79c92fa..e0a217f24 100644 --- a/packages/design-system-react-native/MIGRATION.md +++ b/packages/design-system-react-native/MIGRATION.md @@ -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) @@ -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 diff --git a/packages/design-system-react-native/src/components/BannerBase/BannerBase.tsx b/packages/design-system-react-native/src/components/BannerBase/BannerBase.tsx index d7a7358b1..63eba2c9c 100644 --- a/packages/design-system-react-native/src/components/BannerBase/BannerBase.tsx +++ b/packages/design-system-react-native/src/components/BannerBase/BannerBase.tsx @@ -1,3 +1,4 @@ +import { FontWeight, TextVariant } from '@metamask/design-system-shared'; import React from 'react'; import { GestureResponderEvent } from 'react-native'; @@ -6,10 +7,8 @@ import { BoxBackgroundColor, ButtonIconSize, ButtonSize, - FontWeight, BoxFlexDirection, IconName, - TextVariant, } from '../../types'; import { Box } from '../Box'; import { Button } from '../Button'; diff --git a/packages/design-system-react-native/src/components/Box/Box.stories.tsx b/packages/design-system-react-native/src/components/Box/Box.stories.tsx index bbf77e688..ed5b7f17e 100644 --- a/packages/design-system-react-native/src/components/Box/Box.stories.tsx +++ b/packages/design-system-react-native/src/components/Box/Box.stories.tsx @@ -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'; @@ -9,7 +10,6 @@ import { BoxJustifyContent, BoxBackgroundColor, BoxBorderColor, - TextColor, } from '../../types'; import { Text } from '../Text'; diff --git a/packages/design-system-react-native/src/components/Input/Input.constants.ts b/packages/design-system-react-native/src/components/Input/Input.constants.ts index 127d5df1a..841638c6a 100644 --- a/packages/design-system-react-native/src/components/Input/Input.constants.ts +++ b/packages/design-system-react-native/src/components/Input/Input.constants.ts @@ -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 diff --git a/packages/design-system-react-native/src/components/Input/Input.stories.tsx b/packages/design-system-react-native/src/components/Input/Input.stories.tsx index ad606389f..8cafe5630 100644 --- a/packages/design-system-react-native/src/components/Input/Input.stories.tsx +++ b/packages/design-system-react-native/src/components/Input/Input.stories.tsx @@ -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'; diff --git a/packages/design-system-react-native/src/components/Input/Input.test.tsx b/packages/design-system-react-native/src/components/Input/Input.test.tsx index 632a9de1c..47de73a55 100644 --- a/packages/design-system-react-native/src/components/Input/Input.test.tsx +++ b/packages/design-system-react-native/src/components/Input/Input.test.tsx @@ -1,3 +1,4 @@ +import { TextVariant } from '@metamask/design-system-shared'; import { Theme, ThemeProvider, @@ -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'; diff --git a/packages/design-system-react-native/src/components/Input/Input.tsx b/packages/design-system-react-native/src/components/Input/Input.tsx index 43ce608c4..bbae21503 100644 --- a/packages/design-system-react-native/src/components/Input/Input.tsx +++ b/packages/design-system-react-native/src/components/Input/Input.tsx @@ -1,3 +1,4 @@ +import { FontFamily, TextVariant } from '@metamask/design-system-shared'; import { Theme, useTailwind, @@ -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'; @@ -46,7 +46,7 @@ export const Input = forwardRef( [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 = diff --git a/packages/design-system-react-native/src/components/Input/Input.types.ts b/packages/design-system-react-native/src/components/Input/Input.types.ts index 9cc76639b..759ffec9a 100644 --- a/packages/design-system-react-native/src/components/Input/Input.types.ts +++ b/packages/design-system-react-native/src/components/Input/Input.types.ts @@ -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' diff --git a/packages/design-system-react-native/src/components/KeyValueColumn/KeyValueColumn.tsx b/packages/design-system-react-native/src/components/KeyValueColumn/KeyValueColumn.tsx index c81f33ab2..9bf42dc46 100644 --- a/packages/design-system-react-native/src/components/KeyValueColumn/KeyValueColumn.tsx +++ b/packages/design-system-react-native/src/components/KeyValueColumn/KeyValueColumn.tsx @@ -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'; diff --git a/packages/design-system-react-native/src/components/KeyValueRow/KeyValueRow.tsx b/packages/design-system-react-native/src/components/KeyValueRow/KeyValueRow.tsx index bcba63619..6e0f6b10b 100644 --- a/packages/design-system-react-native/src/components/KeyValueRow/KeyValueRow.tsx +++ b/packages/design-system-react-native/src/components/KeyValueRow/KeyValueRow.tsx @@ -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'; diff --git a/packages/design-system-react-native/src/components/Text/Text.constants.ts b/packages/design-system-react-native/src/components/Text/Text.constants.ts index 44cdb9fea..a01792e08 100644 --- a/packages/design-system-react-native/src/components/Text/Text.constants.ts +++ b/packages/design-system-react-native/src/components/Text/Text.constants.ts @@ -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: { @@ -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, + [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, }; diff --git a/packages/design-system-react-native/src/components/Text/Text.stories.tsx b/packages/design-system-react-native/src/components/Text/Text.stories.tsx index a47ef2597..764d7d13f 100644 --- a/packages/design-system-react-native/src/components/Text/Text.stories.tsx +++ b/packages/design-system-react-native/src/components/Text/Text.stories.tsx @@ -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'; diff --git a/packages/design-system-react-native/src/components/Text/Text.test.tsx b/packages/design-system-react-native/src/components/Text/Text.test.tsx index 8a28f5b84..2dc25e023 100644 --- a/packages/design-system-react-native/src/components/Text/Text.test.tsx +++ b/packages/design-system-react-native/src/components/Text/Text.test.tsx @@ -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({ @@ -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' : '' @@ -104,7 +103,7 @@ describe('Text', () => { expectedStyles = tw.style( ...buildTextStyleArgs({ variant, - fontWeight: MAP_TEXT_VARIANT_FONTWEIGHT[variant], + fontWeight: TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant], }), ); return ( diff --git a/packages/design-system-react-native/src/components/Text/Text.tsx b/packages/design-system-react-native/src/components/Text/Text.tsx index 812bc043a..eec8412bf 100644 --- a/packages/design-system-react-native/src/components/Text/Text.tsx +++ b/packages/design-system-react-native/src/components/Text/Text.tsx @@ -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'; @@ -22,7 +26,8 @@ export const Text: React.FC = ({ ...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; diff --git a/packages/design-system-react-native/src/components/Text/Text.types.ts b/packages/design-system-react-native/src/components/Text/Text.types.ts index 56f5b3bd4..82a0346d6 100644 --- a/packages/design-system-react-native/src/components/Text/Text.types.ts +++ b/packages/design-system-react-native/src/components/Text/Text.types.ts @@ -1,52 +1,13 @@ -// Third party dependencies. +import type { TextPropsShared } from '@metamask/design-system-shared'; import type { TextProps as RNTextProps } from 'react-native'; -import type { - TextVariant, - TextColor, - FontWeight, - FontFamily, - FontStyle, -} from '../../types'; - /** * Text component props. */ -export type TextProps = { - /** - * Optional enum to select between Typography variants. - * - * @default BodyMD - */ - variant?: TextVariant; - /** - * Text to be displayed. - */ - children: React.ReactNode; - /** - * Optional prop to add color to text. - */ - color?: TextColor; - /** - * Optional prop to control the font weight of the text. - * Normal: 400 - * Medium: 500 - * Bold: 600 - */ - fontWeight?: FontWeight; - /** - * Optional prop to adjust the font family. - * Default: Geist - * Accent: MM Sans - * Hero: MM Poly - */ - fontFamily?: FontFamily; - /** - * Optional prop to adjust the style of the font. - */ - fontStyle?: FontStyle; - /** - * Optional prop to add twrnc overriding classNames. - */ - twClassName?: string; -} & RNTextProps; +export type TextProps = TextPropsShared & + RNTextProps & { + /** + * Optional prop to add twrnc overriding classNames. + */ + twClassName?: string; + }; diff --git a/packages/design-system-react-native/src/components/Text/index.ts b/packages/design-system-react-native/src/components/Text/index.ts index e12cc202e..d46b9c465 100644 --- a/packages/design-system-react-native/src/components/Text/index.ts +++ b/packages/design-system-react-native/src/components/Text/index.ts @@ -1,9 +1,10 @@ export { - TextVariant, TextColor, + TextVariant, FontWeight, FontFamily, FontStyle, -} from '../../types'; + type TextPropsShared, +} from '@metamask/design-system-shared'; export { Text } from './Text'; export type { TextProps } from './Text.types'; diff --git a/packages/design-system-react-native/src/components/TextButton/TextButton.figma.tsx b/packages/design-system-react-native/src/components/TextButton/TextButton.figma.tsx index 80bc39899..f349ccb0a 100644 --- a/packages/design-system-react-native/src/components/TextButton/TextButton.figma.tsx +++ b/packages/design-system-react-native/src/components/TextButton/TextButton.figma.tsx @@ -1,10 +1,9 @@ // import figma needs to remain as figma otherwise it breaks code connect // eslint-disable-next-line import-x/no-named-as-default import figma from '@figma/code-connect'; +import { TextVariant } from '@metamask/design-system-shared'; import React from 'react'; -import { TextVariant } from '../../types'; - import { TextButton } from './TextButton'; /** diff --git a/packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx b/packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx index 142fd616e..4b8b5b3f4 100644 --- a/packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx +++ b/packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx @@ -1,7 +1,7 @@ +import { TextVariant } from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-native'; import { View } from 'react-native'; -import { TextVariant } from '../../types'; import { Text } from '../Text'; import { TextButton } from './TextButton'; diff --git a/packages/design-system-react-native/src/components/TextButton/TextButton.test.tsx b/packages/design-system-react-native/src/components/TextButton/TextButton.test.tsx index 9ddd698c6..ca3a8beec 100644 --- a/packages/design-system-react-native/src/components/TextButton/TextButton.test.tsx +++ b/packages/design-system-react-native/src/components/TextButton/TextButton.test.tsx @@ -1,10 +1,9 @@ +import { TextVariant } from '@metamask/design-system-shared'; import { useTailwind } from '@metamask/design-system-twrnc-preset'; import { renderHook } from '@testing-library/react-hooks'; import { render, fireEvent } from '@testing-library/react-native'; import React from 'react'; -import { TextVariant } from '../../types'; - import { TextButton } from './TextButton'; const noopPress = () => undefined; diff --git a/packages/design-system-react-native/src/components/TextButton/TextButton.tsx b/packages/design-system-react-native/src/components/TextButton/TextButton.tsx index 6687dfefe..ad75f4f78 100644 --- a/packages/design-system-react-native/src/components/TextButton/TextButton.tsx +++ b/packages/design-system-react-native/src/components/TextButton/TextButton.tsx @@ -1,6 +1,6 @@ +import { TextColor, TextVariant } from '@metamask/design-system-shared'; import React, { useState } from 'react'; -import { TextVariant, TextColor } from '../../types'; import { Text, FontWeight } from '../Text'; import type { TextButtonProps } from './TextButton.types'; diff --git a/packages/design-system-react-native/src/components/TextField/TextField.tsx b/packages/design-system-react-native/src/components/TextField/TextField.tsx index 700b71f16..13ad28194 100644 --- a/packages/design-system-react-native/src/components/TextField/TextField.tsx +++ b/packages/design-system-react-native/src/components/TextField/TextField.tsx @@ -1,3 +1,4 @@ +import { TextVariant } from '@metamask/design-system-shared'; import { useTailwind } from '@metamask/design-system-twrnc-preset'; import { forwardRef, @@ -9,7 +10,6 @@ import { } from 'react'; import { Pressable, TextInput, View } from 'react-native'; -import { TextVariant } from '../../types'; import { Input } from '../Input'; import type { TextFieldProps } from './TextField.types'; diff --git a/packages/design-system-react-native/src/types/index.ts b/packages/design-system-react-native/src/types/index.ts index 6e936c318..4c0c50619 100644 --- a/packages/design-system-react-native/src/types/index.ts +++ b/packages/design-system-react-native/src/types/index.ts @@ -271,116 +271,6 @@ export enum ButtonIconVariant { Floating = 'floating', } -/** - * Text - variant - */ -export enum TextVariant { - // Display Sizes - DisplayLg = 'display-lg', - DisplayMd = 'display-md', - - // Heading Sizes - HeadingLg = 'heading-lg', - HeadingMd = 'heading-md', - HeadingSm = 'heading-sm', - - // Body Sizes - BodyLg = 'body-lg', - BodyMd = 'body-md', - BodySm = 'body-sm', - BodyXs = 'body-xs', - - // Special Typography Variants - PageHeading = 'page-heading', - SectionHeading = 'section-heading', - ButtonLabelMd = 'button-label-md', - ButtonLabelLg = 'button-label-lg', - AmountDisplayLg = 'amount-display-lg', -} - -/** - * Text - color - */ -export enum TextColor { - /** For default neutral text. */ - TextDefault = 'text-default', - /** For softer contrast neutral text */ - TextAlternative = 'text-alternative', - /** For the softest contrast neutral text (not accessible) */ - TextMuted = 'text-muted', - /** For elements used on top of overlay/alternative. */ - OverlayInverse = 'text-overlay-inverse', - /** For interactive, active, and selected semantics. */ - PrimaryDefault = 'text-primary-default', - /** For softer variants of primary text. */ - PrimaryAlternative = 'text-primary-alternative', - /** For elements used on top of primary/default. */ - PrimaryInverse = 'text-primary-inverse', - /** For primary text in a pressed state. */ - PrimaryDefaultPressed = 'text-primary-default-pressed', - /** For critical alert text. */ - ErrorDefault = 'text-error-default', - /** For stronger contrast error text. */ - ErrorAlternative = 'text-error-alternative', - /** For elements used on top of error/default. */ - ErrorInverse = 'text-error-inverse', - /** For critical alert text in a pressed state. */ - ErrorDefaultPressed = 'text-error-default-pressed', - /** For caution alert text. */ - WarningDefault = 'text-warning-default', - /** For elements used on top of warning/default. */ - WarningInverse = 'text-warning-inverse', - /** For caution text in a pressed state. */ - WarningDefaultPressed = 'text-warning-default-pressed', - /** For positive semantic text. */ - SuccessDefault = 'text-success-default', - /** For elements used on top of success/default. */ - SuccessInverse = 'text-success-inverse', - /** For positive text in a pressed state. */ - SuccessDefaultPressed = 'text-success-default-pressed', - /** For informational read-only text. */ - InfoDefault = 'text-info-default', - /** For elements used on top of info/default. */ - InfoInverse = 'text-info-inverse', - /** Make the text color transparent. */ - Transparent = 'text-transparent', -} - -/** - * Text - fontWeight - */ -export enum FontWeight { - /** - * Weight - 600 - */ - Bold = '600', - /** - * Weight - 500 - */ - Medium = '500', - /** - * Weight - 400 - */ - Regular = '400', -} - -/** - * Text - fontStyle - */ -export enum FontStyle { - Italic = 'italic', - Normal = 'normal', -} - -/** - * Text - fontFamily - */ -export enum FontFamily { - Default = 'default', - Accent = 'accent', - Hero = 'hero', -} - /** * Icon - color */ diff --git a/packages/design-system-react/MIGRATION.md b/packages/design-system-react/MIGRATION.md index a44a4ac89..bbb735b76 100644 --- a/packages/design-system-react/MIGRATION.md +++ b/packages/design-system-react/MIGRATION.md @@ -14,6 +14,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.12.0 to 0.13.0](#from-version-0120-to-0130) - [From version 0.10.0 to 0.11.0](#from-version-0100-to-0110) - [From version 0.1.0 to 0.2.0](#from-version-010-to-020) @@ -669,6 +670,64 @@ import { Checkbox } from '@metamask/design-system-react'; This section covers version-to-version breaking changes within `@metamask/design-system-react`. +## 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`. All existing import paths through `@metamask/design-system-react` continue to work without change. + +#### `FontWeight`, `FontStyle`, and `FontFamily` values changed + +**No migration likely needed.** These were TypeScript `enum` types before this release, so the underlying string values were inaccessible via the type system and would only have been relied upon in rare circumstances. Idiomatic usage (`fontWeight={FontWeight.Bold}`) continues to work without change — the components handle the mapping internally. + +The values did change to semantic identifiers for cross-platform sharing with React Native: + +| Const | Key | Before (0.16.0) | After (0.17.0) | +| ------------ | ---------- | ---------------- | ---------------------- | +| `FontWeight` | `.Bold` | `'font-bold'` | `'bold'` | +| `FontWeight` | `.Medium` | `'font-medium'` | `'medium'` | +| `FontWeight` | `.Regular` | `'font-regular'` | `'regular'` | +| `FontStyle` | `.Normal` | `'not-italic'` | `'normal'` | +| `FontStyle` | `.Italic` | `'italic'` | `'italic'` (unchanged) | +| `FontFamily` | `.Default` | `'font-default'` | `'default'` | +| `FontFamily` | `.Accent` | `'font-accent'` | `'accent'` | +| `FontFamily` | `.Hero` | `'font-hero'` | `'hero'` | + +If you were comparing against the raw string values directly, update to use the const member instead: + +```tsx +// ❌ Rare: comparing against raw string value +if (fontWeight === 'font-bold') { ... } + +// ✅ Use const member (works in both 0.16.0 and 0.17.0) +if (fontWeight === FontWeight.Bold) { ... } +``` + +#### Breaking: Tailwind content scanning + +If your project scans `node_modules/@metamask/design-system-react` for Tailwind class names (e.g. to include `text-primary-default` from `TextColor`), you must also scan `@metamask/design-system-shared` because the class name strings now live in the shared package's compiled output. + +**Before (0.16.0):** + +```js +// tailwind.config.js +content: [ + './node_modules/@metamask/design-system-react/**/*.{mjs,cjs}', +], +``` + +**After (0.17.0):** + +```js +// tailwind.config.js +content: [ + './node_modules/@metamask/design-system-react/**/*.{mjs,cjs}', + './node_modules/@metamask/design-system-shared/**/*.{mjs,cjs}', +], +``` + +--- + ## From version 0.12.0 to 0.13.0 ### Typography: semantic bold is now semibold (600) diff --git a/packages/design-system-react/src/components/BannerBase/BannerBase.tsx b/packages/design-system-react/src/components/BannerBase/BannerBase.tsx index 4a90abad5..b8bd113a8 100644 --- a/packages/design-system-react/src/components/BannerBase/BannerBase.tsx +++ b/packages/design-system-react/src/components/BannerBase/BannerBase.tsx @@ -1,3 +1,4 @@ +import { FontWeight, TextVariant } from '@metamask/design-system-shared'; import React, { forwardRef } from 'react'; import { @@ -5,10 +6,8 @@ import { BoxBackgroundColor, ButtonIconSize, ButtonSize, - FontWeight, BoxFlexDirection, IconName, - TextVariant, } from '../../types'; import { twMerge } from '../../utils/tw-merge'; import { Box } from '../Box'; diff --git a/packages/design-system-react/src/components/Box/Box.stories.tsx b/packages/design-system-react/src/components/Box/Box.stories.tsx index 0e13c8d7f..9cc3e7f11 100644 --- a/packages/design-system-react/src/components/Box/Box.stories.tsx +++ b/packages/design-system-react/src/components/Box/Box.stories.tsx @@ -1,3 +1,4 @@ +import { TextColor } from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; @@ -8,7 +9,6 @@ import { BoxJustifyContent, BoxBackgroundColor, BoxBorderColor, - TextColor, } from '../../types'; import { Text } from '../Text'; diff --git a/packages/design-system-react/src/components/ButtonBase/ButtonBase.stories.tsx b/packages/design-system-react/src/components/ButtonBase/ButtonBase.stories.tsx index 01bea1e18..fc487015e 100644 --- a/packages/design-system-react/src/components/ButtonBase/ButtonBase.stories.tsx +++ b/packages/design-system-react/src/components/ButtonBase/ButtonBase.stories.tsx @@ -1,7 +1,8 @@ +import { TextColor, TextVariant } from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; -import { ButtonBaseSize, TextVariant, TextColor } from '../../types'; +import { ButtonBaseSize } from '../../types'; import { Icon, IconName } from '../Icon'; import { Text } from '../Text'; diff --git a/packages/design-system-react/src/components/Icon/Icon.stories.tsx b/packages/design-system-react/src/components/Icon/Icon.stories.tsx index 4d553d8f2..a83735150 100644 --- a/packages/design-system-react/src/components/Icon/Icon.stories.tsx +++ b/packages/design-system-react/src/components/Icon/Icon.stories.tsx @@ -1,7 +1,8 @@ +import { TextVariant } from '@metamask/design-system-shared'; import type { StoryObj } from '@storybook/react-vite'; import React, { useState } from 'react'; -import { IconName, IconSize, IconColor, TextVariant } from '../../types'; +import { IconName, IconSize, IconColor } from '../../types'; import { Text } from '../Text/Text'; import { Icon } from './Icon'; diff --git a/packages/design-system-react/src/components/Input/Input.stories.tsx b/packages/design-system-react/src/components/Input/Input.stories.tsx index 1dc95b508..9eb3cff2d 100644 --- a/packages/design-system-react/src/components/Input/Input.stories.tsx +++ b/packages/design-system-react/src/components/Input/Input.stories.tsx @@ -1,8 +1,7 @@ +import { TextVariant } from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; -import { TextVariant } from '../../types'; - import { Input } from './Input'; import type { InputProps } from './Input.types'; import README from './README.mdx'; diff --git a/packages/design-system-react/src/components/Input/Input.test.tsx b/packages/design-system-react/src/components/Input/Input.test.tsx index b3c5874ec..f1890211e 100644 --- a/packages/design-system-react/src/components/Input/Input.test.tsx +++ b/packages/design-system-react/src/components/Input/Input.test.tsx @@ -1,8 +1,7 @@ +import { TextVariant } from '@metamask/design-system-shared'; import { render, screen, fireEvent } from '@testing-library/react'; import React, { createRef } from 'react'; -import { TextVariant } from '../../types'; - import { Input } from './Input'; const TEST_ID = 'input'; diff --git a/packages/design-system-react/src/components/Input/Input.tsx b/packages/design-system-react/src/components/Input/Input.tsx index b4efcb647..eeeae80a8 100644 --- a/packages/design-system-react/src/components/Input/Input.tsx +++ b/packages/design-system-react/src/components/Input/Input.tsx @@ -1,10 +1,10 @@ +import { TextVariant } from '@metamask/design-system-shared'; import React, { forwardRef } from 'react'; -import { TextVariant } from '../../types'; import { twMerge } from '../../utils/tw-merge'; import { - CLASSMAP_TEXT_VARIANT_FONTSTYLE, - CLASSMAP_TEXT_VARIANT_FONTWEIGHT, + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE, + TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT, } from '../Text/Text.constants'; import type { InputProps } from './Input.types'; @@ -25,8 +25,8 @@ export const Input = forwardRef( 'w-full rounded border bg-default text-default outline-none transition-colors', 'border-transparent focus:border-primary-default focus:outline-none', 'placeholder:text-alternative', - CLASSMAP_TEXT_VARIANT_FONTSTYLE[textVariant], - CLASSMAP_TEXT_VARIANT_FONTWEIGHT[textVariant], + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE[textVariant], + TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[textVariant], isDisabled && 'cursor-not-allowed opacity-50', className, ); diff --git a/packages/design-system-react/src/components/Input/Input.types.ts b/packages/design-system-react/src/components/Input/Input.types.ts index 8ed0e0102..51c19e4fc 100644 --- a/packages/design-system-react/src/components/Input/Input.types.ts +++ b/packages/design-system-react/src/components/Input/Input.types.ts @@ -1,7 +1,6 @@ +import type { TextVariant } from '@metamask/design-system-shared'; import type { ComponentPropsWithoutRef } from 'react'; -import type { TextVariant } from '../../types'; - export type InputProps = Omit< ComponentPropsWithoutRef<'input'>, 'disabled' | 'readOnly' diff --git a/packages/design-system-react/src/components/Text/Text.constants.ts b/packages/design-system-react/src/components/Text/Text.constants.ts index be28fa68d..913a770a7 100644 --- a/packages/design-system-react/src/components/Text/Text.constants.ts +++ b/packages/design-system-react/src/components/Text/Text.constants.ts @@ -1,6 +1,28 @@ -import { TextVariant } from '../../types'; +import { + FontFamily, + FontStyle, + FontWeight, + TextVariant, +} from '@metamask/design-system-shared'; + +export const TWCLASSMAP_TEXT_FONTWEIGHT: Record = { + [FontWeight.Bold]: 'font-bold', + [FontWeight.Medium]: 'font-medium', + [FontWeight.Regular]: 'font-regular', +}; + +export const TWCLASSMAP_TEXT_FONTFAMILY: Record = { + [FontFamily.Default]: 'font-default', + [FontFamily.Accent]: 'font-accent', + [FontFamily.Hero]: 'font-hero', +}; + +export const TWCLASSMAP_TEXT_FONTSTYLE: Record = { + [FontStyle.Italic]: 'italic', + [FontStyle.Normal]: 'not-italic', +}; -export const CLASSMAP_TEXT_VARIANT_FONTSTYLE: Record = { +export const TWCLASSMAP_TEXT_VARIANT_FONTSTYLE: Record = { [TextVariant.DisplayLg]: 'text-s-display-lg leading-s-display-lg tracking-s-display-lg md:text-l-display-lg md:leading-l-display-lg md:tracking-l-display-lg', [TextVariant.DisplayMd]: @@ -31,7 +53,7 @@ export const CLASSMAP_TEXT_VARIANT_FONTSTYLE: Record = { 'text-s-amount-display-lg leading-s-amount-display-lg tracking-s-amount-display-lg md:text-l-amount-display-lg md:leading-l-amount-display-lg md:tracking-l-amount-display-lg', }; -export const CLASSMAP_TEXT_VARIANT_FONTWEIGHT: Record = { +export const TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT: Record = { [TextVariant.DisplayLg]: 'font-bold md:font-medium', [TextVariant.DisplayMd]: 'font-bold md:font-medium', [TextVariant.HeadingLg]: 'font-bold', diff --git a/packages/design-system-react/src/components/Text/Text.stories.tsx b/packages/design-system-react/src/components/Text/Text.stories.tsx index db5f11c7e..fe7a67a2b 100644 --- a/packages/design-system-react/src/components/Text/Text.stories.tsx +++ b/packages/design-system-react/src/components/Text/Text.stories.tsx @@ -1,16 +1,14 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import React from 'react'; - import { FontFamily, FontStyle, FontWeight, - OverflowWrap, - TextAlign, - TextVariant, - TextTransform, TextColor, -} from '../../types'; + TextVariant, +} from '@metamask/design-system-shared'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import React from 'react'; + +import { OverflowWrap, TextAlign, TextTransform } from '../../types'; import README from './README.mdx'; import { Text } from './Text'; diff --git a/packages/design-system-react/src/components/Text/Text.test.tsx b/packages/design-system-react/src/components/Text/Text.test.tsx index ae6e7f536..6b8dc0260 100644 --- a/packages/design-system-react/src/components/Text/Text.test.tsx +++ b/packages/design-system-react/src/components/Text/Text.test.tsx @@ -1,21 +1,22 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - import { FontFamily, FontStyle, FontWeight, - OverflowWrap, - TextAlign, - TextVariant, - TextTransform, TextColor, -} from '../../types'; + TextVariant, +} from '@metamask/design-system-shared'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { OverflowWrap, TextAlign, TextTransform } from '../../types'; import { twMerge } from '../../utils/tw-merge'; import { - CLASSMAP_TEXT_VARIANT_FONTSTYLE, - CLASSMAP_TEXT_VARIANT_FONTWEIGHT, + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE, + TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT, + TWCLASSMAP_TEXT_FONTFAMILY, + TWCLASSMAP_TEXT_FONTSTYLE, + TWCLASSMAP_TEXT_FONTWEIGHT, } from './Text.constants'; import { Text } from '.'; @@ -42,8 +43,8 @@ describe('Text Component', () => { const expectedClassNames = twMerge( TextColor.TextDefault, - CLASSMAP_TEXT_VARIANT_FONTSTYLE[variant], - CLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant], + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE[variant], + TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant], ); expectedClassNames.split(' ').forEach((className) => { @@ -75,7 +76,9 @@ describe('Text Component', () => { Object.values(FontWeight).forEach((weight) => { it(`applies ${weight} font weight correctly`, () => { const { container } = render(Test); - expect(container.firstChild).toHaveClass(weight); + expect(container.firstChild).toHaveClass( + TWCLASSMAP_TEXT_FONTWEIGHT[weight], + ); }); }); }); @@ -84,7 +87,9 @@ describe('Text Component', () => { Object.values(FontFamily).forEach((family) => { it(`applies ${family} font family correctly`, () => { const { container } = render(Test); - expect(container.firstChild).toHaveClass(family); + expect(container.firstChild).toHaveClass( + TWCLASSMAP_TEXT_FONTFAMILY[family], + ); }); }); }); @@ -93,7 +98,9 @@ describe('Text Component', () => { Object.values(FontStyle).forEach((style) => { it(`applies ${style} font style correctly`, () => { const { container } = render(Test); - expect(container.firstChild).toHaveClass(style); + expect(container.firstChild).toHaveClass( + TWCLASSMAP_TEXT_FONTSTYLE[style], + ); }); }); }); @@ -139,8 +146,8 @@ describe('Text Component', () => { expect(container.firstChild).toHaveClass( TextColor.PrimaryDefault, - FontWeight.Bold, - FontStyle.Italic, + TWCLASSMAP_TEXT_FONTWEIGHT[FontWeight.Bold], + TWCLASSMAP_TEXT_FONTSTYLE[FontStyle.Italic], TextTransform.Uppercase, TextAlign.Center, OverflowWrap.BreakWord, diff --git a/packages/design-system-react/src/components/Text/Text.tsx b/packages/design-system-react/src/components/Text/Text.tsx index 0ab21474b..5d1efd7d6 100644 --- a/packages/design-system-react/src/components/Text/Text.tsx +++ b/packages/design-system-react/src/components/Text/Text.tsx @@ -1,12 +1,19 @@ +import { + FontFamily, + TextColor, + TextVariant, +} from '@metamask/design-system-shared'; import { Slot } from '@radix-ui/react-slot'; import React from 'react'; -import { FontFamily, TextColor, TextVariant } from '../../types'; import { twMerge } from '../../utils/tw-merge'; import { - CLASSMAP_TEXT_VARIANT_FONTSTYLE, - CLASSMAP_TEXT_VARIANT_FONTWEIGHT, + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE, + TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT, + TWCLASSMAP_TEXT_FONTFAMILY, + TWCLASSMAP_TEXT_FONTSTYLE, + TWCLASSMAP_TEXT_FONTWEIGHT, MAP_TEXT_VARIANT_TAG, } from './Text.constants'; import type { TextProps } from './Text.types'; @@ -33,10 +40,12 @@ export const Text: React.FC = ({ const mergedClassName = `${twMerge( color, - CLASSMAP_TEXT_VARIANT_FONTSTYLE[variant], - fontWeight || CLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant], - fontStyle, - fontFamily, + TWCLASSMAP_TEXT_VARIANT_FONTSTYLE[variant], + fontWeight + ? TWCLASSMAP_TEXT_FONTWEIGHT[fontWeight] + : TWCLASSMAP_TEXT_VARIANT_FONTWEIGHT[variant], + fontStyle ? TWCLASSMAP_TEXT_FONTSTYLE[fontStyle] : undefined, + TWCLASSMAP_TEXT_FONTFAMILY[fontFamily], textTransform, textAlign, overflowWrap, diff --git a/packages/design-system-react/src/components/Text/Text.types.ts b/packages/design-system-react/src/components/Text/Text.types.ts index 847c2edab..316c7d7e6 100644 --- a/packages/design-system-react/src/components/Text/Text.types.ts +++ b/packages/design-system-react/src/components/Text/Text.types.ts @@ -1,53 +1,16 @@ -import type { - FontFamily, - FontStyle, - FontWeight, - OverflowWrap, - TextAlign, - TextVariant, - TextTransform, - TextColor, -} from '../../types'; +import type { TextPropsShared } from '@metamask/design-system-shared'; -export type TextProps = { +import type { OverflowWrap, TextAlign, TextTransform } from '../../types'; + +export type TextProps = TextPropsShared & { /** * Optional prop for inline styles */ style?: React.CSSProperties; - /** - * Optional prop to change the font size of the component. The Text component uses responsive font sizes. - * Different variants map to specific HTML elements by default. - * - * @default TextVariant.BodyMd - */ - variant?: TextVariant; - /** - * The text content or elements to be rendered within the component. - */ - children: React.ReactNode; /** * Optional prop for additional CSS classes to be applied to the Text component. */ className?: string; - /** - * Optional prop to control the font weight of the text. - * Regular: 400 - * Medium: 500 - * Bold: 600 - */ - fontWeight?: FontWeight; - /** - * Optional prop to adjust the font family. - * Default: Geist - * Accent: MM Sans - * Hero: MM Poly - */ - fontFamily?: FontFamily; - /** - * Optional prop to control the font style of the text. - * Options: Normal, Italic - */ - fontStyle?: FontStyle; /** * Optional prop to apply text transformation to the content. * Options: Uppercase, Lowercase, Capitalize, Normal @@ -64,7 +27,7 @@ export type TextProps = { */ overflowWrap?: OverflowWrap; /** - * Optional prop that when true, adds an ellipsis (...) when text overflows its container. + * Optional boolean that adds an ellipsis (...) when text overflows its container. * * @default false */ @@ -76,12 +39,6 @@ export type TextProps = { * @default false */ asChild?: boolean; - /** - * Optional prop that sets the color of the text using predefined theme colors. - * - * @default TextColor.TextDefault - */ - color?: TextColor; /** * Optional prop for testing purposes */ diff --git a/packages/design-system-react/src/components/Text/index.ts b/packages/design-system-react/src/components/Text/index.ts index 92ec2edfc..2ea4964b6 100644 --- a/packages/design-system-react/src/components/Text/index.ts +++ b/packages/design-system-react/src/components/Text/index.ts @@ -1,12 +1,11 @@ export { + TextColor, + TextVariant, FontFamily, FontStyle, FontWeight, - OverflowWrap, - TextAlign, - TextVariant, - TextTransform, - TextColor, -} from '../../types'; + type TextPropsShared, +} from '@metamask/design-system-shared'; +export { OverflowWrap, TextAlign, TextTransform } from '../../types'; export { Text } from './Text'; export type { TextProps } from './Text.types'; diff --git a/packages/design-system-react/src/components/temp-components/Blockies/Blockies.stories.tsx b/packages/design-system-react/src/components/temp-components/Blockies/Blockies.stories.tsx index 88b20c04b..9a5284c00 100644 --- a/packages/design-system-react/src/components/temp-components/Blockies/Blockies.stories.tsx +++ b/packages/design-system-react/src/components/temp-components/Blockies/Blockies.stories.tsx @@ -1,7 +1,11 @@ +import { + FontWeight, + TextColor, + TextVariant, +} from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; -import { TextVariant, TextColor, FontWeight } from '../../../types'; import { Box } from '../../Box'; import { Text } from '../../Text'; diff --git a/packages/design-system-react/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx b/packages/design-system-react/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx index 7baa4c772..b4367b2ad 100644 --- a/packages/design-system-react/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx +++ b/packages/design-system-react/src/components/temp-components/Jazzicon/Jazzicon.stories.tsx @@ -1,7 +1,11 @@ +import { + FontWeight, + TextColor, + TextVariant, +} from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; -import { TextVariant, TextColor, FontWeight } from '../../../types'; import { Box } from '../../Box'; import { Text } from '../../Text'; diff --git a/packages/design-system-react/src/components/temp-components/Maskicon/Maskicon.stories.tsx b/packages/design-system-react/src/components/temp-components/Maskicon/Maskicon.stories.tsx index 215e64231..7f1e3cdce 100644 --- a/packages/design-system-react/src/components/temp-components/Maskicon/Maskicon.stories.tsx +++ b/packages/design-system-react/src/components/temp-components/Maskicon/Maskicon.stories.tsx @@ -1,7 +1,11 @@ +import { + FontWeight, + TextColor, + TextVariant, +} from '@metamask/design-system-shared'; import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; -import { TextVariant, TextColor, FontWeight } from '../../../types'; import { Box } from '../../Box'; import { Text } from '../../Text'; diff --git a/packages/design-system-react/src/types/index.ts b/packages/design-system-react/src/types/index.ts index 282da3545..a412bd309 100644 --- a/packages/design-system-react/src/types/index.ts +++ b/packages/design-system-react/src/types/index.ts @@ -280,155 +280,37 @@ export enum ButtonIconVariant { Floating = 'floating', } -/** - * Text - variant - */ -export enum TextVariant { - // Display Sizes - DisplayLg = 'display-lg', - DisplayMd = 'display-md', - - // Heading Sizes - HeadingLg = 'heading-lg', - HeadingMd = 'heading-md', - HeadingSm = 'heading-sm', - - // Font Sizes - BodyLg = 'body-lg', - BodyMd = 'body-md', - BodySm = 'body-sm', - BodyXs = 'body-xs', - - // Special Typography Variants - PageHeading = 'page-heading', - SectionHeading = 'section-heading', - ButtonLabelMd = 'button-label-md', - ButtonLabelLg = 'button-label-lg', - AmountDisplayLg = 'amount-display-lg', -} - -/** - * Text - color - */ -export enum TextColor { - /** For default neutral text. */ - TextDefault = 'text-default', - /** For softer contrast neutral text */ - TextAlternative = 'text-alternative', - /** For the softest contrast neutral text (not accessible) */ - TextMuted = 'text-muted', - /** For elements used on top of overlay/alternative. Used for text, icon or border */ - OverlayInverse = 'text-overlay-inverse', - /** For interactive, active, and selected semantics. Used for text, background, icon or border */ - PrimaryDefault = 'text-primary-default', - /** For primary text in a hover state. */ - PrimaryDefaultHover = 'text-primary-default-hover', - /** For primary text in a pressed state. */ - PrimaryDefaultPressed = 'text-primary-default-pressed', - /** For elements used on top of primary/default. Used for text, icon or border */ - PrimaryInverse = 'text-primary-inverse', - /** For the critical alert semantic elements. Used for text, background, icon or border */ - ErrorDefault = 'text-error-default', - /** For critical alert text in a hover state. */ - ErrorDefaultHover = 'text-error-default-hover', - /** For critical alert text in a pressed state. */ - ErrorDefaultPressed = 'text-error-default-pressed', - /** For the stronger contrast error semantic elements. */ - ErrorAlternative = 'text-error-alternative', - /** For elements used on top of error/default. Used for text, icon or border */ - ErrorInverse = 'text-error-inverse', - /** For the positive semantic elements. Used for text, background, icon or border */ - SuccessDefault = 'text-success-default', - /** For positive text in a hover state. */ - SuccessDefaultHover = 'text-success-default-hover', - /** For positive text in a pressed state. */ - SuccessDefaultPressed = 'text-success-default-pressed', - /** For elements used on top of success/default. Used for text, icon or border */ - SuccessInverse = 'text-success-inverse', - /** For the caution alert semantic elements. Used for text, background, icon or border */ - WarningDefault = 'text-warning-default', - /** For caution text in a hover state. */ - WarningDefaultHover = 'text-warning-default-hover', - /** For caution text in a pressed state. */ - WarningDefaultPressed = 'text-warning-default-pressed', - /** For elements used on top of warning/default. Used for text, icon or border */ - WarningInverse = 'text-warning-inverse', - /** For informational read-only elements. Used for text, background, icon or border */ - InfoDefault = 'text-info-default', - /** For elements used on top of info/default. Used for text, icon or border */ - InfoInverse = 'text-info-inverse', - /** Inherit the color of the parent element */ - Inherit = 'text-inherit', - /** Make the text color transparent */ - Transparent = 'text-transparent', -} - /** * Text - textAlign */ -export enum TextAlign { - Left = 'text-left', - Center = 'text-center', - Right = 'text-right', - Justify = 'text-justify', -} - -/** - * Text - fontWeight - */ -export enum FontWeight { - /** - * Weight - 600 - */ - Bold = 'font-bold', - /** - * Weight - 500 - */ - Medium = 'font-medium', - /** - * Weight - 400 - */ - Regular = 'font-regular', -} +export const TextAlign = { + Left: 'text-left', + Center: 'text-center', + Right: 'text-right', + Justify: 'text-justify', +} as const; +export type TextAlign = (typeof TextAlign)[keyof typeof TextAlign]; /** * Text - overflowWrap */ -export enum OverflowWrap { - BreakWord = 'break-words', - Anywhere = 'break-all', - Normal = 'break-normal', -} - -/** - * Text - fontStyle - */ -export enum FontStyle { - Italic = 'italic', - Normal = 'not-italic', -} +export const OverflowWrap = { + BreakWord: 'break-words', + Anywhere: 'break-all', + Normal: 'break-normal', +} as const; +export type OverflowWrap = (typeof OverflowWrap)[keyof typeof OverflowWrap]; /** * Text - textTransform */ -export enum TextTransform { - // eslint-disable-next-line @typescript-eslint/no-shadow - Uppercase = 'uppercase', - // eslint-disable-next-line @typescript-eslint/no-shadow - Lowercase = 'lowercase', - // eslint-disable-next-line @typescript-eslint/no-shadow - Capitalize = 'capitalize', - Normal = 'normal-case', -} - -/** - * Text - fontFamily - */ -export enum FontFamily { - Default = 'font-default', - Accent = 'font-accent', - Hero = 'font-hero', -} +export const TextTransform = { + Uppercase: 'uppercase', + Lowercase: 'lowercase', + Capitalize: 'capitalize', + Normal: 'normal-case', +} as const; +export type TextTransform = (typeof TextTransform)[keyof typeof TextTransform]; /** * TextButton - size diff --git a/packages/design-system-shared/src/index.ts b/packages/design-system-shared/src/index.ts index cd434d4ab..64d8c65d0 100644 --- a/packages/design-system-shared/src/index.ts +++ b/packages/design-system-shared/src/index.ts @@ -88,3 +88,13 @@ export { AvatarAccountVariant, type AvatarAccountPropsShared, } from './types/AvatarAccount'; + +// Text types (ADR-0003 + ADR-0004) +export { + FontFamily, + FontStyle, + FontWeight, + TextColor, + TextVariant, + type TextPropsShared, +} from './types/Text'; diff --git a/packages/design-system-shared/src/types/Text/Text.types.ts b/packages/design-system-shared/src/types/Text/Text.types.ts new file mode 100644 index 000000000..810b60359 --- /dev/null +++ b/packages/design-system-shared/src/types/Text/Text.types.ts @@ -0,0 +1,196 @@ +import type { ReactNode } from 'react'; + +/** + * Text - fontWeight (ADR-0003) + * Semantic values shared across React and React Native platforms. + */ +export const FontWeight = { + /** Weight - 600 */ + Bold: 'bold', + /** Weight - 500 */ + Medium: 'medium', + /** Weight - 400 */ + Regular: 'regular', +} as const; + +export type FontWeight = (typeof FontWeight)[keyof typeof FontWeight]; + +/** + * Text - fontStyle (ADR-0003) + * Semantic values shared across React and React Native platforms. + */ +export const FontStyle = { + Italic: 'italic', + Normal: 'normal', +} as const; + +export type FontStyle = (typeof FontStyle)[keyof typeof FontStyle]; + +/** + * Text - fontFamily (ADR-0003) + * Semantic values shared across React and React Native platforms. + */ +export const FontFamily = { + /** Default: Geist */ + Default: 'default', + /** Accent: MM Sans */ + Accent: 'accent', + /** Hero: MM Poly */ + Hero: 'hero', +} as const; + +export type FontFamily = (typeof FontFamily)[keyof typeof FontFamily]; + +/** + * Text - variant (ADR-0003) + * Identical values across React and React Native platforms. + */ +export const TextVariant = { + // Display Sizes + DisplayLg: 'display-lg', + DisplayMd: 'display-md', + + // Heading Sizes + HeadingLg: 'heading-lg', + HeadingMd: 'heading-md', + HeadingSm: 'heading-sm', + + // Body Sizes + BodyLg: 'body-lg', + BodyMd: 'body-md', + BodySm: 'body-sm', + BodyXs: 'body-xs', + + // Special Typography Variants + PageHeading: 'page-heading', + SectionHeading: 'section-heading', + ButtonLabelMd: 'button-label-md', + ButtonLabelLg: 'button-label-lg', + AmountDisplayLg: 'amount-display-lg', +} as const; + +export type TextVariant = (typeof TextVariant)[keyof typeof TextVariant]; + +/** + * Text - color (ADR-0003) + * All text color values shared across React and React Native platforms. + */ +export const TextColor = { + /** For default neutral text. */ + TextDefault: 'text-default', + /** For softer contrast neutral text */ + TextAlternative: 'text-alternative', + /** For the softest contrast neutral text (not accessible) */ + TextMuted: 'text-muted', + /** For elements used on top of overlay/alternative. Used for text, icon or border */ + OverlayInverse: 'text-overlay-inverse', + /** For interactive, active, and selected semantics. Used for text, background, icon or border */ + PrimaryDefault: 'text-primary-default', + /** For elements used on top of primary/default. Used for text, icon or border */ + PrimaryInverse: 'text-primary-inverse', + /** For softer variants of primary text. */ + PrimaryAlternative: 'text-primary-alternative', + /** + * For primary text in a hover state. + * + * @note On React Native, hover as an interaction state does not exist — use `PrimaryDefaultPressed` instead. + */ + PrimaryDefaultHover: 'text-primary-default-hover', + /** For primary text in a pressed state. */ + PrimaryDefaultPressed: 'text-primary-default-pressed', + /** For the critical alert semantic elements. Used for text, background, icon or border */ + ErrorDefault: 'text-error-default', + /** For the stronger contrast error semantic elements. */ + ErrorAlternative: 'text-error-alternative', + /** For elements used on top of error/default. Used for text, icon or border */ + ErrorInverse: 'text-error-inverse', + /** + * For critical alert text in a hover state. + * + * @note On React Native, hover as an interaction state does not exist — use `ErrorDefaultPressed` instead. + */ + ErrorDefaultHover: 'text-error-default-hover', + /** For critical alert text in a pressed state. */ + ErrorDefaultPressed: 'text-error-default-pressed', + /** For the positive semantic elements. Used for text, background, icon or border */ + SuccessDefault: 'text-success-default', + /** For elements used on top of success/default. Used for text, icon or border */ + SuccessInverse: 'text-success-inverse', + /** + * For positive text in a hover state. + * + * @note On React Native, hover as an interaction state does not exist — use `SuccessDefaultPressed` instead. + */ + SuccessDefaultHover: 'text-success-default-hover', + /** For positive text in a pressed state. */ + SuccessDefaultPressed: 'text-success-default-pressed', + /** For the caution alert semantic elements. Used for text, background, icon or border */ + WarningDefault: 'text-warning-default', + /** For elements used on top of warning/default. Used for text, icon or border */ + WarningInverse: 'text-warning-inverse', + /** + * For caution text in a hover state. + * + * @note On React Native, hover as an interaction state does not exist — use `WarningDefaultPressed` instead. + */ + WarningDefaultHover: 'text-warning-default-hover', + /** For caution text in a pressed state. */ + WarningDefaultPressed: 'text-warning-default-pressed', + /** For informational read-only elements. Used for text, background, icon or border */ + InfoDefault: 'text-info-default', + /** For elements used on top of info/default. Used for text, icon or border */ + InfoInverse: 'text-info-inverse', + /** Make the text color transparent */ + Transparent: 'text-transparent', + /** + * Inherit the color of the parent element. + * + * @platform web — CSS cascade has no equivalent in React Native; fails silently on mobile. + */ + Inherit: 'text-inherit', +} as const; + +export type TextColor = (typeof TextColor)[keyof typeof TextColor]; + +/** + * Text component shared props (ADR-0004). + * Platform-independent props shared across React and React Native. + */ +export type TextPropsShared = { + /** + * Optional prop to change the font size of the component. + * Different variants map to specific HTML elements by default. + * + * @default TextVariant.BodyMd + */ + variant?: TextVariant; + /** + * The text content or elements to be rendered within the component. + */ + children: ReactNode; + /** + * Optional prop that sets the color of the text using predefined theme colors. + * + * @default TextColor.TextDefault + */ + color?: TextColor; + /** + * Optional prop to control the font weight of the text. + * Regular: 400 + * Medium: 500 + * Bold: 600 + */ + fontWeight?: FontWeight; + /** + * Optional prop to adjust the font family. + * Default: Geist + * Accent: MM Sans + * Hero: MM Poly + */ + fontFamily?: FontFamily; + /** + * Optional prop to control the font style of the text. + * Options: Normal, Italic + */ + fontStyle?: FontStyle; +}; diff --git a/packages/design-system-shared/src/types/Text/index.ts b/packages/design-system-shared/src/types/Text/index.ts new file mode 100644 index 000000000..c0731da3b --- /dev/null +++ b/packages/design-system-shared/src/types/Text/index.ts @@ -0,0 +1,8 @@ +export { + FontFamily, + FontStyle, + FontWeight, + TextColor, + TextVariant, + type TextPropsShared, +} from './Text.types';