Merged in fix/3697-prettier-configs (pull request #3396)

fix(SW-3691): Setup one prettier config for whole repo

* Setup prettierrc in root and remove other configs


Approved-by: Joakim Jäderberg
Approved-by: Linus Flood
This commit is contained in:
Rasmus Langvad
2026-01-07 12:45:50 +00:00
parent 932413412b
commit d0546926a9
500 changed files with 18367 additions and 18419 deletions

View File

@@ -1,14 +1,14 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
import { expect } from 'storybook/test'
import { expect } from "storybook/test"
import { Input } from './Input'
import { TextField } from 'react-aria-components'
import { MaterialIcon } from '../Icons/MaterialIcon'
import type { SymbolCodepoints } from '../Icons/MaterialIcon/MaterialSymbol/types'
import { Input } from "./Input"
import { TextField } from "react-aria-components"
import { MaterialIcon } from "../Icons/MaterialIcon"
import type { SymbolCodepoints } from "../Icons/MaterialIcon/MaterialSymbol/types"
const meta: Meta<typeof Input> = {
title: 'Core Components/Input',
title: "Core Components/Input",
// @ts-expect-error Input does not support this, but wrapping <TextField> does
component: ({ isInvalid, validationState, ...props }) => (
<TextField isInvalid={isInvalid} data-validation-state={validationState}>
@@ -17,117 +17,117 @@ const meta: Meta<typeof Input> = {
),
argTypes: {
label: {
control: 'text',
description: 'The label text displayed for the input field',
control: "text",
description: "The label text displayed for the input field",
table: {
type: { summary: 'string' },
type: { summary: "string" },
},
},
labelPosition: {
control: 'select',
options: ['floating', 'top'],
description: 'Position of the label relative to the input',
control: "select",
options: ["floating", "top"],
description: "Position of the label relative to the input",
table: {
type: { summary: "'floating' | 'top'" },
defaultValue: { summary: "'floating'" },
},
},
placeholder: {
control: 'text',
description: 'Placeholder text shown when input is empty',
control: "text",
description: "Placeholder text shown when input is empty",
table: {
type: { summary: 'string' },
defaultValue: { summary: 'undefined' },
type: { summary: "string" },
defaultValue: { summary: "undefined" },
},
},
required: {
control: 'boolean',
description: 'Whether the input is required',
control: "boolean",
description: "Whether the input is required",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
disabled: {
control: 'boolean',
description: 'Whether the input is disabled',
control: "boolean",
description: "Whether the input is disabled",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
showClearContentIcon: {
control: 'boolean',
description: 'Whether the clear content icon is shown',
control: "boolean",
description: "Whether the clear content icon is shown",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
showLeftIcon: {
control: 'boolean',
description: 'Whether to show a left icon',
control: "boolean",
description: "Whether to show a left icon",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
showRightIcon: {
control: 'boolean',
description: 'Whether to show a right icon',
control: "boolean",
description: "Whether to show a right icon",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
leftIconName: {
control: 'select',
control: "select",
options: [
'calendar_month',
'credit_card',
'email',
'info_circle',
'location_on',
'lock',
'phone',
'search',
'sell',
'visibility',
'visibility_off',
"calendar_month",
"credit_card",
"email",
"info_circle",
"location_on",
"lock",
"phone",
"search",
"sell",
"visibility",
"visibility_off",
],
description: 'Icon name for the left icon',
description: "Icon name for the left icon",
table: {
type: { summary: 'string' },
type: { summary: "string" },
defaultValue: { summary: "'person'" },
},
},
rightIconName: {
control: 'select',
control: "select",
options: [
'calendar_month',
'credit_card',
'email',
'info_circle',
'location_on',
'lock',
'phone',
'search',
'sell',
'visibility',
'visibility_off',
"calendar_month",
"credit_card",
"email",
"info_circle",
"location_on",
"lock",
"phone",
"search",
"sell",
"visibility",
"visibility_off",
],
description: 'Icon name for the right icon',
description: "Icon name for the right icon",
table: {
type: { summary: 'string' },
type: { summary: "string" },
defaultValue: { summary: "'lock'" },
},
},
showWarning: {
control: 'boolean',
description: 'Whether to show warning validation state',
control: "boolean",
description: "Whether to show warning validation state",
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -140,13 +140,13 @@ type Story = StoryObj<typeof Input>
export const Default: Story = {
args: {
label: 'Label',
name: 'foo',
label: "Label",
name: "foo",
required: false,
showLeftIcon: false,
showRightIcon: false,
leftIconName: 'person',
rightIconName: 'lock',
leftIconName: "person",
rightIconName: "lock",
showWarning: false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
@@ -167,7 +167,7 @@ export const Default: Story = {
showWarning?: boolean
}
const validationState = showWarning ? 'warning' : undefined
const validationState = showWarning ? "warning" : undefined
return (
<TextField data-validation-state={validationState}>
@@ -189,15 +189,15 @@ export const Default: Story = {
)
},
play: async ({ canvas, userEvent }) => {
const textbox = canvas.getByRole('textbox')
const textbox = canvas.getByRole("textbox")
expect(textbox).not.toBeDisabled()
expect(textbox).toHaveValue('')
expect(textbox).toHaveValue("")
await userEvent.type(textbox, 'Hello World')
expect(textbox).toHaveValue('Hello World')
await userEvent.type(textbox, "Hello World")
expect(textbox).toHaveValue("Hello World")
await userEvent.clear(textbox)
expect(textbox).toHaveValue('')
expect(textbox).toHaveValue("")
},
}

View File

@@ -1,8 +1,8 @@
import { describe, expect, it, vi, afterEach } from 'vitest'
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Input } from './Input'
import { TextField } from 'react-aria-components'
import { describe, expect, it, vi, afterEach } from "vitest"
import { render, screen, fireEvent, cleanup } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Input } from "./Input"
import { TextField } from "react-aria-components"
afterEach(() => {
cleanup()
@@ -22,158 +22,158 @@ const renderInputStandalone = (props: React.ComponentProps<typeof Input>) => {
return render(<Input {...props} />)
}
describe('Input', () => {
describe('props', () => {
it('applies required attribute', () => {
renderInput({ label: 'Email', required: true })
expect(screen.getByRole('textbox')).toHaveProperty('required', true)
describe("Input", () => {
describe("props", () => {
it("applies required attribute", () => {
renderInput({ label: "Email", required: true })
expect(screen.getByRole("textbox")).toHaveProperty("required", true)
})
it('applies readOnly attribute', () => {
renderInput({ label: 'Email', readOnly: true })
expect(screen.getByRole('textbox')).toHaveProperty('readOnly', true)
it("applies readOnly attribute", () => {
renderInput({ label: "Email", readOnly: true })
expect(screen.getByRole("textbox")).toHaveProperty("readOnly", true)
})
it('applies placeholder for floating label when provided', () => {
renderInput({ label: 'Email', placeholder: 'Enter email' })
expect(screen.getByRole('textbox').getAttribute('placeholder')).toBe(
'Enter email'
it("applies placeholder for floating label when provided", () => {
renderInput({ label: "Email", placeholder: "Enter email" })
expect(screen.getByRole("textbox").getAttribute("placeholder")).toBe(
"Enter email"
)
})
it('applies empty placeholder for top label by default', () => {
renderInput({ label: 'Email', labelPosition: 'top' })
expect(screen.getByRole('textbox').getAttribute('placeholder')).toBe('')
it("applies empty placeholder for top label by default", () => {
renderInput({ label: "Email", labelPosition: "top" })
expect(screen.getByRole("textbox").getAttribute("placeholder")).toBe("")
})
it('applies custom id', () => {
it("applies custom id", () => {
// Use standalone render since TextField overrides id via context
renderInputStandalone({ label: 'Email', id: 'custom-id' })
expect(screen.getByRole('textbox').getAttribute('id')).toBe('custom-id')
renderInputStandalone({ label: "Email", id: "custom-id" })
expect(screen.getByRole("textbox").getAttribute("id")).toBe("custom-id")
})
it('applies aria-describedby', () => {
renderInput({ label: 'Email', 'aria-describedby': 'error-message' })
expect(screen.getByRole('textbox').getAttribute('aria-describedby')).toBe(
'error-message'
it("applies aria-describedby", () => {
renderInput({ label: "Email", "aria-describedby": "error-message" })
expect(screen.getByRole("textbox").getAttribute("aria-describedby")).toBe(
"error-message"
)
})
})
describe('clear content button', () => {
it('does not show clear button when showClearContentIcon is false', () => {
describe("clear content button", () => {
it("does not show clear button when showClearContentIcon is false", () => {
renderInput({
label: 'Email',
value: 'test',
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: false,
})
expect(screen.queryByLabelText('Clear content')).toBeNull()
expect(screen.queryByLabelText("Clear content")).toBeNull()
})
it('does not show clear button when input is empty', () => {
it("does not show clear button when input is empty", () => {
renderInput({
label: 'Email',
value: '',
label: "Email",
value: "",
onChange: vi.fn(),
showClearContentIcon: true,
})
expect(screen.queryByLabelText('Clear content')).toBeNull()
expect(screen.queryByLabelText("Clear content")).toBeNull()
})
it('shows clear button when input has value and showClearContentIcon is true', () => {
it("shows clear button when input has value and showClearContentIcon is true", () => {
renderInput({
label: 'Email',
value: 'test',
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: true,
})
expect(screen.getByLabelText('Clear content')).toBeTruthy()
expect(screen.getByLabelText("Clear content")).toBeTruthy()
})
})
describe('icons', () => {
it('renders left icon when provided', () => {
describe("icons", () => {
it("renders left icon when provided", () => {
renderInput({
label: 'Search',
label: "Search",
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
leftIcon: <span data-testid="left-icon">🔍</span>,
})
expect(screen.getByTestId('left-icon')).toBeTruthy()
expect(screen.getByTestId("left-icon")).toBeTruthy()
})
it('renders right icon when provided', () => {
it("renders right icon when provided", () => {
renderInput({
label: 'Password',
label: "Password",
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(screen.getByTestId('right-icon')).toBeTruthy()
expect(screen.getByTestId("right-icon")).toBeTruthy()
})
it('hides right icon when clear button is shown', () => {
it("hides right icon when clear button is shown", () => {
renderInput({
label: 'Email',
value: 'test',
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: true,
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(screen.queryByTestId('right-icon')).toBeNull()
expect(screen.getByLabelText('Clear content')).toBeTruthy()
expect(screen.queryByTestId("right-icon")).toBeNull()
expect(screen.getByLabelText("Clear content")).toBeTruthy()
})
it('shows right icon when clear button condition not met', () => {
it("shows right icon when clear button condition not met", () => {
renderInput({
label: 'Email',
value: '',
label: "Email",
value: "",
onChange: vi.fn(),
showClearContentIcon: true,
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(screen.getByTestId('right-icon')).toBeTruthy()
expect(screen.getByTestId("right-icon")).toBeTruthy()
})
})
describe('controlled input', () => {
it('displays the controlled value', () => {
describe("controlled input", () => {
it("displays the controlled value", () => {
renderInput({
label: 'Email',
value: 'test@example.com',
label: "Email",
value: "test@example.com",
onChange: vi.fn(),
})
expect(screen.getByRole('textbox')).toHaveProperty(
'value',
'test@example.com'
expect(screen.getByRole("textbox")).toHaveProperty(
"value",
"test@example.com"
)
})
it('calls onChange when typing', async () => {
it("calls onChange when typing", async () => {
const onChange = vi.fn()
renderInput({ label: 'Email', value: '', onChange })
renderInput({ label: "Email", value: "", onChange })
const input = screen.getByRole('textbox')
await userEvent.type(input, 'a')
const input = screen.getByRole("textbox")
await userEvent.type(input, "a")
expect(onChange).toHaveBeenCalled()
})
it('does not change value without onChange updating it', () => {
it("does not change value without onChange updating it", () => {
const onChange = vi.fn()
renderInput({ label: 'Email', value: 'initial', onChange })
renderInput({ label: "Email", value: "initial", onChange })
const input = screen.getByRole('textbox')
fireEvent.change(input, { target: { value: 'changed' } })
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "changed" } })
// Value stays the same because it's controlled
expect(input).toHaveProperty('value', 'initial')
expect(input).toHaveProperty("value", "initial")
})
})
describe('ref forwarding', () => {
it('forwards ref to the input element', () => {
describe("ref forwarding", () => {
it("forwards ref to the input element", () => {
const ref = { current: null as HTMLInputElement | null }
render(
<TextField>
@@ -183,7 +183,7 @@ describe('Input', () => {
expect(ref.current).toBeInstanceOf(HTMLInputElement)
})
it('allows focusing via ref', () => {
it("allows focusing via ref", () => {
const ref = { current: null as HTMLInputElement | null }
render(
<TextField>

View File

@@ -1,26 +1,26 @@
import { cx } from 'class-variance-authority'
import { cx } from "class-variance-authority"
import {
type ForwardedRef,
forwardRef,
useId,
useImperativeHandle,
useRef,
} from 'react'
import { Input as AriaInput, Label as AriaLabel } from 'react-aria-components'
} from "react"
import { Input as AriaInput, Label as AriaLabel } from "react-aria-components"
import { InputLabel } from '../InputLabel'
import { InputLabel } from "../InputLabel"
import styles from './input.module.css'
import styles from "./input.module.css"
import { MaterialIcon } from '../Icons/MaterialIcon'
import { Typography } from '../Typography'
import type { InputProps } from './types'
import { clearInput, useInputHasValue } from './utils'
import { MaterialIcon } from "../Icons/MaterialIcon"
import { Typography } from "../Typography"
import type { InputProps } from "./types"
import { clearInput, useInputHasValue } from "./utils"
const InputComponent = forwardRef(function AriaInputWithLabelComponent(
{
label,
labelPosition = 'floating',
labelPosition = "floating",
leftIcon,
rightIcon,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -29,9 +29,9 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
placeholder,
id,
required,
'data-validation-state': validationState,
"data-validation-state": validationState,
...props
}: InputProps & { 'data-validation-state'?: string },
}: InputProps & { "data-validation-state"?: string },
ref: ForwardedRef<HTMLInputElement>
) {
// Create an internal ref that we can access
@@ -57,7 +57,7 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
}
// When labelPosition is 'top', restructure to have label outside container
// We need an ID for proper label-input association
if (labelPosition === 'top') {
if (labelPosition === "top") {
const inputId = id || generatedId
return (
@@ -93,7 +93,7 @@ const InputComponent = forwardRef(function AriaInputWithLabelComponent(
// Avoid duplicating label text in placeholder when label is positioned above
// Screen readers would announce the label twice (once as label, once as placeholder)
// Only use placeholder if explicitly provided, otherwise use empty string
placeholder={placeholder ?? ''}
placeholder={placeholder ?? ""}
className={cx(
styles.input,
styles.inputTopLabel,

View File

@@ -1 +1 @@
export { Input } from './Input'
export { Input } from "./Input"

View File

@@ -22,11 +22,11 @@
&:has(.input:focus):not(:has(.input:disabled)):not(
:has(.input:read-only)
):not(:has(.input[data-invalid='true'])):not(
:has(.input[aria-invalid='true'])
):not(:has(.input[data-warning='true'])):not(
:has(.input[data-validation-state='warning'])
):not([data-validation-state='warning']) {
):not(:has(.input[data-invalid="true"])):not(
:has(.input[aria-invalid="true"])
):not(:has(.input[data-warning="true"])):not(
:has(.input[data-validation-state="warning"])
):not([data-validation-state="warning"]) {
outline-offset: -2px;
outline: 2px solid var(--Border-Interactive-Focus);
}
@@ -38,7 +38,7 @@
cursor: unset;
}
&:has(.input[data-invalid='true'], .input[aria-invalid='true']) {
&:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
border-color: var(--Border-Interactive-Error);
&:focus-within,
@@ -49,9 +49,9 @@
}
}
&:has(.input[data-warning='true']),
&:has(.input[data-validation-state='warning']),
&[data-validation-state='warning'] {
&:has(.input[data-warning="true"]),
&:has(.input[data-validation-state="warning"]),
&[data-validation-state="warning"] {
background-color: var(--Surface-Feedback-Warning-light);
border-color: var(--Border-Interactive-Focus);
@@ -84,7 +84,7 @@
&:focus,
&:placeholder-shown,
&[value]:not([value='']) {
&[value]:not([value=""]) {
height: 24px;
outline: none;
}

View File

@@ -1,9 +1,9 @@
import { ComponentProps } from 'react'
import { Input } from 'react-aria-components'
import { ComponentProps } from "react"
import { Input } from "react-aria-components"
export interface InputProps extends ComponentProps<typeof Input> {
label: string
labelPosition?: 'floating' | 'top'
labelPosition?: "floating" | "top"
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
onRightIconClick?: () => void

View File

@@ -1,56 +1,56 @@
import { describe, expect, it, vi, beforeEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useInputHasValue, clearInput } from './utils'
import { describe, expect, it, vi, beforeEach } from "vitest"
import { renderHook } from "@testing-library/react"
import { useInputHasValue, clearInput } from "./utils"
describe('useInputHasValue', () => {
const createMockRef = (value = '') => ({
describe("useInputHasValue", () => {
const createMockRef = (value = "") => ({
current: { value } as HTMLInputElement,
})
describe('controlled input (value prop)', () => {
it('returns true when value has content', () => {
describe("controlled input (value prop)", () => {
it("returns true when value has content", () => {
const ref = createMockRef()
const { result } = renderHook(() => useInputHasValue('hello', ref))
const { result } = renderHook(() => useInputHasValue("hello", ref))
expect(result.current).toBe(true)
})
it('returns false when value is empty string', () => {
it("returns false when value is empty string", () => {
const ref = createMockRef()
const { result } = renderHook(() => useInputHasValue('', ref))
const { result } = renderHook(() => useInputHasValue("", ref))
expect(result.current).toBe(false)
})
it('returns false when value is only whitespace', () => {
it("returns false when value is only whitespace", () => {
const ref = createMockRef()
const { result } = renderHook(() => useInputHasValue(' ', ref))
const { result } = renderHook(() => useInputHasValue(" ", ref))
expect(result.current).toBe(false)
})
it('updates when value prop changes', () => {
it("updates when value prop changes", () => {
const ref = createMockRef()
const { result, rerender } = renderHook(
({ value }) => useInputHasValue(value, ref),
{ initialProps: { value: '' } }
{ initialProps: { value: "" } }
)
expect(result.current).toBe(false)
rerender({ value: 'new value' })
rerender({ value: "new value" })
expect(result.current).toBe(true)
})
})
})
describe('clearInput', () => {
describe("clearInput", () => {
beforeEach(() => {
vi.restoreAllMocks()
})
it('calls onChange with empty value for controlled input', () => {
it("calls onChange with empty value for controlled input", () => {
const onChange = vi.fn()
const inputRef = {
current: {
value: 'test',
value: "test",
focus: vi.fn(),
} as unknown as HTMLInputElement,
}
@@ -58,22 +58,22 @@ describe('clearInput', () => {
clearInput({
inputRef,
onChange,
value: 'test',
value: "test",
})
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
target: { value: '' },
target: { value: "" },
})
)
expect(inputRef.current.focus).toHaveBeenCalled()
})
it('sets input value directly for uncontrolled input', () => {
const input = document.createElement('input')
input.value = 'test'
const focusSpy = vi.spyOn(input, 'focus')
const dispatchSpy = vi.spyOn(input, 'dispatchEvent')
it("sets input value directly for uncontrolled input", () => {
const input = document.createElement("input")
input.value = "test"
const focusSpy = vi.spyOn(input, "focus")
const dispatchSpy = vi.spyOn(input, "dispatchEvent")
const inputRef = { current: input }
@@ -83,19 +83,19 @@ describe('clearInput', () => {
value: undefined,
})
expect(input.value).toBe('')
expect(input.value).toBe("")
expect(dispatchSpy).toHaveBeenCalled()
expect(focusSpy).toHaveBeenCalled()
})
it('does nothing when ref is null', () => {
it("does nothing when ref is null", () => {
const onChange = vi.fn()
const inputRef = { current: null }
clearInput({
inputRef,
onChange,
value: 'test',
value: "test",
})
expect(onChange).not.toHaveBeenCalled()

View File

@@ -1,5 +1,5 @@
import type { ChangeEvent, ChangeEventHandler, RefObject } from 'react'
import { useState, useEffect } from 'react'
import type { ChangeEvent, ChangeEventHandler, RefObject } from "react"
import { useState, useEffect } from "react"
interface ClearInputOptions {
inputRef: RefObject<HTMLInputElement | null>
@@ -26,7 +26,7 @@ export function clearInput({
if (isControlled && onChange) {
// For controlled components: call onChange with a synthetic event
const syntheticEvent = {
target: { value: '' },
target: { value: "" },
currentTarget: inputRef.current,
} as ChangeEvent<HTMLInputElement>
onChange(syntheticEvent)
@@ -35,24 +35,24 @@ export function clearInput({
// This triggers React's change detection properly
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
"value"
)?.set
if (nativeInputValueSetter) {
nativeInputValueSetter.call(inputRef.current, '')
nativeInputValueSetter.call(inputRef.current, "")
} else {
inputRef.current.value = ''
inputRef.current.value = ""
}
// Dispatch input event to trigger any listeners
const inputEvent = new Event('input', {
const inputEvent = new Event("input", {
bubbles: true,
cancelable: true,
})
inputRef.current.dispatchEvent(inputEvent)
// Also dispatch change event
const changeEvent = new Event('change', {
const changeEvent = new Event("change", {
bubbles: true,
cancelable: true,
})
@@ -74,16 +74,16 @@ export function useInputHasValue(
const [hasValue, setHasValue] = useState(() => {
// For controlled components, check value prop
if (value !== undefined) {
return String(value ?? '').trim().length > 0
return String(value ?? "").trim().length > 0
}
// For uncontrolled, check ref
return String(inputRef.current?.value ?? '').trim().length > 0
return String(inputRef.current?.value ?? "").trim().length > 0
})
// Sync with controlled value changes
useEffect(() => {
if (value !== undefined) {
setHasValue(String(value ?? '').trim().length > 0)
setHasValue(String(value ?? "").trim().length > 0)
}
}, [value])
@@ -102,17 +102,17 @@ export function useInputHasValue(
setTimeout(() => {
const target = (event?.target as HTMLInputElement) || inputRef.current
if (target) {
setHasValue((target.value ?? '').trim().length > 0)
setHasValue((target.value ?? "").trim().length > 0)
}
}, 0)
}
input.addEventListener('input', updateHasValue)
input.addEventListener("input", updateHasValue)
// Also listen to change event as a fallback
input.addEventListener('change', updateHasValue)
input.addEventListener("change", updateHasValue)
return () => {
input.removeEventListener('input', updateHasValue)
input.removeEventListener('change', updateHasValue)
input.removeEventListener("input", updateHasValue)
input.removeEventListener("change", updateHasValue)
}
}, [value, inputRef])