Skip to content

Commit 33a0102

Browse files
committed
feat: add textarea component
1 parent 6e0e29a commit 33a0102

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { expect, userEvent, within } from "@storybook/test";
3+
import { useState } from "react";
4+
import { Textarea } from "./Textarea";
5+
6+
const meta: Meta<typeof Textarea> = {
7+
title: "components/Textarea",
8+
component: Textarea,
9+
args: {},
10+
argTypes: {
11+
value: {
12+
control: "text",
13+
description: "The controlled value of the textarea",
14+
},
15+
defaultValue: {
16+
control: "text",
17+
description: "The default value when initially rendered",
18+
},
19+
disabled: {
20+
control: "boolean",
21+
description:
22+
"When true, prevents the user from interacting with the textarea",
23+
},
24+
placeholder: {
25+
control: "text",
26+
description: "Placeholder text displayed when the textarea is empty",
27+
},
28+
rows: {
29+
control: "number",
30+
description: "The number of rows to display",
31+
},
32+
},
33+
};
34+
35+
export default meta;
36+
type Story = StoryObj<typeof Textarea>;
37+
38+
export const WithPlaceholder: Story = {
39+
args: {
40+
placeholder: "Enter your message here...",
41+
},
42+
};
43+
44+
export const Disabled: Story = {
45+
args: {
46+
disabled: true,
47+
placeholder: "Placeholder",
48+
},
49+
};
50+
51+
export const WithDefaultValue: Story = {
52+
args: {
53+
defaultValue: "This is some default text in the textarea.",
54+
},
55+
};
56+
57+
export const Large: Story = {
58+
args: {
59+
rows: 8,
60+
placeholder: "Placeholder: A larger textarea with more rows",
61+
},
62+
};
63+
64+
const ControlledTextarea = () => {
65+
const [value, setValue] = useState("This is a controlled textarea.");
66+
return (
67+
<div className="space-y-2">
68+
<Textarea
69+
value={value}
70+
placeholder="Type something..."
71+
onChange={(e) => setValue(e.target.value)}
72+
/>
73+
<div className="text-sm text-content-secondary">
74+
Character count: {value.length}
75+
</div>
76+
</div>
77+
);
78+
};
79+
80+
export const Controlled: Story = {
81+
render: () => <ControlledTextarea />,
82+
};
83+
84+
export const TypeText: Story = {
85+
args: {
86+
placeholder: "Type something here...",
87+
},
88+
play: async ({ canvasElement }) => {
89+
const canvas = within(canvasElement);
90+
const textarea = canvas.getByRole("textbox");
91+
await userEvent.type(
92+
textarea,
93+
"Hello, this is some example text being typed into the textarea!",
94+
);
95+
expect(textarea).toHaveValue(
96+
"Hello, this is some example text being typed into the textarea!",
97+
);
98+
},
99+
};
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copied from shadc/ui on 04/18/2025
3+
* @see {@link https://ui.shadcn.com/docs/components/textarea}
4+
*/
5+
import * as React from "react";
6+
7+
import { cn } from "utils/cn";
8+
9+
export const Textarea = React.forwardRef<
10+
HTMLTextAreaElement,
11+
React.ComponentProps<"textarea">
12+
>(({ className, ...props }, ref) => {
13+
return (
14+
<textarea
15+
className={cn(
16+
`flex min-h-[60px] w-full px-3 py-2 text-sm shadow-sm text-content-primary
17+
rounded-md border border-border bg-transparent placeholder:text-content-secondary
18+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
19+
disabled:cursor-not-allowed disabled:opacity-50 disabled:text-content-disabled md:text-sm`,
20+
className,
21+
)}
22+
ref={ref}
23+
{...props}
24+
/>
25+
);
26+
});

0 commit comments

Comments
 (0)