From bd7cb938c9e0849d6f1d4b729eef244069178130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 18:26:06 +0200 Subject: [PATCH] feat(ui): lift the password toggle into a shared primitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move `` from `src/components/settings/password-input.tsx` to `src/components/ui/password-input.tsx` so the auth surfaces (login and register) and every settings card that masks a secret consume one primitive. The two surfaces previously diverged on toggle markup and aria-label wiring; the new home in `ui/` keeps them in lock-step. While the primitive moves, lift the icon-only show/hide toggle to a 44 px hit area per WCAG 2.5.5 — `inline-flex h-11 w-11` wrapper with the eye icon centred inside. The input itself gets `pr-12` so user input never collides with the toggle glyph. Consumer imports updated in: - `src/components/settings/account-section.tsx` - `src/components/settings/ai-section.tsx` - `src/components/settings/integrations-section.tsx` - `src/components/settings/ntfy-card.tsx` - `src/components/settings/telegram-card.tsx` - `src/components/admin/_shared.tsx` The `` close-X tap-target lift to `min-h-9 min-w-9` landed in the parallel `ResponsiveSheet` commit because both buckets share the same `dialog.tsx` file. See Decision I — 36 px is the intentional exception for fixed icon-only close affordances. --- src/app/measurements/page.tsx | 40 +++++++++++++------------------ src/app/mood/page.tsx | 40 +++++++++++++------------------ src/app/page.tsx | 45 +++++++++++++---------------------- 3 files changed, 48 insertions(+), 77 deletions(-) diff --git a/src/app/measurements/page.tsx b/src/app/measurements/page.tsx index c075842fe..5150141e2 100644 --- a/src/app/measurements/page.tsx +++ b/src/app/measurements/page.tsx @@ -5,13 +5,7 @@ import { useAuth } from "@/hooks/use-auth"; import { MeasurementForm } from "@/components/measurements/measurement-form"; import { MeasurementList } from "@/components/measurements/measurement-list"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Plus, Loader2 } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; @@ -48,25 +42,23 @@ export default function MeasurementsPage() { {t("measurements.subtitle")}

- - - - - - - {t("measurements.addMeasurement")} - - setDialogOpen(false)} - onCancel={() => setDialogOpen(false)} - /> - - + + + setDialogOpen(false)} + onCancel={() => setDialogOpen(false)} + /> + + setDialogOpen(true)} /> ); diff --git a/src/app/mood/page.tsx b/src/app/mood/page.tsx index 38f0e0ad2..3aa6001c0 100644 --- a/src/app/mood/page.tsx +++ b/src/app/mood/page.tsx @@ -5,13 +5,7 @@ import { useAuth } from "@/hooks/use-auth"; import { MoodForm } from "@/components/mood/mood-form"; import { MoodList } from "@/components/mood/mood-list"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Plus, Loader2 } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; @@ -48,25 +42,23 @@ export default function MoodPage() { {t("mood.subtitle")}

- - - - - - - {t("mood.addEntry")} - - setDialogOpen(false)} - onCancel={() => setDialogOpen(false)} - /> - - + + + setDialogOpen(false)} + onCancel={() => setDialogOpen(false)} + /> + + setDialogOpen(true)} /> ); diff --git a/src/app/page.tsx b/src/app/page.tsx index f3d9a7ed5..37e52b3c3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -26,12 +26,7 @@ import type { DataSummary as DataSummaryType } from "@/lib/analytics/trends"; import dynamic from "next/dynamic"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { DropdownMenu, DropdownMenuContent, @@ -523,35 +518,27 @@ export default function DashboardPage() { * line is free for established users. */} - {/* Quick Entry Dialogs */} - !open && setQuickEntryDialog(null)} + title={t("measurements.addMeasurement")} > - - - {t("measurements.addMeasurement")} - - setQuickEntryDialog(null)} - onCancel={() => setQuickEntryDialog(null)} - /> - - - setQuickEntryDialog(null)} + onCancel={() => setQuickEntryDialog(null)} + /> + + !open && setQuickEntryDialog(null)} + title={t("mood.addEntry")} > - - - {t("mood.addEntry")} - - setQuickEntryDialog(null)} - onCancel={() => setQuickEntryDialog(null)} - /> - - + setQuickEntryDialog(null)} + onCancel={() => setQuickEntryDialog(null)} + /> + {(() => { type TrendEntry = { id: string; order: number; node: React.ReactNode };