mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
chore(coach): lift the rail-tray chevrons out of the overlay
The mobile history + sources tray triggers used to mount as absolute chevron buttons over the message thread, so they (a) overlapped the first message bubble, (b) sat at a 28 px tap target (`h-7`), and (c) relied on `backdrop-blur` to stay legible — which still failed on busy threads. Lift them into a sub-header strip above the thread: a flex row with two ghost buttons at `min-h-11`. The strip itself carries `xl:hidden` since both rails are inline at xl+; the history trigger keeps its own `lg:hidden` because the history rail mounts inline already at lg+. While the rail surface is in scope, drop the `opacity-0 group-hover:opacity-100` reveal on the history-rail per-row delete button — touch surfaces have no hover, so the affordance was invisible on mobile. The button now sits at `size-11` always-visible; the confirming state still styles via `data-confirming`. Test re-baselined to assert the new strip wrapper + `min-h-11` floor.
This commit is contained in:
1 parent
2d8c5e906e
commit
beb2b40f7d
3 files changed
+68
-42
No files matched your search
@@ -41,18 +41,22 @@ describe("<CoachDrawerBody> — mobile rail trays", () => {
|
||||
});
|
||||
|
||||
it("hides the history trigger on >=lg and the sources trigger on >=xl", () => {
|
||||
// v1.4.27 R3d MB2 — the strip wrapper carries `xl:hidden` so the
|
||||
// sources trigger inherits the breakpoint; the history trigger
|
||||
// keeps its own `lg:hidden` because the history rail mounts
|
||||
// inline already on lg+ while the sources rail still needs the
|
||||
// chevron until xl.
|
||||
const html = render(<CoachDrawerBody {...baseProps} />);
|
||||
const strip = html.match(
|
||||
/<div[^>]*data-slot="coach-drawer-rail-tray-strip"[^>]*>/,
|
||||
);
|
||||
expect(strip?.[0]).toContain("xl:hidden");
|
||||
|
||||
const historyTrigger = html.match(
|
||||
/<button[^>]*data-slot="coach-drawer-history-tray-trigger"[^>]*>/,
|
||||
);
|
||||
expect(historyTrigger).not.toBeNull();
|
||||
expect(historyTrigger?.[0]).toContain("lg:hidden");
|
||||
|
||||
const sourcesTrigger = html.match(
|
||||
/<button[^>]*data-slot="coach-drawer-sources-tray-trigger"[^>]*>/,
|
||||
);
|
||||
expect(sourcesTrigger).not.toBeNull();
|
||||
expect(sourcesTrigger?.[0]).toContain("xl:hidden");
|
||||
});
|
||||
|
||||
it("renders the desktop history rail (lg+) and sources rail (xl+)", () => {
|
||||
@@ -95,12 +99,16 @@ describe("<CoachDrawerBody> — mobile rail trays", () => {
|
||||
expect(html).toContain("Worauf ich zugreife");
|
||||
});
|
||||
|
||||
it("the thread region wraps the rail triggers in a relative container", () => {
|
||||
// The triggers use absolute positioning; the thread region must
|
||||
// be `relative` for them to anchor inside it.
|
||||
it("renders the rail triggers in a sub-header strip above the thread", () => {
|
||||
// v1.4.27 R3d MB2 — the triggers were lifted out of the absolute
|
||||
// overlay into a sub-header strip so the chevrons sit at a 44 px
|
||||
// tap target and never overlay the first message bubble. The
|
||||
// strip lives above the thread within the centre column.
|
||||
const html = render(<CoachDrawerBody {...baseProps} />);
|
||||
expect(html).toMatch(
|
||||
/<main[^>]*data-slot="coach-drawer-thread"[^>]*relative/,
|
||||
expect(html).toMatch(/data-slot="coach-drawer-rail-tray-strip"/);
|
||||
const trigger = html.match(
|
||||
/<button[^>]*data-slot="coach-drawer-history-tray-trigger"[^>]*>/,
|
||||
);
|
||||
expect(trigger?.[0]).toContain("min-h-11");
|
||||
});
|
||||
});
|
||||
@@ -59,36 +59,48 @@ export function CoachDrawerBody({
|
||||
{/* Centre — message thread. */}
|
||||
<main
|
||||
data-slot="coach-drawer-thread"
|
||||
className="relative flex h-full min-h-0 flex-col"
|
||||
className="flex h-full min-h-0 flex-col"
|
||||
>
|
||||
{/* v1.4.20 phase B4 — mobile rail tray triggers. Hidden on
|
||||
`>=lg` because the rails are inline there. */}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onOpenHistoryTray}
|
||||
data-slot="coach-drawer-history-tray-trigger"
|
||||
aria-label={t("insights.coach.historyTitle")}
|
||||
className="bg-background/80 border-border/60 absolute top-2 left-2 z-10 h-7 gap-1 border px-2 text-[11px] backdrop-blur lg:hidden"
|
||||
{/* v1.4.27 R3d MB2 — rail-tray triggers lifted out of the
|
||||
absolute overlay into a sub-header strip so the chevrons
|
||||
sit at a 44 px tap target and never overlay the first
|
||||
message bubble. Hidden on `>=xl` because both rails are
|
||||
inline at that breakpoint. The history trigger remains
|
||||
visible up to lg-1 (rail is inline at lg+); the sources
|
||||
trigger remains visible up to xl-1 (rail is inline at
|
||||
xl+). */}
|
||||
<div
|
||||
data-slot="coach-drawer-rail-tray-strip"
|
||||
className="border-border/70 flex items-center justify-between gap-2 border-b px-3 py-2 xl:hidden"
|
||||
>
|
||||
<ChevronRight className="size-3" aria-hidden="true" />
|
||||
{t("insights.coach.historyTitle")}
|
||||
</Button>
|
||||
{/* Sources tray trigger stays visible up to xl since the
|
||||
inline sources rail is xl+ only. */}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onOpenSourcesTray}
|
||||
data-slot="coach-drawer-sources-tray-trigger"
|
||||
aria-label={t("insights.coach.sourcesTitle")}
|
||||
className="bg-background/80 border-border/60 absolute top-2 right-2 z-10 h-7 gap-1 border px-2 text-[11px] backdrop-blur xl:hidden"
|
||||
>
|
||||
{t("insights.coach.sourcesTitle")}
|
||||
<ChevronLeft className="size-3" aria-hidden="true" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onOpenHistoryTray}
|
||||
data-slot="coach-drawer-history-tray-trigger"
|
||||
aria-label={t("insights.coach.historyTitle")}
|
||||
className="min-h-11 gap-1.5 text-xs lg:hidden"
|
||||
>
|
||||
<ChevronRight className="size-4" aria-hidden="true" />
|
||||
{t("insights.coach.historyTitle")}
|
||||
</Button>
|
||||
{/* Sources tray trigger stays visible up to xl since the
|
||||
inline sources rail is xl+ only. The empty `<span>` to
|
||||
the left keeps the right-aligned button placement when
|
||||
the history trigger hides at lg+. */}
|
||||
<span aria-hidden="true" className="hidden lg:block" />
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onOpenSourcesTray}
|
||||
data-slot="coach-drawer-sources-tray-trigger"
|
||||
aria-label={t("insights.coach.sourcesTitle")}
|
||||
className="min-h-11 gap-1.5 text-xs"
|
||||
>
|
||||
{t("insights.coach.sourcesTitle")}
|
||||
<ChevronLeft className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1">{thread}</div>
|
||||
{/* Composer pinned to the bottom. */}
|
||||
|
||||
@@ -138,6 +138,12 @@ export function HistoryRail({
|
||||
{formatRelativeTime(c.updatedAt, t)}
|
||||
</span>
|
||||
</button>
|
||||
{/* v1.4.27 R3d MB2 — drop the hover-only `opacity-0`
|
||||
reveal: touch surfaces have no hover, so the delete
|
||||
affordance was invisible on mobile. The button now
|
||||
sits at 44 px always-visible per the WCAG 2.5.5
|
||||
floor; confirming state stays styled via the
|
||||
`data-confirming` attribute. */}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -151,12 +157,12 @@ export function HistoryRail({
|
||||
data-slot="coach-history-delete"
|
||||
data-confirming={isConfirming ? "true" : undefined}
|
||||
className={cn(
|
||||
"size-6 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
|
||||
"size-11 shrink-0",
|
||||
isConfirming &&
|
||||
"text-dracula-red hover:text-dracula-red opacity-100",
|
||||
"text-dracula-red hover:text-dracula-red",
|
||||
)}
|
||||
>
|
||||
<Trash2 className="size-3.5" aria-hidden="true" />
|
||||
<Trash2 className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user