diff --git a/.changeset/eight-moose-dance.md b/.changeset/eight-moose-dance.md new file mode 100644 index 0000000000..0d33ab500f --- /dev/null +++ b/.changeset/eight-moose-dance.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/input": patch +"@nextui-org/theme": patch +--- + +I have made a change so that when both endContent and isClearable are specified, endContent no longer replaces the Clear button. Now, both endContent and the Clear button will be displayed. diff --git a/packages/components/input/src/input.tsx b/packages/components/input/src/input.tsx index 91dadea0e9..82464c2ef3 100644 --- a/packages/components/input/src/input.tsx +++ b/packages/components/input/src/input.tsx @@ -1,11 +1,15 @@ import {CloseFilledIcon} from "@nextui-org/shared-icons"; -import {useMemo} from "react"; +import {useMemo, useEffect, useState, useRef, CSSProperties} from "react"; import {forwardRef} from "@nextui-org/system"; import {UseInputProps, useInput} from "./use-input"; export interface InputProps extends Omit {} +export interface ClearButtonCSSProperties { + "--end-content-width": string; +} + const Input = forwardRef<"input", InputProps>((props, ref) => { const { Component, @@ -32,15 +36,40 @@ const Input = forwardRef<"input", InputProps>((props, ref) => { getClearButtonProps, } = useInput({...props, ref}); - const labelContent = label ? : null; + const [endContentWidth, setEndContentWidth] = useState(0); + const endContentWrapperRef = useRef(null); - const end = useMemo(() => { - if (isClearable) { - return ; + useEffect(() => { + if (endContentWrapperRef.current) { + setEndContentWidth(endContentWrapperRef.current.getBoundingClientRect().width); } + }, [endContent]); - return endContent; - }, [isClearable, getClearButtonProps]); + const end = useMemo(() => { + return ( + <> + {isClearable && ( + + )} + {endContent &&
{endContent}
} + + ); + }, [isClearable, endContent, getClearButtonProps]); const helperWrapper = useMemo(() => { const shouldShowError = isInvalid && errorMessage; @@ -68,6 +97,29 @@ const Input = forwardRef<"input", InputProps>((props, ref) => { ]); const innerWrapper = useMemo(() => { + if (startContent || end) { + return ( +
+ {startContent} + + {end} +
+ ); + } + return (
{startContent} @@ -77,6 +129,8 @@ const Input = forwardRef<"input", InputProps>((props, ref) => { ); }, [startContent, end, getInputProps, getInnerWrapperProps]); + const labelContent = label ? : null; + const mainWrapper = useMemo(() => { if (shouldLabelBeOutside) { return ( diff --git a/packages/components/input/stories/input.stories.tsx b/packages/components/input/stories/input.stories.tsx index ebb29582a7..2884599fbf 100644 --- a/packages/components/input/stories/input.stories.tsx +++ b/packages/components/input/stories/input.stories.tsx @@ -606,6 +606,37 @@ export const Clearable = { }, }; +export const ClearableAndEndContent = { + render: Template, + args: { + ...defaultProps, + variant: "bordered", + placeholder: "Search for something", + defaultValue: "", + // eslint-disable-next-line no-console + onClear: () => console.log("input cleared"), + clearable: true, + label: "Search", + labelPlacement: "outside", + startContent: ( + + ), + endContent: ( + <> + + + + + + ), + }, +}; + export const StartContent = { render: StartContentTemplate, diff --git a/packages/core/theme/src/components/input.ts b/packages/core/theme/src/components/input.ts index bc53279f34..2d122bb9fd 100644 --- a/packages/core/theme/src/components/input.ts +++ b/packages/core/theme/src/components/input.ts @@ -53,7 +53,6 @@ const input = tv({ "z-10", "hidden", "absolute", - "end-3", "start-auto", "appearance-none", "outline-none", @@ -190,8 +189,10 @@ 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", + input: + "peer pe-6 input-search-cancel-button-none data-[filled=true]:data-[is-end-content=true]:pr-[calc(var(--end-content-width)+theme(spacing.4))] data-[filled=true]:data-[is-end-content=true]:rtl:pl-[calc(var(--end-content-width)+theme(spacing.4))] rtl:!pr-auto", + clearButton: + "peer-data-[filled=true]:opacity-70 peer-data-[filled=true]:block data-[is-end-content=false]:right-3 data-[is-end-content=false]:rtl:left-3 data-[is-end-content=true]:right-[calc(var(--end-content-width)+theme(spacing.5))] data-[is-end-content=true]:rtl:left-[calc(var(--end-content-width)+theme(spacing.5))] rtl:!right-auto", }, }, isDisabled: {