diff --git a/packages/components/input-otp/__tests__/input-otp.test.tsx b/packages/components/input-otp/__tests__/input-otp.test.tsx index c9a8e95f2a..fdde22f4d6 100644 --- a/packages/components/input-otp/__tests__/input-otp.test.tsx +++ b/packages/components/input-otp/__tests__/input-otp.test.tsx @@ -1,11 +1,20 @@ import * as React from "react"; -import {act, render, renderHook} from "@testing-library/react"; +import {render, renderHook, screen} from "@testing-library/react"; import {useForm} from "react-hook-form"; import userEvent, {UserEvent} from "@testing-library/user-event"; import {InputOtp} from "../src"; -describe("InputOtp", () => { +// Mock document.elementFromPoint to avoid test environment errors +beforeAll(() => { + document.elementFromPoint = jest.fn(() => { + const mockElement = document.createElement("div"); + + return mockElement; + }); +}); + +describe("InputOtp Component", () => { let user: UserEvent; beforeAll(() => { @@ -18,228 +27,131 @@ describe("InputOtp", () => { expect(() => wrapper.unmount()).not.toThrow(); }); - it("ref should be forwarded", () => { + it("should forward ref correctly", () => { const ref = React.createRef(); render(); expect(ref.current).not.toBeNull(); }); - it("should have length according to the prop", async () => { + it("should create segments according to length prop", () => { render(); - const segments = document.querySelectorAll("[data-slot=segment]"); + const segments = screen.getAllByRole("presentation"); expect(segments.length).toBe(5); }); - it("should display error message", async () => { + it("should display error message when isInvalid is true", () => { const errorMessage = "custom error message"; render(); - const base = document.querySelector("[data-slot=base]")!; - - expect(base).toHaveTextContent(errorMessage); + expect(screen.getByText(errorMessage)).toBeInTheDocument(); }); - it("should display description message", async () => { + it("should display description message", () => { const descriptionMessage = "custom description message"; render(); - const base = document.querySelector("[data-slot=base]")!; - - expect(base).toHaveTextContent(descriptionMessage); + expect(screen.getByText(descriptionMessage)).toBeInTheDocument(); }); - it("should not focus on disabled", async () => { + it("should not focus when disabled", async () => { render(); - const input = document.querySelector("[data-slot=input]")!; - - await act(async () => { - await user.click(input); - }); + const input = screen.getByRole("textbox"); + await user.click(input); expect(input).not.toHaveAttribute("data-focus", "true"); }); - it("should select first segment when clicked", async () => { + it("should activate the first segment on click", async () => { render(); - const base = document.querySelector("[data-slot=base]")!; - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - await act(async () => { - await user.click(input); - }); - - expect(base).toHaveAttribute("data-focus", "true"); - expect(input).toHaveAttribute("data-focus", "true"); + const input = screen.getByRole("textbox"); + const segments = screen.getAllByRole("presentation"); + await user.click(input); expect(segments[0]).toHaveAttribute("data-active", "true"); - expect(segments[1].getAttribute("data-active")).toBe(null); - expect(segments[2].getAttribute("data-active")).toBe(null); - expect(segments[3].getAttribute("data-active")).toBe(null); - }); - - it("should not be focused when disabled", async () => { - render(); - const input = document.querySelector("[data-slot=input]")!; - - await act(async () => { - await user.click(input); - }); - - expect(input).toBeDisabled(); + expect(segments[1]).not.toHaveAttribute("data-active"); }); - it("should shift focus to next segment when valid digit is typed", async () => { + it("should move focus to the next segment on valid input", async () => { render(); + const input = screen.getByRole("textbox"); + const segments = screen.getAllByRole("presentation"); - const base = document.querySelector("[data-slot=base]")!; - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - await act(async () => { - await user.click(input); - }); - - expect(base).toHaveAttribute("data-focus", "true"); - expect(input).toHaveAttribute("data-focus", "true"); - // since no input is entered hence segment[1] will not be active - expect(segments[1].getAttribute("data-active")).toBe(null); - - await act(async () => { - await user.keyboard("1"); - }); + await user.click(input); + expect(segments[1]).not.toHaveAttribute("data-active"); - // after the keypress, the focus should shift to segment[1] + await user.keyboard("1"); expect(segments[1]).toHaveAttribute("data-active", "true"); expect(input).toHaveAttribute("value", "1"); }); - it("should be able to erase the input", async () => { + it("should clear input on backspace", async () => { render(); + const input = screen.getByRole("textbox"); + const segments = screen.getAllByRole("presentation"); - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - // clicking on the component and typing in "12" - await act(async () => { - await user.click(input); - await user.keyboard("1"); - await user.keyboard("2"); - }); - - // value should be "12" and segement[2] should be active + await user.click(input); + await user.keyboard("12"); expect(input).toHaveAttribute("value", "12"); expect(segments[2]).toHaveAttribute("data-active", "true"); - // removing the data by pressing backspace - await act(async () => { - await user.keyboard("[BackSpace]"); - }); - - // after one Backspace keypress, the value should be "1" and segment[1] should be active + await user.keyboard("[Backspace]"); expect(input).toHaveAttribute("value", "1"); expect(segments[1]).toHaveAttribute("data-active", "true"); }); - it("should be able to paste value", async () => { + it("should paste values", async () => { render(); + const input = screen.getByRole("textbox"); - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - // clicking on the component and pasting in "1234" - await act(async () => { - await user.click(input); - await user.paste("1234"); - }); - - // value should be "1234" + await user.click(input); + await user.paste("1234"); expect(input).toHaveAttribute("value", "1234"); }); - it("should not take non-allowed inputs", async () => { + it("should restrict non-allowed inputs", async () => { render(); + const input = screen.getByRole("textbox"); + const segments = screen.getAllByRole("presentation"); - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - // clicking on the component and typing the unallowed letter (here, "a") - await act(async () => { - await user.click(input); - await user.keyboard("a"); - }); - - // since unallowed letter was typed, "value" should remain empty and segment[0] remains active - expect(segments[0]).toHaveAttribute("data-active", "true"); + await user.click(input); + await user.keyboard("a"); expect(input).toHaveAttribute("value", ""); + expect(segments[0]).toHaveAttribute("data-active", "true"); }); it("should allow inputs based on custom regex", async () => { - // below exp matches with chars from small "a" to small "z" const regEx = "^[a-z]*$"; render(); + const input = screen.getByRole("textbox"); - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); - - expect(segments.length).toBe(4); - - // clicking on the component and typing the "a" letter - await act(async () => { - await user.click(input); - await user.keyboard("a"); - }); - - expect(segments[1]).toHaveAttribute("data-active", "true"); + await user.click(input); + await user.keyboard("a"); expect(input).toHaveAttribute("value", "a"); }); - it("should call onFill callback when inputOtp is completely filled", async () => { - const onFill = jest.fn(); - - render(); - - const input = document.querySelector("[data-slot=input]")!; - const segments = document.querySelectorAll("[data-slot=segment]"); + it("should call onComplete when all segments are filled", async () => { + const onComplete = jest.fn(); - expect(segments.length).toBe(4); + render(); + const input = screen.getByRole("textbox"); - // clicking on the component and pasting "1234" - await act(async () => { - await user.click(input); - await user.paste("1234"); - }); - - expect(onFill).toHaveBeenCalledTimes(1); + await user.click(input); + await user.paste("1234"); + expect(onComplete).toHaveBeenCalledTimes(1); }); }); -describe("InputOtp with react hook form", () => { - let inputOtp1: Element; - let inputOtp2: Element; - let inputOtp3: Element; - let submitButton: HTMLButtonElement; - let onSubmit: () => void; +describe("InputOtp with react-hook-form", () => { let user: UserEvent; beforeAll(() => { user = userEvent.setup(); }); - beforeEach(() => { + it("should integrate with react-hook-form correctly", async () => { const {result} = renderHook(() => useForm({ defaultValues: { @@ -255,42 +167,29 @@ describe("InputOtp with react hook form", () => { register, formState: {errors}, } = result.current; - - onSubmit = jest.fn(); + const onSubmit = jest.fn(); render( -
- - - - {errors.requiredField && This field is required} + + + + + {errors.requiredField && This field is required} , ); - inputOtp1 = document.querySelectorAll("[data-slot=input]")[0]!; - inputOtp2 = document.querySelectorAll("[data-slot=input]")[1]!; - inputOtp3 = document.querySelectorAll("[data-slot=input]")[2]!; - submitButton = document.querySelector("button")!; - }); - - it("should work with defaultValues", () => { - expect(inputOtp1).toHaveValue("1234"); - expect(inputOtp2).toHaveValue(""); - expect(inputOtp3).toHaveValue(""); - }); - - it("should not submit form when required field is empty", async () => { - await user.click(submitButton); - + await user.click(screen.getByText(/Submit/i)); expect(onSubmit).toHaveBeenCalledTimes(0); - }); - it("should submit form when required field is not empty", async () => { - await user.type(inputOtp3, "1234"); - - await user.click(submitButton); + const inputOtp3 = screen.getAllByRole("textbox")[2]; + await user.type(inputOtp3, "1234"); + await user.click(screen.getByText(/Submit/i)); expect(onSubmit).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/components/input-otp/package.json b/packages/components/input-otp/package.json index 6db6335717..e433dd8227 100644 --- a/packages/components/input-otp/package.json +++ b/packages/components/input-otp/package.json @@ -46,8 +46,10 @@ "@react-aria/focus": "3.17.1", "@react-aria/utils": "3.24.1", "@react-stately/utils": "3.10.1", + "@react-stately/form": "3.0.5", "@react-types/textfield": "3.9.3", - "@react-aria/textfield": "3.14.5" + "@react-aria/textfield": "3.14.5", + "input-otp": "1.4.1" }, "devDependencies": { "@nextui-org/theme": "workspace:*", diff --git a/packages/components/input-otp/src/input-otp-segment.tsx b/packages/components/input-otp/src/input-otp-segment.tsx index 2beda82545..9a106f63b4 100644 --- a/packages/components/input-otp/src/input-otp-segment.tsx +++ b/packages/components/input-otp/src/input-otp-segment.tsx @@ -1,50 +1,38 @@ -import {clsx, dataAttr} from "@nextui-org/shared-utils"; -import {HTMLNextUIProps} from "@nextui-org/system"; +import {SlotProps} from "input-otp"; import {useMemo} from "react"; +import {clsx, dataAttr} from "@nextui-org/shared-utils"; import {useInputOtpContext} from "./input-otp-context"; -interface InputOtpSegmentProps extends HTMLNextUIProps<"div"> { - accessorIndex: number; -} +export const InputOtpSegment = (props: SlotProps) => { + const {classNames, slots, type} = useInputOtpContext(); -export const InputOtpSegment = ({accessorIndex}: InputOtpSegmentProps) => { - const {length, value, isInputFocused, classNames, slots, type} = useInputOtpContext(); - - const isActive = useMemo( - () => - (value.length == accessorIndex || (value.length == length && accessorIndex == length - 1)) && - isInputFocused, - [value, isInputFocused], - ); - const hasValue = useMemo(() => value.length > accessorIndex, [value, accessorIndex]); - - const segmentStyles = clsx(classNames?.segment); - const caretStyles = clsx(classNames?.caret); const passwordCharStyles = clsx(classNames?.passwordChar); + const caretStyles = clsx(classNames?.caret); + const segmentStyles = clsx(classNames?.segment); const displayValue = useMemo(() => { - if (hasValue && type == "password") { - return
; - } - - if (hasValue) { - return value[accessorIndex]; - } - - if (isActive) { + if (props.isActive && !props.char) { return
; } + if (props.char) { + return type === "password" ? ( +
+ ) : ( +
{props.char}
+ ); + } - return null; - }, [type, hasValue, value, isActive]); + return
{props.placeholderChar}
; + }, [props.char, props.isActive, type]); return (
{displayValue}
diff --git a/packages/components/input-otp/src/input-otp.tsx b/packages/components/input-otp/src/input-otp.tsx index e92f5a401f..2148534ae9 100644 --- a/packages/components/input-otp/src/input-otp.tsx +++ b/packages/components/input-otp/src/input-otp.tsx @@ -1,9 +1,11 @@ import {forwardRef} from "@nextui-org/system"; import {useMemo} from "react"; +import {OTPInput} from "input-otp"; +import {clsx} from "@nextui-org/shared-utils"; import {UseInputOtpProps, useInputOtp} from "./use-input-otp"; -import {InputOtpSegment} from "./input-otp-segment"; import {InputOtpProvider} from "./input-otp-context"; +import {InputOtpSegment} from "./input-otp-segment"; export interface InputOtpProps extends UseInputOtpProps {} @@ -17,33 +19,16 @@ const InputOtp = forwardRef<"div", InputOtpProps>((props, ref) => { isInvalid, errorMessage, description, + slots, + classNames, getBaseProps, - getInputWrapperProps, - getInputProps, + getInputOtpProps, getSegmentWrapperProps, getHelperWrapperProps, getErrorMessageProps, getDescriptionProps, } = context; - const segmentsSection = useMemo(() => { - return ( -
- {Array.from(Array(length)).map((_, idx) => ( - - ))} -
- ); - }, [length, getSegmentWrapperProps]); - - const inputSection = useMemo(() => { - return ( -
- -
- ); - }, [getInputWrapperProps, getInputProps]); - const helperSection = useMemo(() => { if (!hasHelper) { return null; @@ -68,14 +53,27 @@ const InputOtp = forwardRef<"div", InputOtpProps>((props, ref) => { getDescriptionProps, ]); + const wrapperStyles = clsx(classNames?.wrapper); + return ( -
- {segmentsSection} - {inputSection} - {helperSection} -
+ ( +
+ {slots.map((slot, idx) => ( + + ))} +
+ )} + {...getInputOtpProps()} + data-slot="input" + role="textbox" + /> + {helperSection}
); diff --git a/packages/components/input-otp/src/use-input-otp.ts b/packages/components/input-otp/src/use-input-otp.ts index 3c74c2872b..90af74f3c9 100644 --- a/packages/components/input-otp/src/use-input-otp.ts +++ b/packages/components/input-otp/src/use-input-otp.ts @@ -12,16 +12,13 @@ import { useProviderContext, } from "@nextui-org/system"; import {inputOtp} from "@nextui-org/theme"; -import {filterDOMProps, ReactRef, useDOMRef} from "@nextui-org/react-utils"; -import {clsx, dataAttr, isEmpty, objectToDeps, safeAriaLabel} from "@nextui-org/shared-utils"; +import {ReactRef, useDOMRef} from "@nextui-org/react-utils"; +import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {useCallback, useMemo} from "react"; -import {useFocusRing} from "@react-aria/focus"; -import {mergeProps} from "@react-aria/utils"; -import {useHover} from "@react-aria/interactions"; +import {chain, mergeProps} from "@react-aria/utils"; import {AriaTextFieldProps} from "@react-types/textfield"; -import {AriaTextFieldOptions, useTextField} from "@react-aria/textfield"; import {useControlledState} from "@react-stately/utils"; -import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; +import {useFormValidationState} from "@react-stately/form"; interface Props extends HTMLNextUIProps<"div"> { /** @@ -43,7 +40,7 @@ interface Props extends HTMLNextUIProps<"div"> { /** * Callback that will be fired when the value has length equal to otp length */ - onFill?: (v?: string) => void; + onComplete?: (v?: string) => void; /** * Boolean to disable the input-otp component. */ @@ -95,11 +92,12 @@ export function useInputOtp(originalProps: UseInputOtpProps) { className, classNames, length = 4, - onFill = () => {}, + onComplete = () => {}, onValueChange = () => {}, allowedKeys = "^[0-9]*$", validationBehavior = globalContext?.validationBehavior ?? "aria", type, + name, ...otherProps } = props; @@ -111,11 +109,8 @@ export function useInputOtp(originalProps: UseInputOtpProps) { const handleValueChange = useCallback( (value: string | undefined) => { onValueChange(value ?? ""); - if (value && value?.length === length) { - onFill(value); - } }, - [onValueChange, onFill, length], + [onValueChange], ); const [value, setValue] = useControlledState( @@ -126,70 +121,27 @@ export function useInputOtp(originalProps: UseInputOtpProps) { const disableAnimation = originalProps.disableAnimation ?? globalContext?.disableAnimation ?? false; - const isDisabled = originalProps.isDisabled ?? false; + const isDisabled = originalProps.isDisabled; const baseStyles = clsx(classNames?.base, className); - const {focusProps, isFocused: isInputFocused} = useFocusRing({isTextInput: true}); - const allowedKeysRegex = new RegExp(allowedKeys); - const isFilled = !isEmpty(value); - const {isHovered, hoverProps} = useHover({isDisabled: !!originalProps?.isDisabled}); - - const onKeyDownCapture = (e: React.KeyboardEvent) => { - const key = e.key; - - if (key === "Backspace") { - return; - } - if (key === "ArrowLeft" || key === "ArrowRight") { - e.stopPropagation(); - e.preventDefault(); - - return; - } - if (!allowedKeysRegex.test(key)) { - e.stopPropagation(); - e.preventDefault(); - - return; - } - - return; - }; - - type AutoCapitalize = AriaTextFieldOptions<"input">["autoCapitalize"]; - const { - inputProps, - isInvalid: isAriaInvalid, + isInvalid: isValidationInvalid, validationErrors, validationDetails, - descriptionProps, - errorMessageProps, - } = useTextField( - { - ...originalProps, - validationBehavior, - autoCapitalize: originalProps.autoCapitalize as AutoCapitalize, - value: value, - "aria-label": safeAriaLabel( - originalProps["aria-label"], - originalProps.label, - originalProps.placeholder, - ), - inputElementType: "input", - onChange: setValue, - minLength: length, - maxLength: length, - }, - inputRef, - ); - - const isReadOnly = originalProps.isReadOnly ?? false; - const isInvalid = originalProps.isInvalid || isAriaInvalid; + } = useFormValidationState({ + ...props, + validationBehavior, + value: value, + }).displayValidation; + + const isReadOnly = originalProps.isReadOnly; + const isRequired = originalProps.isRequired; + const isInvalid = originalProps.isInvalid || isValidationInvalid; const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({isInvalid, validationErrors, validationDetails}) : props.errorMessage || validationErrors?.join(" "); + const description = props.description; const hasHelper = !!description || !!errorMessage; @@ -204,12 +156,6 @@ export function useInputOtp(originalProps: UseInputOtpProps) { [objectToDeps(variantProps), disableAnimation, isInvalid], ); - useSafeLayoutEffect(() => { - if (!inputRef.current) return; - - setValue(inputRef.current.value); - }, [inputRef.current]); - const getBaseProps: PropGetter = useCallback( (props = {}) => { return { @@ -217,19 +163,45 @@ export function useInputOtp(originalProps: UseInputOtpProps) { className: slots.base({ class: baseStyles, }), - onKeyDownCapture: onKeyDownCapture, "data-slot": "base", - "data-filled": dataAttr(isFilled), - "data-focus": dataAttr(isInputFocused), - "data-hover": dataAttr(isHovered), "data-disabled": dataAttr(isDisabled), "data-invalid": dataAttr(isInvalid), "data-required": dataAttr(originalProps?.isRequired), "data-readonly": dataAttr(originalProps?.isReadOnly), + role: "base", ...props, }; }, - [baseDomRef, slots, baseStyles, isFilled, isInputFocused, isDisabled], + [baseDomRef, slots, baseStyles, isDisabled], + ); + + const getInputOtpProps = useCallback( + () => ({ + required: isRequired, + disabled: isDisabled, + readOnly: isReadOnly, + pattern: allowedKeys, + ref: inputRef, + name: name, + min: length, + max: length, + onChange: chain(setValue), + onBlur: props.onBlur, + onComplete: onComplete, + }), + [ + isRequired, + isDisabled, + isReadOnly, + allowedKeys, + inputRef, + name, + length, + props.onChange, + setValue, + props.onBlur, + onComplete, + ], ); const getInputWrapperProps: PropGetter = useCallback( @@ -245,47 +217,6 @@ export function useInputOtp(originalProps: UseInputOtpProps) { [slots, classNames?.inputWrapper], ); - const getInputProps: PropGetter = useCallback( - (props = {}) => { - return { - ref: inputRef, - className: slots.input({ - class: clsx(classNames?.input, props?.className), - }), - maxLength: length, - minLength: length, - value, - disabled: isDisabled, - ...mergeProps( - focusProps, - hoverProps, - inputProps, - filterDOMProps(otherProps, { - enabled: true, - omitEventNames: new Set(Object.keys(inputProps)), - }), - props, - ), - placeholder: "", - "data-slot": "input", - "data-focus": dataAttr(isInputFocused), - "data-filled": dataAttr(isFilled), - "data-disabled": dataAttr(isDisabled), - }; - }, - [ - inputRef, - slots, - classNames?.input, - length, - value, - isDisabled, - setValue, - isInputFocused, - isFilled, - ], - ); - const getSegmentWrapperProps: PropGetter = useCallback( (props = {}) => { return { @@ -320,7 +251,7 @@ export function useInputOtp(originalProps: UseInputOtpProps) { class: clsx(classNames?.errorMessage, props?.className), }), "data-slot": "error-message", - ...mergeProps(errorMessageProps, props), + ...mergeProps(props), }; }, [slots, classNames?.errorMessage], @@ -333,7 +264,7 @@ export function useInputOtp(originalProps: UseInputOtpProps) { class: clsx(classNames?.description, props?.className), }), "data-slot": "description", - ...mergeProps(descriptionProps, props), + ...mergeProps(props), }; }, [slots, classNames?.description], @@ -344,7 +275,6 @@ export function useInputOtp(originalProps: UseInputOtpProps) { inputRef, length, value, - isInputFocused, classNames, slots, hasHelper, @@ -353,8 +283,8 @@ export function useInputOtp(originalProps: UseInputOtpProps) { errorMessage, type, getBaseProps, + getInputOtpProps, getInputWrapperProps, - getInputProps, getSegmentWrapperProps, getHelperWrapperProps, getErrorMessageProps, diff --git a/packages/core/theme/src/components/input-otp.ts b/packages/core/theme/src/components/input-otp.ts index e448f77c05..d2081f34cf 100644 --- a/packages/core/theme/src/components/input-otp.ts +++ b/packages/core/theme/src/components/input-otp.ts @@ -6,6 +6,7 @@ const inputOtp = tv({ slots: { base: ["relative", "flex", "flex-col", "w-fit"], inputWrapper: [], + wrapper: ["group", "flex items-center", "has-[:disabled]:opacity-30"], input: [ "absolute", "inset-0", @@ -38,11 +39,11 @@ const inputOtp = tv({ "text-2xl", "h-[50%]", "w-px", - "bg-white", + "bg-default-800", ], helperWrapper: ["text-xs", "mt-0.5", "font-extralight", ""], - errorMessage: ["text-tiny text-danger w-full"], - description: ["text-tiny text-foreground-400"], + errorMessage: ["text-xs text-danger w-full"], + description: ["text-xs text-foreground-400"], }, variants: { variant: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0846ce9eb..6ddc8a65d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1678,19 +1678,25 @@ importers: version: link:../../hooks/use-safe-layout-effect '@react-aria/focus': specifier: 3.17.1 - version: 3.17.1(react@18.2.0) + version: 3.17.1(react@18.3.1) '@react-aria/textfield': specifier: 3.14.5 - version: 3.14.5(react@18.2.0) + version: 3.14.5(react@18.3.1) '@react-aria/utils': specifier: 3.24.1 - version: 3.24.1(react@18.2.0) + version: 3.24.1(react@18.3.1) + '@react-stately/form': + specifier: 3.0.5 + version: 3.0.5(react@18.3.1) '@react-stately/utils': specifier: 3.10.1 - version: 3.10.1(react@18.2.0) + version: 3.10.1(react@18.3.1) '@react-types/textfield': specifier: 3.9.3 - version: 3.9.3(react@18.2.0) + version: 3.9.3(react@18.3.1) + input-otp: + specifier: 1.4.1 + version: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@nextui-org/system': specifier: workspace:* @@ -1703,13 +1709,13 @@ importers: version: 2.2.0 react: specifier: ^18.2.0 - version: 18.2.0 + version: 18.3.1 react-dom: specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) + version: 18.3.1(react@18.3.1) react-hook-form: specifier: ^7.51.3 - version: 7.51.3(react@18.2.0) + version: 7.53.1(react@18.3.1) packages/components/kbd: dependencies: @@ -6795,6 +6801,11 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 + '@react-aria/textfield@3.14.5': + resolution: {integrity: sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA==} + peerDependencies: + react: ^18.2.0 + '@react-aria/textfield@3.14.8': resolution: {integrity: sha512-FHEvsHdE1cMR2B7rlf+HIneITrC40r201oLYbHAp3q26jH/HUujzFBB9I20qhXjyBohMWfQLqJhSwhs1VW1RJQ==} peerDependencies: @@ -7157,6 +7168,11 @@ packages: peerDependencies: react: ^18.2.0 + '@react-types/textfield@3.9.3': + resolution: {integrity: sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw==} + peerDependencies: + react: ^18.2.0 + '@react-types/textfield@3.9.6': resolution: {integrity: sha512-0uPqjJh4lYp1aL1HL9IlV8Cgp8eT0PcsNfdoCktfkLytvvBPmox2Pfm57W/d0xTtzZu2CjxhYNTob+JtGAOeXA==} peerDependencies: @@ -11106,6 +11122,12 @@ packages: inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + input-otp@1.4.1: + resolution: {integrity: sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + inquirer@6.5.2: resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==} engines: {node: '>=6.0.0'} @@ -18287,15 +18309,13 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)': + '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.13) '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/logger': 2.12.0 '@parcel/utils': 2.12.0 lmdb: 2.8.5 - transitivePeerDependencies: - - '@swc/helpers' '@parcel/codeframe@2.12.0': dependencies: @@ -18355,7 +18375,7 @@ snapshots: '@parcel/core@2.12.0(@swc/helpers@0.5.13)': dependencies: '@mischnic/json-sourcemap': 0.1.1 - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) @@ -18770,7 +18790,7 @@ snapshots: '@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)': dependencies: - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13)) '@parcel/diagnostic': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.13))(@swc/helpers@0.5.13) @@ -19861,6 +19881,19 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@react-aria/textfield@3.14.5(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.18.2(react@18.3.1) + '@react-aria/form': 3.0.8(react@18.3.1) + '@react-aria/label': 3.7.11(react@18.3.1) + '@react-aria/utils': 3.25.2(react@18.3.1) + '@react-stately/form': 3.0.5(react@18.3.1) + '@react-stately/utils': 3.10.3(react@18.3.1) + '@react-types/shared': 3.24.1(react@18.3.1) + '@react-types/textfield': 3.9.6(react@18.3.1) + '@swc/helpers': 0.5.13 + react: 18.3.1 + '@react-aria/textfield@3.14.8(react@18.3.1)': dependencies: '@react-aria/focus': 3.18.2(react@18.3.1) @@ -20386,6 +20419,11 @@ snapshots: '@react-types/shared': 3.24.1(react@18.3.1) react: 18.3.1 + '@react-types/textfield@3.9.3(react@18.3.1)': + dependencies: + '@react-types/shared': 3.24.1(react@18.3.1) + react: 18.3.1 + '@react-types/textfield@3.9.6(react@18.3.1)': dependencies: '@react-types/shared': 3.24.1(react@18.3.1) @@ -23885,7 +23923,7 @@ snapshots: eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@7.32.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@7.32.0) eslint-plugin-react: 7.37.2(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.2(eslint@7.32.0) @@ -23955,7 +23993,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -24006,35 +24044,6 @@ snapshots: lodash: 4.17.21 string-natural-compare: 3.0.1 - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 7.32.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@7.32.0))(eslint@7.32.0) - hasown: 2.0.2 - is-core-module: 2.15.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - string.prototype.trimend: 1.0.8 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0): dependencies: '@rtsao/scc': 1.1.0 @@ -25403,6 +25412,11 @@ snapshots: inline-style-parser@0.2.4: {} + input-otp@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + inquirer@6.5.2: dependencies: ansi-escapes: 3.2.0