| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- "use client";
- /**
- * Password field with show/hide eye control.
- * Use this for every password input in the portal.
- */
- import { useState, type ReactNode } from "react";
- import { Input } from "@/components/ui/input";
- import { cn } from "@/lib/utils";
- export function PasswordInput({
- id,
- label,
- value,
- onChange,
- placeholder,
- autoComplete = "current-password",
- disabled,
- className,
- inputClassName,
- onEnter,
- hideLabel,
- }: {
- id: string;
- /** Visible label; use empty + hideLabel for icon-only contexts */
- label?: string;
- value: string;
- onChange: (value: string) => void;
- placeholder?: string;
- autoComplete?: string;
- disabled?: boolean;
- className?: string;
- inputClassName?: string;
- onEnter?: () => void;
- hideLabel?: boolean;
- }) {
- const [show, setShow] = useState(false);
- const accessibleName = label || placeholder || "Password";
- return (
- <div className={cn("space-y-2", className)}>
- {label && !hideLabel ? (
- <label htmlFor={id} className="text-sm font-medium">
- {label}
- </label>
- ) : (
- <label htmlFor={id} className="sr-only">
- {accessibleName}
- </label>
- )}
- <div className="relative">
- <Input
- id={id}
- type={show ? "text" : "password"}
- autoComplete={autoComplete}
- value={value}
- disabled={disabled}
- onChange={(e) => onChange(e.target.value)}
- placeholder={placeholder}
- className={cn("pr-11", inputClassName)}
- onKeyDown={(e) => {
- if (e.key === "Enter") onEnter?.();
- }}
- />
- <button
- type="button"
- disabled={disabled}
- onClick={() => setShow((v) => !v)}
- className="absolute inset-y-0 right-0 flex min-w-11 items-center justify-center rounded-r-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-inset disabled:pointer-events-none disabled:opacity-50"
- aria-label={show ? `Hide ${accessibleName}` : `Show ${accessibleName}`}
- aria-pressed={show}
- title={show ? "Hide password" : "Show password"}
- >
- {show ? <EyeOffIcon /> : <EyeIcon />}
- </button>
- </div>
- </div>
- );
- }
- /** Alias used by mavi screens */
- export function PasswordField(
- props: Parameters<typeof PasswordInput>[0],
- ): ReactNode {
- return <PasswordInput {...props} />;
- }
- function EyeIcon() {
- return (
- <svg
- width="18"
- height="18"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- aria-hidden
- >
- <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
- <circle cx="12" cy="12" r="3" />
- </svg>
- );
- }
- function EyeOffIcon() {
- return (
- <svg
- width="18"
- height="18"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- aria-hidden
- >
- <path d="M10.7 5.1A10.8 10.8 0 0 1 12 5c6.5 0 10 7 10 7a18.5 18.5 0 0 1-2.2 3.2" />
- <path d="M6.6 6.6C3.9 8.3 2 12 2 12s3.5 7 10 7a10.4 10.4 0 0 0 5.4-1.5" />
- <path d="M14.1 14.1A3 3 0 0 1 9.9 9.9" />
- <path d="M2 2l20 20" />
- </svg>
- );
- }
|