Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Textarea

Textarea is a light-weight, controlled-only borderless multi-line text input component.

```tsx
import { Textarea, TextVariant } from '@metamask/design-system-react-native';

<Textarea value="" placeholder="Enter text" textVariant={TextVariant.BodyMd} />;
```

## Props

This component extends React Native's [TextInput](https://reactnative.dev/docs/textinput) component with `multiline` enabled.

### `value`

Required controlled value for the Textarea.

| TYPE | REQUIRED | DEFAULT |
| -------- | -------- | ------- |
| `string` | Yes | N/A |

### `textVariant`

Optional enum to select between Typography variants.

| TYPE | REQUIRED | DEFAULT |
| ------------- | -------- | -------------------- |
| `TextVariant` | No | `TextVariant.BodyMd` |

```tsx
import { Textarea, TextVariant } from '@metamask/design-system-react-native';

<Textarea value="" placeholder="BodyMd (default)" textVariant={TextVariant.BodyMd} />
<Textarea value="" placeholder="BodySm" textVariant={TextVariant.BodySm} />
```

### `isDisabled`

Optional boolean to disable the Textarea.

| TYPE | REQUIRED | DEFAULT |
| --------- | -------- | ------- |
| `boolean` | No | `false` |

```tsx
<Textarea value="Not editable" isDisabled />
```

### `isReadOnly`

Optional boolean to make the Textarea read-only.

| TYPE | REQUIRED | DEFAULT |
| --------- | -------- | ------- |
| `boolean` | No | `false` |

```tsx
<Textarea value="Read-only value" isReadOnly />
```

### `isError`

Optional boolean to indicate an error state. Applies error border styling.

| TYPE | REQUIRED | DEFAULT |
| --------- | -------- | ------- |
| `boolean` | No | `false` |

```tsx
<Textarea value="" placeholder="Invalid input" isError />
```

### `numberOfLines`

Optional number of visible lines. Controls the minimum height of the textarea.

| TYPE | REQUIRED | DEFAULT |
| -------- | -------- | ------- |
| `number` | No | `4` |

```tsx
<Textarea value="" placeholder="4 lines (default)" numberOfLines={4} />
<Textarea value="" placeholder="8 lines" numberOfLines={8} />
```

### `isStateStylesDisabled`

Optional boolean to disable state styles (focus border, disabled opacity).

| TYPE | REQUIRED | DEFAULT |
| --------- | -------- | ------- |
| `boolean` | No | `false` |

```tsx
import { Textarea } from '@metamask/design-system-react-native';

<Textarea value="Disabled, full opacity" isDisabled isStateStylesDisabled />;
```

### `twClassName`

Use the `twClassName` prop to add Tailwind CSS classes to the component. These classes will be merged with the component's default classes using `twMerge`, allowing you to:

- Add new styles that don't exist in the default component
- Override the component's default styles when needed

| TYPE | REQUIRED | DEFAULT |
| -------- | -------- | ----------- |
| `string` | No | `undefined` |

```tsx
import { Textarea } from '@metamask/design-system-react-native';

// Add additional styles
<Textarea value="" twClassName="mt-4" placeholder="With margin" />

// Override default styles
<Textarea value="" twClassName="bg-error-muted" placeholder="Override background" />
```

### `style`

Use the `style` prop to customize the component's appearance with React Native styles. For consistent styling, prefer using `twClassName` with Tailwind classes when possible. Use `style` with `tw.style()` for conditionals or dynamic values.

| TYPE | REQUIRED | DEFAULT |
| ---------------------- | -------- | ----------- |
| `StyleProp<TextStyle>` | No | `undefined` |

```tsx
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { Textarea } from '@metamask/design-system-react-native';

export const ConditionalExample = ({ isActive }: { isActive: boolean }) => {
const tw = useTailwind();

return (
<Textarea
value=""
placeholder="Conditional styling"
style={tw.style('text-default', isActive && 'border-primary-default')}
/>
);
};
```

## References

[MetaMask Design System Guides](https://www.notion.so/MetaMask-Design-System-Guides-Design-f86ecc914d6b4eb6873a122b83c12940)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { typography } from '@metamask/design-tokens';

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,
},
};
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


/**
* Default number of lines displayed in the Textarea.
*/
export const TEXTAREA_DEFAULT_NUMBER_OF_LINES = 4;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 React from 'react';

import { Textarea } from './Textarea';

/**
* -- This file was auto-generated by Code Connect --
* React Native implementation of Textarea component
* `props` includes a mapping from Figma properties and variants to
* suggested values. You should update this to match the props of your
* code component, and update the `example` function to return the
* code example you'd like to see in Figma
*/

figma.connect(
Textarea,
'https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/%F0%9F%A6%8A-WIP--MMDS-Components?node-id=12091%3A104',
{
props: {
isDisabled: figma.boolean('isDisabled'),
isError: figma.boolean('isError'),
isReadOnly: figma.boolean('isReadOnly'),
placeholder: figma.string('placeholder'),
},
example: ({ isDisabled, isError, isReadOnly, placeholder }) => (
<Textarea
value=""
isDisabled={isDisabled}
isError={isError}
isReadOnly={isReadOnly}
placeholder={placeholder ?? 'Enter text'}
/>
),
},
);
Loading
Loading