feat: add PasswordInput component with visibility toggle
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { HourglassSimpleMediumIcon } from "@phosphor-icons/react";
|
||||
import { useAuth } from "../../hooks/useAuth";
|
||||
import { Modal } from "../ui/Modal";
|
||||
import { PasswordInput } from "../ui/PasswordInput";
|
||||
|
||||
export function PasskeyModal() {
|
||||
const { unlock } = useAuth();
|
||||
@@ -27,7 +28,7 @@ export function PasskeyModal() {
|
||||
</p>
|
||||
<div className="modal-action items-center gap-4">
|
||||
<form
|
||||
className="form-control w-full inline-flex"
|
||||
className="form-control w-full"
|
||||
onSubmit={async (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
@@ -36,22 +37,23 @@ export function PasskeyModal() {
|
||||
await unlock(password);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
placeholder="password"
|
||||
data-testid="passkey-input"
|
||||
className="font-sans validator input input-bordered rounded-r-none"
|
||||
/>
|
||||
<div className="validator-message text-xs text-error"></div>
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="passkey-submit-btn"
|
||||
className="btn btn-primary rounded-l-none"
|
||||
>
|
||||
Unlock
|
||||
</button>
|
||||
<div className="join w-full">
|
||||
<PasswordInput
|
||||
name="password"
|
||||
required
|
||||
placeholder="password"
|
||||
data-testid="passkey-input"
|
||||
className="rounded-r-none w-full"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="passkey-submit-btn"
|
||||
className="btn btn-primary rounded-l-none"
|
||||
>
|
||||
Unlock
|
||||
</button>
|
||||
</div>
|
||||
<div className="validator-message text-xs text-error mt-2"></div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UseFormRegisterReturn } from "react-hook-form";
|
||||
import { PasswordInput } from "./PasswordInput";
|
||||
|
||||
interface FormFieldProps {
|
||||
label: string;
|
||||
@@ -20,25 +21,36 @@ export default function FormField({
|
||||
"data-testid": testId,
|
||||
}: FormFieldProps) {
|
||||
return (
|
||||
<div className="form-control">
|
||||
<div className="form-control w-full">
|
||||
<label
|
||||
htmlFor={registration.name}
|
||||
className="field-label font-display text-neutral-content/80 font-medium"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
{...registration}
|
||||
id={registration.name}
|
||||
data-testid={testId}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
className={`input input-bordered focus:input-primary ${
|
||||
error ? "input-error" : ""
|
||||
}`}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
{error && <p className="text-error">{error}</p>}
|
||||
{type === "password" ? (
|
||||
<PasswordInput
|
||||
{...registration}
|
||||
id={registration.name}
|
||||
data-testid={testId}
|
||||
placeholder={placeholder}
|
||||
error={!!error}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
{...registration}
|
||||
id={registration.name}
|
||||
data-testid={testId}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
className={`input input-bordered focus:input-primary w-full ${
|
||||
error ? "input-error" : ""
|
||||
}`}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
)}
|
||||
{error && <p className="mt-1 text-xs text-error font-medium">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { EyeIcon, EyeSlashIcon } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface PasswordInputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
export function PasswordInput({
|
||||
className,
|
||||
error,
|
||||
...props
|
||||
}: PasswordInputProps) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<input
|
||||
{...props}
|
||||
type={showPassword ? "text" : "password"}
|
||||
className={`input input-bordered focus:input-primary w-full pr-12 ${error ? "input-error" : ""
|
||||
} ${className}`}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-neutral-content/40 hover:text-primary transition-all duration-300 cursor-pointer"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeSlashIcon size={22} weight="duotone" />
|
||||
) : (
|
||||
<EyeIcon size={22} weight="duotone" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+959
-963
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user