Files
HealthLog/src/components/insights/target-adjust-button.tsx
T
Marc-André Bombeck 528ce93c71 feat(insights): move target-range editing to a header gear
The target reference card carried an inline "Adjust target range" link,
which read as a control buried in a read surface. Lift the action to a
gear button in the category-page header, to the left of the Coach launch
icon, so the two sit as a consistent icon cluster beside the heading.

A small page-scoped provider bridges the two: the reference card
registers its editable target (one per page, except blood glucose which
fans out to several contexts and registers each) and the header gear
opens the per-metric editor sheet the provider owns. Single-target pages
open that target directly; glucose opens the primary context, since
target editing lives only on these pages — there is no separate targets
management surface. The card itself is now a pure read surface.

The gear carries the action's existing label as its accessible name,
clears the 44 px touch-target floor on mobile, and exposes a stable
`data-slot` for end-to-end coverage.
2026-06-04 17:47:17 +02:00

62 lines
2.2 KiB
TypeScript

"use client";
import { Settings2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useTranslations } from "@/lib/i18n/context";
import { useTargetAdjust } from "@/lib/insights/target-adjust-context";
/**
* Header gear that opens the per-metric target editor.
*
* The "Adjust target range" affordance used to sit inside the target
* reference card; it now lives in the Insights category-page header
* (`<SubPageShell>`), to the left of the Coach launch icon, so the card
* stays a read surface. The button is a thin wrapper around
* `useTargetAdjust()`:
*
* - renders nothing when no provider is mounted or no editable target
* has registered for the page (e.g. a metric without a numeric
* target band, where the card paints nothing), so the header never
* shows a dead control;
* - opens the primary registered target's `<TargetEditSheet>` on click.
*
* Sized to match `<CoachLaunchButton variant="icon">` so the gear + the
* Coach sparkle read as a consistent icon cluster top-right of the
* heading.
*/
export function TargetAdjustButton({ className }: { className?: string }) {
const { t } = useTranslations();
const adjust = useTargetAdjust();
// No provider, or no editable target registered for this page.
if (!adjust || !adjust.canAdjust) return null;
// Reuse the action's existing copy ("Adjust target range" /
// "Zielbereich anpassen") — the gear performs exactly what the old
// in-card link did, so the same string is the right accessible label.
const label = t("insights.subPage.target.adjustLink");
return (
<Button
type="button"
variant="ghost"
size="icon"
data-slot="target-adjust-trigger"
aria-label={label}
title={label}
onClick={() => adjust.requestAdjust()}
className={cn(
// The `icon` size is 40 px; bump to the 44 px WCAG 2.5.5 floor on
// mobile and settle back to the 40 px that matches the sibling
// Coach icon from `sm` up, so the cluster reads as one on desktop.
"text-muted-foreground hover:text-foreground size-11 sm:size-10",
className,
)}
>
<Settings2 className="size-4" aria-hidden="true" />
</Button>
);
}