mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
Verified UI-consistency findings from the campaign's design audit, each pixel-faithful except the intended correction: - insights hero band: off-scale py-5/gap-5 → the standard scale; H1 font-semibold → font-bold; sm:text-[28px] → sm:text-3xl. - medication-compliance-chart + recent-achievements-card: hand-rolled muted h-4 icon + text-sm headers → the standard TileHeader primitive so they match sibling tiles. - achievements stat tiles + clinician-view shells: p-5 → p-4 md:p-6. - two text-primary header glyphs (research-mode dialog, enroll-mfa) → text-foreground (never accent on a header glyph); the deliberate badge-boxed icons left untouched. - SheetHeader now reserves the trailing close-X gutter like DialogHeader, closing the latent overlap class for future raw-Sheet consumers. - five raw Dialogs on phone-reachable surfaces → ResponsiveSheet. - new bg-card-scoped healthlog/spacing-scale lint (flags p-5/gap-5 etc. only on card shells; list-marker insets + half-steps excluded) at error, with the five offenders it flagged swept. - dropped the stale CLAUDE.md '18 PascalCase outliers' line (0 remain). Every data-slot preserved. No migration.
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import { XIcon } from "lucide-react";
|
|
import { Dialog as SheetPrimitive } from "radix-ui";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
|
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
}
|
|
|
|
function SheetTrigger({
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
}
|
|
|
|
function SheetClose({
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
}
|
|
|
|
function SheetPortal({
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
}
|
|
|
|
function SheetOverlay({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
return (
|
|
<SheetPrimitive.Overlay
|
|
data-slot="sheet-overlay"
|
|
className={cn(
|
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function SheetContent({
|
|
className,
|
|
children,
|
|
side = "right",
|
|
showCloseButton = true,
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
side?: "top" | "right" | "bottom" | "left";
|
|
showCloseButton?: boolean;
|
|
}) {
|
|
const { t } = useTranslations();
|
|
return (
|
|
<SheetPortal>
|
|
<SheetOverlay />
|
|
<SheetPrimitive.Content
|
|
data-slot="sheet-content"
|
|
className={cn(
|
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
|
side === "right" &&
|
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
|
side === "left" &&
|
|
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
|
side === "top" &&
|
|
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
|
side === "bottom" &&
|
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
{showCloseButton && (
|
|
// v1.4.27 R4 RC3 — widen the icon-only close-X tap target to
|
|
// a 36 px floor, matching the Dialog close-X treatment from
|
|
// MB2 (Decision I). 36 px (not 44 px) is the intentional
|
|
// exception for fixed icon-only close affordances per the
|
|
// dialog primitive's rationale: 44 px would crowd the sheet
|
|
// header out of proportion on the bottom-sheet branch.
|
|
<SheetPrimitive.Close
|
|
data-slot="sheet-close-button"
|
|
className="ring-offset-background focus-visible:border-ring focus-visible:ring-ring/50 text-muted-foreground hover:bg-accent hover:text-foreground absolute top-3 right-4 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-[3px] focus-visible:outline-none disabled:pointer-events-none sm:min-h-9 sm:min-w-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
>
|
|
<XIcon />
|
|
{/* v1.4.34 IW-G — read `common.close` so screen readers
|
|
hear the localised label instead of literal English. */}
|
|
<span className="sr-only">{t("common.close")}</span>
|
|
</SheetPrimitive.Close>
|
|
)}
|
|
</SheetPrimitive.Content>
|
|
</SheetPortal>
|
|
);
|
|
}
|
|
|
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
return (
|
|
<div
|
|
data-slot="sheet-header"
|
|
// Reserve the trailing gutter for the absolute close-X the way
|
|
// `DialogHeader` (pr-9) does, sized to the sheet's own close button
|
|
// (`top-3 right-4`, min-w-9…11) so a long raw-`<Sheet>` title never
|
|
// slides under the X — the Documents close-X overlap class, foreclosed
|
|
// at the primitive. Consumers that render `showCloseButton={false}`
|
|
// (or their own close affordance) drop it back with `pr-4` via
|
|
// `className` — tailwind-merge keeps the later value.
|
|
className={cn("flex flex-col gap-1.5 p-4 pr-12", className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
return (
|
|
<div
|
|
data-slot="sheet-footer"
|
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function SheetTitle({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
return (
|
|
<SheetPrimitive.Title
|
|
data-slot="sheet-title"
|
|
className={cn("text-foreground font-semibold", className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function SheetDescription({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
return (
|
|
<SheetPrimitive.Description
|
|
data-slot="sheet-description"
|
|
className={cn("text-muted-foreground text-sm", className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export {
|
|
Sheet,
|
|
SheetTrigger,
|
|
SheetClose,
|
|
SheetContent,
|
|
SheetHeader,
|
|
SheetFooter,
|
|
SheetTitle,
|
|
SheetDescription,
|
|
};
|