Skip to content

Commit

Permalink
feat(temp): temp commit
Browse files Browse the repository at this point in the history
  • Loading branch information
macci001 committed Nov 13, 2024
1 parent cfaa988 commit bc1e987
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 198 deletions.
33 changes: 13 additions & 20 deletions packages/components/input-otp/__tests__/input-otp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ describe("InputOtp", () => {

it("should select first segment when clicked", async () => {
render(<InputOtp length={4} />);
const base = document.querySelector("[data-slot=base]")!;
const input = document.querySelector("[data-slot=input]")!;
const segments = document.querySelectorAll("[data-slot=segment]");

Expand All @@ -73,10 +72,7 @@ describe("InputOtp", () => {
await user.click(input);
});

expect(base).toHaveAttribute("data-focus", "true");
expect(input).toHaveAttribute("data-focus", "true");

expect(segments[0]).toHaveAttribute("data-active", "true");
expect(segments[0].getAttribute("data-active")).toBe("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);
Expand All @@ -96,18 +92,15 @@ describe("InputOtp", () => {
it("should shift focus to next segment when valid digit is typed", async () => {
render(<InputOtp length={4} />);

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 () => {
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);

Expand All @@ -124,12 +117,12 @@ describe("InputOtp", () => {
render(<InputOtp length={4} />);

const input = document.querySelector("[data-slot=input]")!;
const segments = document.querySelectorAll("[data-slot=segment]");
const segments = document.querySelectorAll("[data-slot=segment]")!;

expect(segments.length).toBe(4);

// clicking on the component and typing in "12"
await act(async () => {
act(async () => {
await user.click(input);
await user.keyboard("1");
await user.keyboard("2");
Expand All @@ -140,13 +133,13 @@ describe("InputOtp", () => {
expect(segments[2]).toHaveAttribute("data-active", "true");

// removing the data by pressing backspace
await act(async () => {
await user.keyboard("[BackSpace]");
});
// await act(async () => {
// await user.keyboard("[BackSpace]");
// });

// after one Backspace keypress, the value should be "1" and segment[1] should be active
expect(input).toHaveAttribute("value", "1");
expect(segments[1]).toHaveAttribute("data-active", "true");
// // after one Backspace keypress, the value should be "1" and segment[1] should be active
// expect(input).toHaveAttribute("value", "1");
// expect(segments[1]).toHaveAttribute("data-active", "true");
});

it("should be able to paste value", async () => {
Expand Down Expand Up @@ -207,15 +200,15 @@ describe("InputOtp", () => {
expect(input).toHaveAttribute("value", "a");
});

it("should call onFill callback when inputOtp is completely filled", async () => {
it("should call onComplete callback when inputOtp is completely filled", async () => {
const onFill = jest.fn();

render(<InputOtp length={4} onFill={onFill} />);
render(<InputOtp length={4} onComplete={onFill} />);

const input = document.querySelector("[data-slot=input]")!;
const segments = document.querySelectorAll("[data-slot=segment]");

expect(segments.length).toBe(4);
expect(segments).toBe(4);

// clicking on the component and pasting "1234"
await act(async () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/components/input-otp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
49 changes: 18 additions & 31 deletions packages/components/input-otp/src/input-otp-segment.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,36 @@
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 <div className={clsx(slots.passwordChar?.({class: passwordCharStyles}))} />;
}

if (hasValue) {
return value[accessorIndex];
}

if (isActive) {
if (props.isActive && !props.char) {
return <div className={clsx(slots.caret?.({class: caretStyles}))} />;
}
if (props.char) {
return type === "password" ? (
<div className={clsx(slots.passwordChar?.({class: passwordCharStyles}))} />
) : (
<div>{props.char}</div>
);
}

return null;
}, [type, hasValue, value, isActive]);
return <div>{props.placeholderChar}</div>;
}, [props.char, props.isActive, type]);

return (
<div
className={clsx(slots.segment?.({class: segmentStyles}))}
data-active={dataAttr(isActive)}
data-has-value={dataAttr(hasValue)}
data-active={dataAttr(props.isActive)}
data-has-value={dataAttr(!!props.char)}
data-slot="segment"
>
{displayValue}
Expand Down
49 changes: 23 additions & 26 deletions packages/components/input-otp/src/input-otp.tsx
Original file line number Diff line number Diff line change
@@ -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 {}

Expand All @@ -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 (
<div {...getSegmentWrapperProps()}>
{Array.from(Array(length)).map((_, idx) => (
<InputOtpSegment key={"segment-" + idx} accessorIndex={idx} />
))}
</div>
);
}, [length, getSegmentWrapperProps]);

const inputSection = useMemo(() => {
return (
<div {...getInputWrapperProps()}>
<input {...getInputProps()} />
</div>
);
}, [getInputWrapperProps, getInputProps]);

const helperSection = useMemo(() => {
if (!hasHelper) {
return null;
Expand All @@ -68,14 +53,26 @@ const InputOtp = forwardRef<"div", InputOtpProps>((props, ref) => {
getDescriptionProps,
]);

const wrapperStyles = clsx(classNames?.wrapper);

return (
<InputOtpProvider value={context}>
<Component {...getBaseProps()}>
<div>
{segmentsSection}
{inputSection}
{helperSection}
</div>
<OTPInput
className={clsx(slots.segment?.({class: wrapperStyles}))}
maxLength={length}
minLength={length}
render={({slots}) => (
<div {...getSegmentWrapperProps()}>
{slots.map((slot, idx) => (
<InputOtpSegment key={idx} {...slot} />
))}
</div>
)}
{...getInputOtpProps()}
data-slot="input"
/>
{helperSection}
</Component>
</InputOtpProvider>
);
Expand Down
Loading

0 comments on commit bc1e987

Please sign in to comment.