Skip to content

feat: [DSR+DSRN] Migrate Textarea component from extension#1036

Draft
georgewrmarshall wants to merge 1 commit intomainfrom
textarea
Draft

feat: [DSR+DSRN] Migrate Textarea component from extension#1036
georgewrmarshall wants to merge 1 commit intomainfrom
textarea

Conversation

@georgewrmarshall
Copy link
Copy Markdown
Contributor

@georgewrmarshall georgewrmarshall commented Apr 2, 2026

Description

Migrates the Textarea component from the MetaMask Extension component-library into the design system monorepo for both React (web) and React Native (mobile) platforms.

The Extension has a Textarea component; mobile has no dedicated multiline text input, so the React Native implementation is new and follows the existing Input component pattern.

Follows ADR-0003 (const objects instead of enums) and ADR-0004 (centralized types in @metamask/design-system-shared).

What's included

@metamask/design-system-shared

  • TextareaPropsShared — shared cross-platform props: isDisabled, isReadOnly, isError

@metamask/design-system-react

  • Textarea component — forwardRef wrapper around <textarea> with Tailwind styling
  • TextareaResize const object — None, Both, Horizontal, Vertical (maps to Tailwind resize-* classes)
  • Props: textVariant, resize, rows, cols, maxLength, isDisabled, isReadOnly, isError, className, style
  • Sets aria-invalid when isError is true
  • Storybook stories: Default, Variant, Resize, IsDisabled, IsReadOnly, IsError, Rows
  • README.mdx with Canvas blocks

@metamask/design-system-react-native

  • Textarea component — forwardRef wrapper around TextInput with multiline={true} and textAlignVertical="top"
  • Props: textVariant, numberOfLines (default: 4), isDisabled, isReadOnly, isError, isStateStylesDisabled, twClassName, style
  • Focus border and error border state styling
  • Storybook stories: Default, Variant, IsDisabled, IsReadOnly, IsError, NumberOfLines, IsStateStylesDisabled
  • README.md

Both platforms include Figma Code Connect files pointing to node 12091:104.

Related issues

Fixes:

Manual testing steps

  1. Run yarn storybook and navigate to React Components/Textarea — verify Default, Variant, Resize, IsDisabled, IsReadOnly, IsError, and Rows stories render correctly
  2. Run yarn storybook:ios and navigate to Components/Textarea — verify all stories render correctly on iOS simulator
  3. Test resize behavior in web Storybook — confirm each TextareaResize value applies the correct CSS class
  4. Verify error state shows border-error-default styling and aria-invalid="true" on web
  5. Verify disabled state shows opacity-50 and cursor-not-allowed on web, and opacity-50 + non-editable on native

Screenshots/Recordings

Before

No Textarea component in the design system monorepo.

After

Pre-merge author checklist

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I've included tests if applicable
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR (see labeling guidelines). Not required for external contributors.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Introduces new public Textarea exports in both design-system-react and design-system-react-native, plus shared prop types, which may impact consumers via new API surface and styling/behavior expectations. Risk is mitigated by comprehensive Storybook docs and unit tests, but integration/visual regressions are still possible.

Overview
Adds a new cross-platform Textarea component to the design system for both web (design-system-react) and React Native (design-system-react-native), and exports it from each package’s components index.

Web Textarea wraps <textarea> with Tailwind classes, supports typography via textVariant, error/disabled/readonly states (including aria-invalid), and a new TextareaResize const mapping to CSS resize-* behavior. React Native Textarea wraps TextInput with multiline, controlled-only value, theming-based placeholder color, numberOfLines defaulting to 4, and optional state styling (focus/disabled/error) that can be disabled via isStateStylesDisabled.

Adds TextareaPropsShared to design-system-shared, plus Storybook stories, Figma Code Connect mappings, READMEs, and unit tests for both platforms.

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

…repo

Adds Textarea component to both React and React Native packages following ADR-0003/0004 patterns.

- Shared types in @metamask/design-system-shared with TextareaPropsShared (isDisabled, isReadOnly, isError)
- React: forwardRef textarea with TextareaResize (None/Both/Horizontal/Vertical), textVariant, error/disabled/readonly states, aria-invalid
- React Native: multiline TextInput wrapper with numberOfLines, focus/error styling, isStateStylesDisabled
- Storybook stories, README, tests (23 tests, 100% coverage), and Figma Code Connect for both platforms
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

📖 Storybook Preview

Copy link
Copy Markdown
Contributor

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

Fix All in Cursor

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

  • ✅ Fixed: Web Textarea uses visible border instead of transparent
    • Replaced the non-error border class with border-transparent to match borderless design and Input behavior.
  • ✅ Fixed: Textarea metrics constant duplicates Input's identical constant
    • Removed duplicated RN Textarea metrics object and re-exported the Input metrics constant instead.

Create PR

Or push these changes by commenting:

@cursor push 33e5ca596b
Preview (33e5ca596b)
diff --git a/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts b/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
--- a/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
+++ b/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
@@ -1,73 +1,11 @@
-import { typography } from '@metamask/design-tokens';
+import { MAP_TEXT_VARIANT_INPUT_METRICS } from '../Input/Input.constants';
 
-import { TextVariant } from '../../types';
-
 /**
  * Typographic metrics for Textarea: same tokens as `text-*` utilities but **without** `lineHeight`.
  * React Native `TextInput` with multiline aligns text more predictably when line height is not set
  * from the design-system paragraph specs.
  */
-export const MAP_TEXT_VARIANT_TEXTAREA_METRICS: Record<
-  TextVariant,
-  { fontSize: number; letterSpacing: number }
-> = {
-  [TextVariant.DisplayLg]: {
-    fontSize: typography.sDisplayLG.fontSize,
-    letterSpacing: typography.sDisplayLG.letterSpacing,
-  },
-  [TextVariant.DisplayMd]: {
-    fontSize: typography.sDisplayMD.fontSize,
-    letterSpacing: typography.sDisplayMD.letterSpacing,
-  },
-  [TextVariant.HeadingLg]: {
-    fontSize: typography.sHeadingLG.fontSize,
-    letterSpacing: typography.sHeadingLG.letterSpacing,
-  },
-  [TextVariant.HeadingMd]: {
-    fontSize: typography.sHeadingMD.fontSize,
-    letterSpacing: typography.sHeadingMD.letterSpacing,
-  },
-  [TextVariant.HeadingSm]: {
-    fontSize: typography.sHeadingSM.fontSize,
-    letterSpacing: typography.sHeadingSM.letterSpacing,
-  },
-  [TextVariant.BodyLg]: {
-    fontSize: typography.sBodyLGMedium.fontSize,
-    letterSpacing: typography.sBodyLGMedium.letterSpacing,
-  },
-  [TextVariant.BodyMd]: {
-    fontSize: typography.sBodyMD.fontSize,
-    letterSpacing: typography.sBodyMD.letterSpacing,
-  },
-  [TextVariant.BodySm]: {
-    fontSize: typography.sBodySM.fontSize,
-    letterSpacing: typography.sBodySM.letterSpacing,
-  },
-  [TextVariant.BodyXs]: {
-    fontSize: typography.sBodyXS.fontSize,
-    letterSpacing: typography.sBodyXS.letterSpacing,
-  },
-  [TextVariant.PageHeading]: {
-    fontSize: typography.sPageHeading.fontSize,
-    letterSpacing: typography.sPageHeading.letterSpacing,
-  },
-  [TextVariant.SectionHeading]: {
-    fontSize: typography.sSectionHeading.fontSize,
-    letterSpacing: typography.sSectionHeading.letterSpacing,
-  },
-  [TextVariant.ButtonLabelMd]: {
-    fontSize: typography.sButtonLabelMd.fontSize,
-    letterSpacing: typography.sButtonLabelMd.letterSpacing,
-  },
-  [TextVariant.ButtonLabelLg]: {
-    fontSize: typography.sButtonLabelLg.fontSize,
-    letterSpacing: typography.sButtonLabelLg.letterSpacing,
-  },
-  [TextVariant.AmountDisplayLg]: {
-    fontSize: typography.sAmountDisplayLg.fontSize,
-    letterSpacing: typography.sAmountDisplayLg.letterSpacing,
-  },
-};
+export const MAP_TEXT_VARIANT_TEXTAREA_METRICS = MAP_TEXT_VARIANT_INPUT_METRICS;
 
 /**
  * Default number of lines displayed in the Textarea.

diff --git a/packages/design-system-react/src/components/Textarea/Textarea.tsx b/packages/design-system-react/src/components/Textarea/Textarea.tsx
--- a/packages/design-system-react/src/components/Textarea/Textarea.tsx
+++ b/packages/design-system-react/src/components/Textarea/Textarea.tsx
@@ -30,7 +30,7 @@
       'placeholder:text-alternative',
       isError
         ? 'border-error-default focus:border-error-default'
-        : 'border-default focus:border-primary-default',
+        : 'border-transparent focus:border-primary-default',
       CLASSMAP_TEXTAREA_RESIZE[resize],
       CLASSMAP_TEXT_VARIANT_FONTSTYLE[textVariant],
       CLASSMAP_TEXT_VARIANT_FONTWEIGHT[textVariant],

You can send follow-ups to this agent here.

'placeholder:text-alternative',
isError
? 'border-error-default focus:border-error-default'
: 'border-default focus:border-primary-default',
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.

Web Textarea uses visible border instead of transparent

Medium Severity

The non-error border class is border-default (a visible design-token color), but the README describes this as a "borderless" component. The web Input component and the React Native Textarea both use border-transparent for the non-error default state. Using border-default here causes the web Textarea to render with a visible border, inconsistent with the stated design intent and the other components it mirrors.

Fix in Cursor Fix in Web

fontSize: typography.sAmountDisplayLg.fontSize,
letterSpacing: typography.sAmountDisplayLg.letterSpacing,
},
};
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.

Textarea metrics constant duplicates Input's identical constant

Low Severity

MAP_TEXT_VARIANT_TEXTAREA_METRICS is an exact copy of MAP_TEXT_VARIANT_INPUT_METRICS from Input.constants.ts — same type signature, same values for every TextVariant key. This duplication means any typography-metric fix or new variant addition needs to be applied in both files, risking drift.

Fix in Cursor Fix in Web

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.

1 participant