Skip to content

Commit

Permalink
fix(input): input with file type (#3268)
Browse files Browse the repository at this point in the history
* fix(input): remove value & onChange for input[type="file"]

* refactor(theme): remove unnecessary styles

* fix(theme): input file styles

* fix(theme): revise cursor-pointer on file

* feat(input): add file input logic

* feat(changeset): add changeset

* refactor(input): use warn function from shared-utils instead

* feat(input): add file type story

* refactor(input): include in Input Types
  • Loading branch information
wingkwong authored Jul 6, 2024
1 parent a3a77bf commit 93f1787
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 10 deletions.
6 changes: 6 additions & 0 deletions .changeset/rich-berries-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nextui-org/input": patch
"@nextui-org/theme": patch
---

revised styles and logic for input with file type (#2397, #2311, #2965)
32 changes: 29 additions & 3 deletions packages/components/input/src/use-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {useFocusRing} from "@react-aria/focus";
import {input} from "@nextui-org/theme";
import {useDOMRef, filterDOMProps} from "@nextui-org/react-utils";
import {useFocusWithin, useHover, usePress} from "@react-aria/interactions";
import {clsx, dataAttr, isEmpty, objectToDeps, safeAriaLabel} from "@nextui-org/shared-utils";
import {clsx, dataAttr, isEmpty, objectToDeps, safeAriaLabel, warn} from "@nextui-org/shared-utils";
import {useControlledState} from "@react-stately/utils";
import {useMemo, Ref, useCallback, useState} from "react";
import {chain, mergeProps} from "@react-aria/utils";
Expand Down Expand Up @@ -147,6 +147,7 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
const isFilledWithin = isFilled || isFocusWithin;
const isHiddenType = type === "hidden";
const isMultiline = originalProps.isMultiline;
const isFileTypeInput = type === "file";

const baseStyles = clsx(classNames?.base, className, isFilled ? "is-filled" : "");

Expand Down Expand Up @@ -191,6 +192,14 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
domRef,
);

if (isFileTypeInput) {
// for input[type="file"], we don't need `value` and `onChange` from `useTextField`
// otherwise, the default value with empty string will block the first attempt of file upload
// hence, remove `value` and `onChange` attribute here
delete inputProps.value;
delete inputProps.onChange;
}

const {isFocusVisible, isFocused, focusProps} = useFocusRing({
autoFocus,
isTextInput: true,
Expand All @@ -212,6 +221,19 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
const isInvalid = validationState === "invalid" || originalProps.isInvalid || isAriaInvalid;

const labelPlacement = useMemo<InputVariantProps["labelPlacement"]>(() => {
if (isFileTypeInput) {
// if `labelPlacement` is not defined, choose `outside` instead
// since the default value `inside` is not supported in file input
if (!originalProps.labelPlacement) return "outside";

// throw a warning if `labelPlacement` is `inside`
// and change it to `outside`
if (originalProps.labelPlacement === "inside") {
warn("Input with file type doesn't support inside label. Converting to outside ...");

return "outside";
}
}
if ((!originalProps.labelPlacement || originalProps.labelPlacement === "inside") && !label) {
return "outside";
}
Expand Down Expand Up @@ -271,10 +293,14 @@ export function useInput<T extends HTMLInputElement | HTMLTextAreaElement = HTML
className: slots.base({class: baseStyles}),
"data-slot": "base",
"data-filled": dataAttr(
isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown,
isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown || isFileTypeInput,
),
"data-filled-within": dataAttr(
isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown,
isFilledWithin ||
hasPlaceholder ||
hasStartContent ||
isPlaceholderShown ||
isFileTypeInput,
),
"data-focus-within": dataAttr(isFocusWithin),
"data-focus-visible": dataAttr(isFocusVisible),
Expand Down
2 changes: 2 additions & 0 deletions packages/components/input/stories/input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ const InputTypesTemplate = (args) => (
<Input {...args} label="Month" placeholder="Enter your month" type="month" />
<Input {...args} label="Week" placeholder="Enter your week" type="week" />
<Input {...args} label="Range" placeholder="Enter your range" type="range" />
<Input {...args} label="Single File" type="file" />
<Input {...args} multiple label="Multiple Files" type="file" />
</div>
);

Expand Down
9 changes: 2 additions & 7 deletions packages/core/theme/src/components/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const input = tv({
"w-full font-normal bg-transparent !outline-none placeholder:text-foreground-500 focus-visible:outline-none",
"data-[has-start-content=true]:ps-1.5",
"data-[has-end-content=true]:pe-1.5",
"file:cursor-pointer file:bg-transparent file:border-0",
],
clearButton: [
"p-2",
Expand Down Expand Up @@ -594,13 +595,7 @@ const input = tv({
label: ["group-data-[filled-within=true]:pointer-events-auto"],
},
},
// labelPlacement=[outside,outside-left]
{
labelPlacement: ["outside", "outside-left"],
class: {
input: "h-full",
},
},
// labelPlacement=[outside] & isMultiline
{
labelPlacement: "outside",
isMultiline: false,
Expand Down

0 comments on commit 93f1787

Please sign in to comment.