mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(targets): make the coach launch an icon-only affordance
The per-card Coach CTA was rendering a labelled ghost pill (chat glyph + 'Coach fragen' text). On a card already carrying a status pill, range bar, consistency strip, headline number, edit cog and a source link, the labelled pill dominated the optical hierarchy. Drop the visible label, keep the chat-bubble glyph, and surface the affordance via aria-label + title so screen readers and tooltips still announce 'Ask Coach' / 'Coach fragen'. Collapse the footer flex container to a single horizontal row at every breakpoint now that the button is icon-sized.
This commit is contained in:
1 parent
d286220bed
commit
1b0e81ae42
3 files changed
+31
-15
No files matched your search
@@ -28,7 +28,13 @@ describe("<TargetCoachButton>", () => {
|
||||
it("renders the button when aiEnabled is true", () => {
|
||||
const html = render({ ...baseProps, aiEnabled: true });
|
||||
expect(html).toContain('data-slot="target-coach-cta"');
|
||||
expect(html).toContain("Ask Coach");
|
||||
// v1.4.28 FB-L1 — the affordance is now icon-only; the label
|
||||
// survives as an aria-label + title so screen readers + tooltips
|
||||
// still surface "Ask Coach" / "Coach fragen".
|
||||
expect(html).toContain('aria-label="Ask Coach"');
|
||||
expect(html).toContain('title="Ask Coach"');
|
||||
// No visible text label — only the chat-bubble glyph.
|
||||
expect(html).not.toMatch(/>Ask Coach</);
|
||||
});
|
||||
|
||||
it("renders NOTHING when aiEnabled is false (no broken-button state)", () => {
|
||||
|
||||
@@ -656,20 +656,19 @@ export function TargetCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Row 6: footer. Mobile-first reflow:
|
||||
- default (<640px): stack vertically; the Coach CTA
|
||||
spans full width so the touch target is generous, the
|
||||
source link sits beneath it right-aligned.
|
||||
- sm+ (≥640px): horizontal row, Coach left + source right.
|
||||
`mt-auto` pins to card bottom so grid cells align. */}
|
||||
<div className="mt-auto flex flex-col items-stretch gap-2 pt-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
|
||||
{/* Row 6: footer. v1.4.28 FB-L1 — the Coach CTA is now an
|
||||
icon-only affordance (chat-bubble glyph + aria-label), so
|
||||
the row stays horizontal at every breakpoint: icon left,
|
||||
source link right. `mt-auto` pins to card bottom so grid
|
||||
cells align. */}
|
||||
<div className="mt-auto flex flex-row items-center justify-between gap-3 pt-1">
|
||||
{aiEnabled && (
|
||||
<TargetCoachButton
|
||||
prefill={coachPrefill}
|
||||
sources={coachSources}
|
||||
onAskCoach={onAskCoach}
|
||||
aiEnabled={aiEnabled}
|
||||
className="w-full justify-center sm:-ml-2 sm:w-auto sm:justify-start"
|
||||
className="-ml-2"
|
||||
/>
|
||||
)}
|
||||
{sourceLink ? (
|
||||
@@ -677,13 +676,13 @@ export function TargetCard({
|
||||
href={sourceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 self-end text-xs sm:ml-auto sm:self-auto"
|
||||
className="text-muted-foreground hover:text-foreground ml-auto inline-flex items-center gap-1 text-xs"
|
||||
>
|
||||
<span>{target.source}</span>
|
||||
<ExternalLink className="size-3" aria-hidden="true" />
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-muted-foreground self-end text-xs sm:ml-auto sm:self-auto">
|
||||
<span className="text-muted-foreground ml-auto text-xs">
|
||||
{target.source}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { MessageCircle } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -17,6 +17,14 @@ import type { CoachScope, CoachScopeSource } from "@/lib/ai/coach/types";
|
||||
* On click the button fires `onAskCoach({ prefill, scope })`. The
|
||||
* parent owns the `CoachDrawer` state; this button doesn't know about
|
||||
* the drawer at all.
|
||||
*
|
||||
* v1.4.28 R3c FB-L1 — collapsed to an icon-only affordance. The card
|
||||
* footer was carrying a labelled pill ("Coach fragen"); on a card with
|
||||
* a status pill, range bar, consistency strip, headline number, edit
|
||||
* cog and a source link, the labelled pill dominated the visual
|
||||
* hierarchy. Dropping the label retains the affordance via the
|
||||
* chat-bubble glyph + `aria-label` while shrinking the optical weight
|
||||
* to a per-card icon button.
|
||||
*/
|
||||
export interface TargetCoachButtonProps {
|
||||
/**
|
||||
@@ -51,11 +59,13 @@ export function TargetCoachButton({
|
||||
|
||||
if (!aiEnabled) return null;
|
||||
|
||||
const accessibleLabel = t("targets.coach.cta");
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
onAskCoach({
|
||||
prefill,
|
||||
@@ -66,10 +76,11 @@ export function TargetCoachButton({
|
||||
})
|
||||
}
|
||||
data-slot="target-coach-cta"
|
||||
aria-label={accessibleLabel}
|
||||
title={accessibleLabel}
|
||||
className={className}
|
||||
>
|
||||
<Sparkles className="size-3.5" aria-hidden="true" />
|
||||
<span>{t("targets.coach.cta")}</span>
|
||||
<MessageCircle className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user