fix(ui): close consistency-audit contrast and duplication findings

Today hero's top-signal delta used opacity-on-muted text, landing below
AA on the app's most prominent card; five mood charts and the sleep-
stage IN_BED fill painted raw --dracula-*/hardcoded-hex hues with no
(or an unused) light-theme counterpart. Route all of them through
existing semantic/chart tokens instead (destructive/warning/success,
chart-1..5, and a newly minted --chart-inbed with its own Alucard
override) so every hue is AA-tuned per theme.

Also collapses three duplicated primitives the audit flagged: the
click-Popover VitalsInfoTip and hover-Tooltip InfoHint merge into one
touch-first ui/info-popover; MedicationViewToggle and ModuleViewToggle
were verbatim copy-paste and now wrap one shared ui/view-toggle; the
triplicated sport-icon map (already drifted — mixedCardio rendered a
different glyph in the list vs the detail/dashboard views) moves to
lib/workouts/sport-icons.ts with HeartPulse as the settled icon.

Adds a no-restricted-imports lint rule banning direct recharts imports
outside the chart-runtime barrel, catching the bundle-duplication
mistake at local lint instead of PR-CI.
This commit is contained in:
Marc-André Bombeck committed 2026-07-18 01:23:37 +02:00
1 parent 1ff179178f
commit 247bc4f33c
25 files changed
+401 -400

No files matched your search

@@ -3,16 +3,13 @@
import { LayoutGrid, Table as TableIcon } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { cn } from "@/lib/utils";
import { ViewToggle } from "@/components/ui/view-toggle";
import type { MedicationListView } from "@/lib/medication-list-layout";
/**
* v1.16.10 — icon-only card/table view toggle for the /medications page
* header. A small two-segment control: the active segment carries the
* filled surface, the inactive one stays ghost. `aria-pressed` announces
* the state per segment; the group is named for screen readers. Segments
* keep the 44-px mobile tap floor (`size-11`), shrinking to the standard
* 36-px control height on desktop like the neighbouring Add button.
* header. Thin wrapper over the shared `ViewToggle` primitive; see its
* docstring for the interaction grammar.
*/
interface MedicationViewToggleProps {
view: MedicationListView;
@@ -25,49 +22,24 @@ export function MedicationViewToggle({
}: MedicationViewToggleProps) {
const { t } = useTranslations();
const segments = [
{
value: "cards" as const,
label: t("medications.viewCards"),
icon: LayoutGrid,
},
{
value: "table" as const,
label: t("medications.viewTable"),
icon: TableIcon,
},
];
return (
<div
role="group"
aria-label={t("medications.viewToggleLabel")}
className="border-border bg-background/30 inline-flex shrink-0 items-center rounded-md border p-0.5"
>
{segments.map(({ value, label, icon: Icon }) => {
const active = view === value;
return (
<button
key={value}
type="button"
onClick={() => {
if (!active) onChange(value);
}}
aria-pressed={active}
aria-label={label}
title={label}
data-slot={`medications-view-${value}`}
className={cn(
"focus-visible:ring-ring inline-flex size-10 items-center justify-center rounded-[5px] transition-colors focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none sm:size-8",
active
? "bg-secondary text-secondary-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground",
)}
>
<Icon className="size-4" aria-hidden="true" />
</button>
);
})}
</div>
<ViewToggle
view={view}
onChange={onChange}
groupLabel={t("medications.viewToggleLabel")}
dataSlotPrefix="medications-view"
segments={[
{
value: "cards",
label: t("medications.viewCards"),
icon: LayoutGrid,
},
{
value: "table",
label: t("medications.viewTable"),
icon: TableIcon,
},
]}
/>
);
}