Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
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
---
Comment thread
wingkwong marked this conversation as resolved.

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="junior@nextui.org"
label="Email"
placeholder="Enter your email"
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";
Comment thread
IsDyh01 marked this conversation as resolved.

export const textareaContent = {
usage,
Expand All @@ -20,4 +21,5 @@ export const textareaContent = {
description,
controlled,
disableAutosize,
clearButton,
};
8 changes: 8 additions & 0 deletions apps/docs/content/docs/components/textarea.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ 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/).

### 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} />
Comment on lines +95 to +100
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IsDyh01 Please move this above the description example.

## Slots

- **base**: Input wrapper, it handles alignment, placement, and general appearance.
Expand All @@ -100,6 +106,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 +170,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", () => {
Comment thread
IsDyh01 marked this conversation as resolved.
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);
});
Comment thread
IsDyh01 marked this conversation as resolved.

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);
});
Comment thread
IsDyh01 marked this conversation as resolved.

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();
});
Comment thread
IsDyh01 marked this conversation as resolved.
});
Comment thread
IsDyh01 marked this conversation as resolved.
24 changes: 18 additions & 6 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,9 @@ const Textarea = forwardRef<"textarea", TextAreaProps>(
getHelperWrapperProps,
getDescriptionProps,
getErrorMessageProps,
isClearable,
getClearButtonProps,
getHeaderWrapperProps,
} = useInput<HTMLTextAreaElement>({...otherProps, ref, isMultiline: true});

const [hasMultipleRows, setIsHasMultipleRows] = useState(minRows > 1);
Expand Down Expand Up @@ -122,6 +122,10 @@ 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 @@ -143,7 +147,15 @@ const Textarea = forwardRef<"textarea", TextAreaProps>(
<Component {...getBaseProps()}>
{shouldLabelBeOutside ? labelContent : null}
<div {...getInputWrapperProps()} data-has-multiple-rows={dataAttr(hasMultipleRows)}>
{shouldLabelBeInside ? labelContent : null}
{isClearable ? (
<div {...getHeaderWrapperProps()}>
{shouldLabelBeInside ? labelContent : <label {...getLabelProps()}>{}</label>}
{clearButtonContent}
</div>
) : shouldLabelBeInside ? (
labelContent
) : null}
Comment thread
IsDyh01 marked this conversation as resolved.

{innerWrapper}
</div>
{hasHelper && hasHelperContent ? (
Expand Down
27 changes: 24 additions & 3 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 @@ -414,7 +414,11 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
"data-focus-visible": dataAttr(isFocusVisible),
"data-focus": dataAttr(isFocused),
className: slots.inputWrapper({
class: clsx(classNames?.inputWrapper, isFilled ? "is-filled" : ""),
class: clsx(
classNames?.inputWrapper,
isFilled ? "is-filled" : "",
isMultiline ? "flex-col items-start gap-0" : "",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IsDyh01 Tailwind classes should be exclusively defined in the theme package, specifically in the input.ts file. This is because the TailwindCSS content path is configured to target only the theme package

e.g.

// tailwind.config.js
import {nextui} from "@nextui-org/react";

/** @type {import('tailwindcss').Config} */
const config = {
  content: [
    // ...
    // make sure it's pointing to the ROOT node_module
    "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}" // <- theme package
  ],
  theme: {
    extend: {},
  },
  darkMode: "class",
  plugins: [nextui()]
}

export default config;

),
}),
...mergeProps(props, hoverProps),
onClick: (e) => {
Expand Down Expand Up @@ -518,13 +522,29 @@ 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,
isMultiline ? (isFilled ? "relative block opacity-100 p-0 -m-0 end-0" : "") : "",
),
}),
...mergeProps(clearPressProps, clearFocusProps),
};
},
[slots, isClearButtonFocusVisible, clearPressProps, clearFocusProps, classNames?.clearButton],
);

const getHeaderWrapperProps: PropGetter = useCallback(
(props = {}) => {
return {
...props,
className: slots.headerWrapper({class: clsx(classNames?.headerWrapper, props?.className)}),
};
},
[slots, classNames?.headerWrapper],
);

return {
Component,
classNames,
Expand Down Expand Up @@ -555,6 +575,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
getDescriptionProps,
getErrorMessageProps,
getClearButtonProps,
getHeaderWrapperProps,
};
}

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: "junior@nextui.org",
// eslint-disable-next-line no-console
onClear: () => console.log("textarea cleared"),
},
};
1 change: 1 addition & 0 deletions packages/core/theme/src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const input = tv({
helperWrapper: "hidden group-data-[has-helper=true]:flex p-1 relative flex-col gap-1.5",
description: "text-tiny text-foreground-400",
errorMessage: "text-tiny text-danger",
headerWrapper: "flex pb-1 justify-between w-full items-center",
Comment thread
IsDyh01 marked this conversation as resolved.
},
variants: {
variant: {
Expand Down
2 changes: 2 additions & 0 deletions packages/utilities/shared-icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export * from "./info-circle";
export * from "./warning";
export * from "./danger";
export * from "./success";
export * from "./trash";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IsDyh01 Delete this icon since we're not using it


// sets
export * from "./bulk";
export * from "./bold";
Expand Down
25 changes: 25 additions & 0 deletions packages/utilities/shared-icons/src/trash.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {IconSvgProps} from "./types";

export const TrashIcon = (props: IconSvgProps) => (
<svg
fill="none"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M14.0071 3.8847L14.0087 3.88486C14.0648 3.8902 14.1093 3.93884 14.108 3.99818C14.0999 4.04945 14.0549 4.08666 14.0067 4.08666H14.0066H14.0064H14.0063H14.0062H14.0061H14.006H14.0059H14.0057H14.0056H14.0055H14.0054H14.0053H14.0052H14.005H14.0049H14.0048H14.0047H14.0046H14.0045H14.0044H14.0042H14.0041H14.004H14.0039H14.0038H14.0037H14.0036H14.0034H14.0033H14.0032H14.0031H14.003H14.0029H14.0028H14.0027H14.0025H14.0024H14.0023H14.0022H14.0021H14.002H14.0019H14.0018H14.0017H14.0015H14.0014H14.0013H14.0012H14.0011H14.001H14.0009H14.0008H14.0007H14.0005H14.0004H14.0003H14.0002H14.0001H14H13.9999H13.9998H13.9997H13.9996H13.9995H13.9993H13.9992H13.9991H13.999H13.9989H13.9988H13.9987H13.9986H13.9985H13.9984H13.9983H13.9982H13.9981H13.9979H13.9978H13.9977H13.9976H13.9975H13.9974H13.9973H13.9972H13.9971H13.997H13.9969H13.9968H13.9967H13.9966H13.9965H13.9964H13.9962H13.9961H13.996H13.9959H13.9958H13.9957H13.9956H13.9955H13.9954H13.9953H13.9952H13.9951H13.995H13.9949H13.9948H13.9947H13.9946H13.9945H13.9944H13.9943H13.9942H13.9941H13.9939H13.9938H13.9937H13.9936H13.9935H13.9934H13.9933H13.9932H13.9931H13.993H13.9929H13.9928H13.9927H13.9926H13.9925H13.9924H13.9923H13.9922H13.9921H13.992H13.9919H13.9918H13.9917H13.9916H13.9915H13.9914H13.9913H13.9912H13.9911H13.991H13.9909H13.9908H13.9907H13.9906H13.9905H13.9904H13.9903H13.9902H13.9901H13.99H13.9899H13.9898H13.9897H13.9896H13.9895H13.9894H13.9893H13.9892H13.9891H13.989H13.9889H13.9888H13.9887H13.9886H13.9885H13.9884H13.9883H13.9882H13.9881H13.988H13.9879H13.9878H13.9877H13.9876H13.9875H13.9874H13.9873H13.9872H13.9871H13.987H13.9869H13.9868H13.9867H13.9866H13.9865H13.9864H13.9863H13.9862H13.9861H13.986H13.9859H13.9858H13.9857H13.9856H13.9855H13.9854H13.9853H13.9852H13.9851H13.985H13.9849H13.9848H13.9847H13.9846H13.9845H13.9844H13.9843H13.9842H13.9841H13.984H13.9839H13.9838H13.9837H13.9836H13.9835H13.9834H13.9833H13.9832H13.9831H13.983H13.9829H13.9828H13.9827H13.9826H13.9825H13.9824H13.9823H13.9823H13.9822H13.9821H13.982H13.9819H13.9818H13.9817H13.9816H13.9815H13.9814H13.9813H13.9812H13.9811H13.981H13.9809H13.9808H13.9807H13.9806H13.9805H13.9804H13.9803H13.9802H13.9801H13.98H13.9799H13.9798H13.9797H13.9796H13.9795H13.9794H13.9793H13.9792H13.9791H13.979H13.9789H13.9788H13.9787H13.9786H13.9785H13.9784H13.9783H13.9782H13.9781H13.9781H13.978H13.9779H13.9778H13.9777H13.9776H13.9775H13.9774H13.9773H13.9772H13.9771H13.977H13.9769H13.9768H13.9767H13.9766H13.9765H13.9764H13.9763H13.9762H13.9761H13.976H13.9759H13.9758H13.9757H13.9756H13.9755H13.9754H13.9753H13.9752H13.9751H13.975H13.9749H13.9748H13.9747H13.9746H13.9745H13.9744H13.9743H13.9742H13.9741H13.974H13.9739H13.9738H13.9737H13.9736H13.9735H13.9734H13.9733H13.9733C10.4314 3.7333 6.88973 3.59909 3.36755 3.94858C3.36742 3.94859 3.36729 3.9486 3.36717 3.94862L2.00875 4.0818C2.00861 4.08181 2.00848 4.08182 2.00835 4.08183C1.94717 4.08747 1.90326 4.04725 1.89811 3.99449C1.8929 3.9411 1.93182 3.89052 1.99126 3.88486L1.99237 3.88475L3.35187 3.75147C3.35194 3.75146 3.35201 3.75145 3.35208 3.75145C3.96457 3.69218 4.57811 3.65256 5.19909 3.6125L5.51656 3.59202L5.56806 3.27809L5.708 2.42509C5.76399 2.08568 5.81767 1.81209 5.98475 1.60624C6.12965 1.42771 6.42122 1.23333 7.12 1.23333H8.86667C9.56355 1.23333 9.85752 1.43445 10.0046 1.61915C10.1739 1.83176 10.2261 2.10927 10.2786 2.43103L10.2785 2.43103L10.2789 2.4334L10.42 3.26694V3.61839L10.7978 3.63938C11.8728 3.6991 12.9403 3.77868 14.0071 3.8847Z"
fill="#A1A1AA"
stroke="#A1A1AA"
strokeWidth="0.8"
/>
<path
d="M12.2134 5.56667C12.3292 5.56667 12.4438 5.61405 12.5285 5.70064C12.6088 5.78957 12.6542 5.90963 12.6474 6.03693C12.6474 6.03722 12.6473 6.03752 12.6473 6.03782L12.2342 12.8669C12.1959 13.3993 12.1525 13.8463 11.9087 14.1798C11.69 14.4792 11.2352 14.7733 10.14 14.7733H5.86004C4.76563 14.7733 4.31057 14.4779 4.09155 14.1776C3.8475 13.843 3.80414 13.3957 3.76583 12.8668L3.35272 6.03052C3.35271 6.03034 3.3527 6.03016 3.35269 6.02998C3.34597 5.90978 3.39108 5.78807 3.47441 5.69749C3.54714 5.61844 3.66453 5.56667 3.7867 5.56667H12.2134ZM6.8867 12.2333H9.1067C9.60095 12.2333 10.0067 11.8276 10.0067 11.3333C10.0067 10.8391 9.60095 10.4333 9.1067 10.4333H6.8867C6.39246 10.4333 5.9867 10.8391 5.9867 11.3333C5.9867 11.8276 6.39246 12.2333 6.8867 12.2333ZM6.33337 9.56667H9.6667C10.161 9.56667 10.5667 9.16092 10.5667 8.66667C10.5667 8.17242 10.161 7.76667 9.6667 7.76667H6.33337C5.83912 7.76667 5.43337 8.17242 5.43337 8.66667C5.43337 9.16092 5.83912 9.56667 6.33337 9.56667Z"
fill="#A1A1AA"
stroke="#A1A1AA"
strokeWidth="0.8"
/>
</svg>
);