Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6c2d9d2
feat(components): add clear button to the textarea component
IsDyh01 Jul 15, 2024
de5b54e
docs(textarea): add test and changeset
IsDyh01 Jul 16, 2024
6f92cdb
feat(textarea): modify the changeset file
IsDyh01 Jul 21, 2024
e471830
Merge branch 'canary' into pr/3477
wingkwong Oct 5, 2024
36b4540
feat(textarea): add clear button to textarea
IsDyh01 Oct 9, 2024
bc6f329
feat(textarea): add isClearable prop to textarea
IsDyh01 Oct 9, 2024
41a29ff
docs(textarea): add documentation to textarea
IsDyh01 Oct 9, 2024
bc18964
docs(textarea): add documentation to textarea
IsDyh01 Oct 9, 2024
ac31ed0
feat(textarea): replace the textarea component clear icon and modify …
IsDyh01 Oct 10, 2024
815193a
feat(textarea): revise the clear button position
IsDyh01 Oct 11, 2024
9ff14d5
feat(textarea): revise the clear button structure
IsDyh01 Oct 12, 2024
1ce8a2b
feat(textarea): revise the styles of clear button and textarea
IsDyh01 Oct 13, 2024
ecd3fa2
feat(textarea): revise the styles of RTL case
IsDyh01 Oct 13, 2024
7e1bd65
Merge remote-tracking branch 'upstream/canary' into feat/textarea-add…
IsDyh01 Oct 13, 2024
989b309
feat(textarea): change the rtl to pe
IsDyh01 Oct 13, 2024
590587f
feat(textarea): delete the px classname
IsDyh01 Oct 13, 2024
7eabb9f
chore(changeset): update package and message
wingkwong Oct 13, 2024
65f4074
test(textarea): add test case
IsDyh01 Oct 13, 2024
073599e
Merge remote-tracking branch 'upstream/canary' into feat/textarea-add…
IsDyh01 Oct 16, 2024
ac1ff15
feat(textarea): change the clear button structure
IsDyh01 Oct 16, 2024
6295aad
feat(textarea): optimized code
IsDyh01 Oct 17, 2024
811f214
chore(textarea): update the changeset file
IsDyh01 Oct 18, 2024
b3dcdd6
Merge remote-tracking branch 'upstream/canary' into feat/textarea-add…
IsDyh01 Oct 31, 2024
a1fe23d
docs(textarea): add slots doc to textarea
IsDyh01 Oct 31, 2024
7b136d9
Merge branch 'feat/textarea-add-clearButton' of github.com:IsDyh01/ne…
IsDyh01 Oct 31, 2024
80eead7
chore(textarea): update peerDevpeerDependencies version
IsDyh01 Oct 31, 2024
d57ed70
chore(textarea): add usecallback dep
IsDyh01 Oct 31, 2024
a457246
Merge remote-tracking branch 'upstream/canary' into feat/textarea-add…
IsDyh01 Nov 4, 2024
a4c2310
Update .changeset/five-adults-protect.md
jrgarciadev Nov 4, 2024
c2b16d4
feat(textarea): modify the clear button icon
IsDyh01 Nov 5, 2024
e2c276a
Merge remote-tracking branch 'upstream/canary' into feat/textarea-add…
IsDyh01 Nov 8, 2024
8bd6f64
fix(textarea): fix clearButton display
IsDyh01 Nov 8, 2024
ade96c0
Merge branch 'beta/release-next' into pr/3477
wingkwong Nov 23, 2024
7812fce
Update apps/docs/content/docs/components/textarea.mdx
jrgarciadev Nov 27, 2024
d6d8711
Merge branch 'beta/release-next' into pr/3477
wingkwong Nov 29, 2024
8a2487c
Merge branch 'beta/release-next' into pr/3477
wingkwong Nov 29, 2024
05df8ba
refactor(docs): apply new structure to doc
wingkwong Nov 29, 2024
27332f3
Merge branch 'beta/release-next' of github.com:nextui-org/nextui into…
jrgarciadev Nov 29, 2024
ac85227
fix: textarea issues with the clear button
jrgarciadev Nov 29, 2024
2db5d11
chore: adjust clear button position
jrgarciadev Nov 29, 2024
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
7 changes: 7 additions & 0 deletions .changeset/five-adults-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nextui-org/input": patch
"@nextui-org/shared-icons": patch
"@nextui-org/theme": patch
---

introduce `isClearable` to Textarea component (#2348, #2112)
16 changes: 16 additions & 0 deletions apps/docs/content/components/textarea/clear-button.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Textarea} from "@nextui-org/react";

export default function App() {
return (
<Textarea
isClearable
className="max-w-xs"
defaultValue="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
label="Description"
placeholder="Description"
variant="bordered"
// eslint-disable-next-line no-console
onClear={() => console.log("textarea cleared")}
/>
);
}
9 changes: 9 additions & 0 deletions apps/docs/content/components/textarea/clear-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import App from "./clear-button.raw.jsx?raw";

const react = {
"/App.jsx": App,
};

export default {
...react,
};
2 changes: 2 additions & 0 deletions apps/docs/content/components/textarea/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import errorMessage from "./error-message";
import description from "./description";
import controlled from "./controlled";
import disableAutosize from "./disable-autosize";
import clearButton from "./clear-button";

export const textareaContent = {
usage,
Expand All @@ -20,4 +21,5 @@ export const textareaContent = {
description,
controlled,
disableAutosize,
clearButton,
};
10 changes: 10 additions & 0 deletions apps/docs/content/docs/components/textarea.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ the end of the label and the textarea will be required.

<CodeDemo title="Required" files={textareaContent.required} />

### Clear Button

If you pass the `isClearable` property to the textarea, it will have a clear button at the
end of the textarea, it will be visible when the textarea has a value.

<CodeDemo title="Clear Button" files={textareaContent.clearButton} />

### Autosize

Textarea grows automatically based on the content, but you can also set a min and max height to
Expand Down Expand Up @@ -92,6 +99,7 @@ You can use the `value` and `onValueChange` properties to control the input valu
> **Note**: NextUI `Textarea` also supports native events like `onChange`, useful for form libraries
> such as [Formik](https://formik.org/) and [React Hook Form](https://react-hook-form.com/).


## Slots

- **base**: Input wrapper, it handles alignment, placement, and general appearance.
Expand All @@ -100,6 +108,7 @@ You can use the `value` and `onValueChange` properties to control the input valu
- **input**: The textarea input element.
- **description**: The description of the textarea.
- **errorMessage**: The error message of the textarea.
- **headerWrapper**: Wraps the `label` and the `clearButton`.

<Spacer y={4} />

Expand Down Expand Up @@ -163,6 +172,7 @@ You can use the `value` and `onValueChange` properties to control the input valu
| isRequired | `boolean` | Whether user input is required on the textarea before form submission. | `false` |
| isReadOnly | `boolean` | Whether the textarea can be selected but not changed by the user. | |
| isDisabled | `boolean` | Whether the textarea is disabled. | `false` |
| isClearable | `boolean` | Whether the textarea should have a clear button. | `false` |
| isInvalid | `boolean` | Whether the textarea is invalid. | `false` |
| validationState | `valid` \| `invalid` | Whether the textarea should display its "valid" or "invalid" visual styling. (**Deprecated**) use **isInvalid** instead. | - |
| disableAutosize | `boolean` | Whether the textarea auto vertically resize should be disabled. | `false` |
Expand Down
81 changes: 81 additions & 0 deletions packages/components/input/__tests__/textarea.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from "react";
import {render} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import {Textarea} from "../src";

describe("Textarea", () => {
it("should clear the value and onClear is triggered", async () => {
const onClear = jest.fn();

const ref = React.createRef<HTMLTextAreaElement>();

const {getByRole} = render(
<Textarea
ref={ref}
isClearable
defaultValue="junior@nextui.org"
label="test textarea"
onClear={onClear}
/>,
);

const clearButton = getByRole("button");

expect(clearButton).not.toBeNull();

const user = userEvent.setup();

await user.click(clearButton);

expect(ref.current?.value)?.toBe("");

expect(onClear).toHaveBeenCalledTimes(1);
});

it("should disable clear button when isReadOnly is true", async () => {
const onClear = jest.fn();

const ref = React.createRef<HTMLTextAreaElement>();

const {getByRole} = render(
<Textarea
ref={ref}
isClearable
isReadOnly
defaultValue="readOnly test for clear button"
label="test textarea"
onClear={onClear}
/>,
);

const clearButton = getByRole("button")!;

expect(clearButton).not.toBeNull();

const user = userEvent.setup();

await user.click(clearButton);

expect(onClear).toHaveBeenCalledTimes(0);
});

it("should appear clear button when just define onClear but not define isClearable", async () => {
const onClear = jest.fn();

const ref = React.createRef<HTMLTextAreaElement>();

const {getByRole} = render(
<Textarea
ref={ref}
defaultValue="junior@nextui.org"
label="test textarea"
onClear={onClear}
/>,
);

const clearButton = getByRole("button");

expect(clearButton).not.toBeNull();
});
});
18 changes: 13 additions & 5 deletions packages/components/input/src/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {forwardRef} from "@nextui-org/system";
import {mergeProps} from "@react-aria/utils";
import {useMemo, useState} from "react";
import TextareaAutosize from "react-textarea-autosize";
import {CloseFilledIcon} from "@nextui-org/shared-icons";

import {UseInputProps, useInput} from "./use-input";

Expand All @@ -14,11 +15,7 @@ type TextareaAutoSizeStyle = Omit<
height?: number;
};

type OmittedInputProps =
| "isClearButtonFocusVisible"
| "isLabelPlaceholder"
| "isClearable"
| "isTextarea";
type OmittedInputProps = "isClearButtonFocusVisible" | "isLabelPlaceholder" | "isTextarea";

export type TextareaHeightChangeMeta = {
rowHeight: number;
Expand Down Expand Up @@ -88,6 +85,8 @@ const Textarea = forwardRef<"textarea", TextAreaProps>(
getHelperWrapperProps,
getDescriptionProps,
getErrorMessageProps,
isClearable,
getClearButtonProps,
} = useInput<HTMLTextAreaElement>({...otherProps, ref, isMultiline: true});

const [hasMultipleRows, setIsHasMultipleRows] = useState(minRows > 1);
Expand Down Expand Up @@ -122,6 +121,14 @@ const Textarea = forwardRef<"textarea", TextAreaProps>(
/>
);

const clearButtonContent = useMemo(() => {
return isClearable ? (
<button {...getClearButtonProps()}>
<CloseFilledIcon />
</button>
) : null;
}, [isClearable, getClearButtonProps]);

const innerWrapper = useMemo(() => {
if (startContent || endContent) {
return (
Expand All @@ -145,6 +152,7 @@ const Textarea = forwardRef<"textarea", TextAreaProps>(
<div {...getInputWrapperProps()} data-has-multiple-rows={dataAttr(hasMultipleRows)}>
{shouldLabelBeInside ? labelContent : null}
{innerWrapper}
{clearButtonContent}
</div>
{hasHelper && hasHelperContent ? (
<div {...getHelperWrapperProps()}>
Expand Down
6 changes: 4 additions & 2 deletions packages/components/input/src/use-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
"data-has-start-content": dataAttr(hasStartContent),
"data-has-end-content": dataAttr(!!endContent),
className: slots.input({
class: clsx(classNames?.input, isFilled ? "is-filled" : ""),
class: clsx(classNames?.input, isFilled ? "is-filled" : "", isMultiline ? "pe-0" : ""),
}),
...mergeProps(
focusProps,
Expand Down Expand Up @@ -518,7 +518,9 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
"aria-label": "clear input",
"data-slot": "clear-button",
"data-focus-visible": dataAttr(isClearButtonFocusVisible),
className: slots.clearButton({class: clsx(classNames?.clearButton, props?.className)}),
className: slots.clearButton({
class: clsx(classNames?.clearButton, props?.className),
}),
...mergeProps(clearPressProps, clearFocusProps),
};
},
Expand Down
12 changes: 12 additions & 0 deletions packages/components/input/stories/textarea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,15 @@ export const IsInvalid = {
errorMessage: "Please enter a valid description",
},
};

export const Clearable = {
render: Template,

args: {
...defaultProps,
placeholder: "Enter your description",
defaultValue: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
// eslint-disable-next-line no-console
onClear: () => console.log("textarea cleared"),
},
};
30 changes: 27 additions & 3 deletions packages/core/theme/src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ const input = tv({
"p-2",
"-m-2",
"z-10",
"hidden",
"absolute",
"end-3",
"start-auto",
"pointer-events-none",
"appearance-none",
"outline-none",
"select-none",
Expand Down Expand Up @@ -192,7 +192,11 @@ const input = tv({
isClearable: {
true: {
input: "peer pe-6 input-search-cancel-button-none",
clearButton: "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block",
clearButton: [
"peer-data-[filled=true]:pointer-events-auto",
"peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block",
"peer-data-[filled=true]:scale-100",
],
},
},
isDisabled: {
Expand All @@ -219,6 +223,7 @@ const input = tv({
inputWrapper: "!h-auto",
innerWrapper: "items-start group-data-[has-label=true]:items-start",
input: "resize-none data-[hide-scroll=true]:scrollbar-hide",
clearButton: "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10",
},
},
disableAnimation: {
Expand All @@ -236,7 +241,14 @@ const input = tv({
"motion-reduce:transition-none",
"transition-[transform,color,left,opacity]",
],
clearButton: ["transition-opacity", "motion-reduce:transition-none"],
clearButton: [
"scale-90",
"ease-out",
"duration-150",
"transition-[opacity,transform]",
"motion-reduce:transition-none",
"motion-reduce:scale-100",
],
},
},
},
Expand Down Expand Up @@ -862,6 +874,18 @@ const input = tv({
inputWrapper: "data-[has-multiple-rows=true]:rounded-large",
},
},
// isClearable & isMultiline
{
isClearable: true,
isMultiline: true,
class: {
clearButton: [
"group-data-[has-value=true]:opacity-70 group-data-[has-value=true]:block",
"group-data-[has-value=true]:scale-100",
"group-data-[has-value=true]:pointer-events-auto",
],
},
},
],
});

Expand Down
1 change: 1 addition & 0 deletions packages/utilities/shared-icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export * from "./info-circle";
export * from "./warning";
export * from "./danger";
export * from "./success";

// sets
export * from "./bulk";
export * from "./bold";
Expand Down