From dc6447f5ec57a9cd9f1becbf48b1ed1d3ab554f9 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 17 Jan 2026 04:41:47 +0900 Subject: [PATCH 1/4] UI: Add Textarea primitive --- packages/ui/src/form/primitives/index.ts | 1 + .../ui/src/form/primitives/textarea/index.ts | 1 + .../textarea/stories/index.story.tsx | 40 ++++++ .../form/primitives/textarea/style.module.css | 22 +++ .../primitives/textarea/test/index.test.tsx | 131 ++++++++++++++++++ .../src/form/primitives/textarea/textarea.tsx | 55 ++++++++ .../ui/src/form/primitives/textarea/types.ts | 18 +++ 7 files changed, 268 insertions(+) create mode 100644 packages/ui/src/form/primitives/textarea/index.ts create mode 100644 packages/ui/src/form/primitives/textarea/stories/index.story.tsx create mode 100644 packages/ui/src/form/primitives/textarea/style.module.css create mode 100644 packages/ui/src/form/primitives/textarea/test/index.test.tsx create mode 100644 packages/ui/src/form/primitives/textarea/textarea.tsx create mode 100644 packages/ui/src/form/primitives/textarea/types.ts diff --git a/packages/ui/src/form/primitives/index.ts b/packages/ui/src/form/primitives/index.ts index c5531ce572579b..4e6973efd0b084 100644 --- a/packages/ui/src/form/primitives/index.ts +++ b/packages/ui/src/form/primitives/index.ts @@ -3,3 +3,4 @@ export * as Fieldset from './fieldset'; export { Input } from './input'; export { InputLayout } from './input-layout'; export * as Select from './select'; +export { Textarea } from './textarea'; diff --git a/packages/ui/src/form/primitives/textarea/index.ts b/packages/ui/src/form/primitives/textarea/index.ts new file mode 100644 index 00000000000000..0c82925ff6c0fe --- /dev/null +++ b/packages/ui/src/form/primitives/textarea/index.ts @@ -0,0 +1 @@ +export { Textarea } from './textarea'; diff --git a/packages/ui/src/form/primitives/textarea/stories/index.story.tsx b/packages/ui/src/form/primitives/textarea/stories/index.story.tsx new file mode 100644 index 00000000000000..24479466b4e83c --- /dev/null +++ b/packages/ui/src/form/primitives/textarea/stories/index.story.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Textarea } from '../index'; + +const meta: Meta< typeof Textarea > = { + title: 'Design System/Components/Form/Primitives/Textarea', + component: Textarea, +}; +export default meta; + +type Story = StoryObj< typeof Textarea >; + +export const Default: Story = { + args: { + placeholder: 'Placeholder', + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + disabled: true, + }, +}; + +export const WithOverflow: Story = { + args: { + ...Default.args, + defaultValue: `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`, + }, +}; + +/** + * When `rows` is set to `1`, the textarea will have the same footprint as a default `Input`. + */ +export const WithOneRow: Story = { + args: { + ...Default.args, + rows: 1, + }, +}; diff --git a/packages/ui/src/form/primitives/textarea/style.module.css b/packages/ui/src/form/primitives/textarea/style.module.css new file mode 100644 index 00000000000000..7447d1f290b32d --- /dev/null +++ b/packages/ui/src/form/primitives/textarea/style.module.css @@ -0,0 +1,22 @@ +@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides; + +@layer wp-ui-components { + .wrapper { + --wp-ui-textarea-min-height: 40px; + } + + .textarea { + /* Prevents users from resizing the textarea below this height. */ + min-height: calc(var(--wp-ui-textarea-min-height) - 2px); + resize: block; + } +} + +@layer wp-ui-compositions { + .wrapper { + --wp-ui-input-padding-block: 9.9px; + + height: auto; + line-height: 1.4; /* TODO: Use variable */ + } +} diff --git a/packages/ui/src/form/primitives/textarea/test/index.test.tsx b/packages/ui/src/form/primitives/textarea/test/index.test.tsx new file mode 100644 index 00000000000000..f2f2a0d96366eb --- /dev/null +++ b/packages/ui/src/form/primitives/textarea/test/index.test.tsx @@ -0,0 +1,131 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { createRef, useState } from '@wordpress/element'; +import { Textarea } from '../index'; + +describe( 'Textarea', () => { + it( 'forwards ref', () => { + const ref = createRef< HTMLTextAreaElement >(); + + render(