diff --git a/src/components/admin/_shared.tsx b/src/components/admin/_shared.tsx index 59c64b96b..b4ef25354 100644 --- a/src/components/admin/_shared.tsx +++ b/src/components/admin/_shared.tsx @@ -6,7 +6,7 @@ import { toast } from "sonner"; import { Cpu, Fingerprint, Globe, KeyRound } from "lucide-react"; import { Switch } from "@/components/ui/switch"; import { useTranslations } from "@/lib/i18n/context"; -import { PasswordInput as SettingsPasswordInput } from "@/components/settings/password-input"; +import { PasswordInput as SharedPasswordInput } from "@/components/ui/password-input"; /** * Re-export the canonical password input so admin sections have access to @@ -15,7 +15,7 @@ import { PasswordInput as SettingsPasswordInput } from "@/components/settings/pa * `button-name` violation on `/admin/integrations` and * `/admin/users` (axe-core, WCAG 2.1.2). */ -export const PasswordInput = SettingsPasswordInput; +export const PasswordInput = SharedPasswordInput; export interface AdminUser { id: string; diff --git a/src/components/settings/account-section.tsx b/src/components/settings/account-section.tsx index 5a37c8b16..615d863b6 100644 --- a/src/components/settings/account-section.tsx +++ b/src/components/settings/account-section.tsx @@ -37,7 +37,7 @@ import { import { Input } from "@/components/ui/input"; import { DateInput } from "@/components/ui/date-input"; import { Label } from "@/components/ui/label"; -import { PasswordInput } from "@/components/settings/password-input"; +import { PasswordInput } from "@/components/ui/password-input"; import { useAuth } from "@/hooks/use-auth"; import { formatDate } from "@/lib/format"; import { locales, localeLabels, type Locale } from "@/lib/i18n/config"; diff --git a/src/components/settings/ai-section.tsx b/src/components/settings/ai-section.tsx index 088f11b51..5dc57d6b9 100644 --- a/src/components/settings/ai-section.tsx +++ b/src/components/settings/ai-section.tsx @@ -32,7 +32,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; -import { PasswordInput } from "@/components/settings/password-input"; +import { PasswordInput } from "@/components/ui/password-input"; import { useAuth } from "@/hooks/use-auth"; import { formatDateTime } from "@/lib/format"; import { useTranslations } from "@/lib/i18n/context"; diff --git a/src/components/settings/integrations-section.tsx b/src/components/settings/integrations-section.tsx index 83d63bf07..79a0f9c44 100644 --- a/src/components/settings/integrations-section.tsx +++ b/src/components/settings/integrations-section.tsx @@ -27,7 +27,7 @@ import { import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { PasswordInput } from "@/components/settings/password-input"; +import { PasswordInput } from "@/components/ui/password-input"; import { TestConnectionButton } from "@/components/settings/test-connection-button"; import { IntegrationStatusPill } from "@/components/settings/integration-status-pill"; import type { IntegrationPillState } from "@/components/settings/integration-status-pill"; diff --git a/src/components/settings/ntfy-card.tsx b/src/components/settings/ntfy-card.tsx index 5320d8fb8..afa9221bc 100644 --- a/src/components/settings/ntfy-card.tsx +++ b/src/components/settings/ntfy-card.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; -import { PasswordInput } from "@/components/settings/password-input"; +import { PasswordInput } from "@/components/ui/password-input"; import { useTranslations } from "@/lib/i18n/context"; interface NtfySettings { diff --git a/src/components/settings/password-input.tsx b/src/components/settings/password-input.tsx deleted file mode 100644 index 0c44a1040..000000000 --- a/src/components/settings/password-input.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Eye, EyeOff } from "lucide-react"; - -import { Input } from "@/components/ui/input"; -import { useTranslations } from "@/lib/i18n/context"; - -/** - * Password input with a built-in show/hide toggle. Extracted from the legacy - * settings monolith so every section that needs a masked secret (Withings, - * moodLog, ntfy, Telegram, AI provider keys, account password) can share the - * same UX without duplicating the visibility-toggle plumbing. - */ -export function PasswordInput(props: React.ComponentProps) { - const [visible, setVisible] = useState(false); - const { t } = useTranslations(); - return ( -
- - -
- ); -} diff --git a/src/components/settings/telegram-card.tsx b/src/components/settings/telegram-card.tsx index 2619e098d..28298fd2a 100644 --- a/src/components/settings/telegram-card.tsx +++ b/src/components/settings/telegram-card.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; -import { PasswordInput } from "@/components/settings/password-input"; +import { PasswordInput } from "@/components/ui/password-input"; import { useTranslations } from "@/lib/i18n/context"; interface TelegramSettings { diff --git a/src/components/ui/password-input.tsx b/src/components/ui/password-input.tsx new file mode 100644 index 000000000..7e1ac7625 --- /dev/null +++ b/src/components/ui/password-input.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useState } from "react"; +import { Eye, EyeOff } from "lucide-react"; + +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; +import { useTranslations } from "@/lib/i18n/context"; + +/** + * Password input with a built-in show/hide toggle. Lives in the shared UI + * layer so every surface that needs a masked secret (Withings, moodLog, + * ntfy, Telegram, AI provider keys, account password, auth login / + * register) can share the same UX without duplicating the + * visibility-toggle plumbing. + * + * v1.4.27 R3d MB2 — the icon-only toggle wraps in an `inline-flex h-11 + * w-11` hit area so the 44 px WCAG 2.5.5 floor is honoured on touch. + * The icon stays at 16 px; the surrounding padding does the work. The + * input itself gets `pr-12` so user input never collides with the + * toggle glyph. + */ +export function PasswordInput({ + className, + ...props +}: React.ComponentProps) { + const [visible, setVisible] = useState(false); + const { t } = useTranslations(); + return ( +
+ + +
+ ); +}