Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,17 @@ summary::-webkit-details-marker {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--focus) 25%, transparent) !important;
}

/* Underline-tab focus: an inset rounded ring that hugs the label instead of the
default full-height box, which collides with the tablist border. */
.focus-ring-tab {
border-radius: var(--radius-md);
}
.focus-ring-tab:focus-visible {
outline: 2px solid var(--focus) !important;
outline-offset: -4px !important;
box-shadow: none !important;
}

/* Premium hover transitions for source capsules */
.source-capsule-hover {
transition:
Expand Down
3 changes: 1 addition & 2 deletions src/components/DocumentTagCloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ function DocumentTagChip({
groupToneClass(tag.group),
tag.queryMatched && "ring-2 ring-[color:var(--focus)]/25",
selected && "ring-2 ring-[color:var(--primary)]/35",
onTagClick &&
"cursor-pointer transition hover:-translate-y-0.5 hover:border-[color:var(--border-strong)] focus:outline-none focus:ring-2 focus:ring-[color:var(--focus)]/40",
onTagClick && "cursor-pointer transition hover:-translate-y-0.5 hover:border-[color:var(--border-strong)]",
);
const content = (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/components/applications-launcher-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ function FilterTabs({
aria-controls="launcher-results-panel"
onClick={() => onFilterChange(filter.id)}
className={cn(
"inline-flex min-h-9 items-center justify-center rounded-lg border px-4 text-xs font-bold transition",
"inline-flex min-h-11 items-center justify-center whitespace-nowrap rounded-lg border px-4 text-xs font-bold transition",
active
? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
: "border-[color:var(--border)] bg-[color:var(--surface-lux)] text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text-heading)]",
Expand All @@ -368,7 +368,7 @@ function FilterTabs({
aria-controls="launcher-results-panel"
onClick={() => onFilterChange(filter.id)}
className={cn(
"inline-flex min-h-7 shrink-0 items-center justify-center gap-0.5 rounded-lg border px-2 text-4xs font-bold transition",
"inline-flex min-h-11 shrink-0 items-center justify-center gap-0.5 whitespace-nowrap rounded-lg border px-2 text-4xs font-bold transition",
active
? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
: "border-[color:var(--border)] bg-[color:var(--surface-lux)] text-[color:var(--text-muted)]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ function RowActionsMenu({ item }: { item: FavouriteItem }) {
aria-label={`More actions for ${item.title}`}
onClick={() => setOpen((current) => !current)}
className={cn(
"grid h-9 w-9 place-items-center rounded-lg text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)]",
"grid h-11 w-11 place-items-center rounded-lg text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)]",
focusRing,
)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ function SidebarRow({ entry }: { entry: SidebarEntry }) {
aria-pressed={entry.active}
onClick={entry.onClick}
className={cn(
"flex min-h-9 w-full items-center gap-2.5 rounded-lg border px-2.5 text-left text-sm-minus font-semibold transition",
"flex min-h-11 w-full items-center gap-2.5 rounded-lg border px-2.5 text-left text-sm-minus font-semibold transition",
entry.active
? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[inset_2px_0_0_var(--clinical-accent)]"
: "border-transparent text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
Expand Down Expand Up @@ -302,7 +302,7 @@ export function FavouritesSidebar({
type="button"
onClick={() => onCollapsedChange(true)}
className={cn(
"grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-transparent text-[color:var(--text-muted)] transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]",
"grid h-11 w-11 shrink-0 place-items-center rounded-lg border border-transparent text-[color:var(--text-muted)] transition hover:border-[color:var(--border)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]",
focusRing,
)}
aria-label="Compact library sidebar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function MedicationHome({
actionsLabel="Medication prompts"
actions={medicationPrompts.map((prompt) => ({
title: prompt.label,
description: "Prescribing-focused search.",
description: prompt.description,
icon: prompt.icon,
onClick: () => onSuggestedSearch(prompt.label),
disabled: loading,
Expand Down
14 changes: 7 additions & 7 deletions src/components/differentials/differential-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,13 @@ function SectionItems({
{slug ? (
<Link
href={`/differentials/diagnoses/${slug}`}
className="inline-flex min-h-9 items-center gap-1 rounded-md border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] px-2.5 text-xs font-bold text-[color:var(--clinical-accent)] hover:border-[color:var(--clinical-accent)]"
className="inline-flex min-h-11 items-center gap-1 rounded-md border border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] px-2.5 text-xs font-bold text-[color:var(--clinical-accent)] hover:border-[color:var(--clinical-accent)]"
>
{item}
<ChevronRight className="h-3.5 w-3.5" aria-hidden />
</Link>
) : (
<span className="inline-flex min-h-9 items-center rounded-md border border-[color:var(--border)] bg-[color:var(--surface-subtle)] px-2.5 text-xs font-semibold text-[color:var(--text-muted)]">
<span className="inline-flex min-h-11 items-center rounded-md border border-[color:var(--border)] bg-[color:var(--surface-subtle)] px-2.5 text-xs font-semibold text-[color:var(--text-muted)]">
{item}
</span>
)}
Expand Down Expand Up @@ -714,11 +714,11 @@ function TopActions({
onCompare: () => void;
}) {
return (
<div className="hidden items-center gap-3 lg:flex">
<div className="hidden shrink-0 items-center gap-3 lg:flex">
<button
type="button"
onClick={onCompare}
className="inline-flex min-h-11 items-center gap-2 rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface)] px-4 text-sm font-bold text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)] hover:bg-[color:var(--surface-subtle)]"
className="inline-flex min-h-11 items-center gap-2 whitespace-nowrap rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface)] px-4 text-sm font-bold text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)] hover:bg-[color:var(--surface-subtle)]"
>
<GitCompareArrows className="h-4 w-4" aria-hidden />
Compare
Expand Down Expand Up @@ -886,10 +886,10 @@ function Tabs({
tabIndex={isActive ? 0 : -1}
onClick={() => onChange(tab.id)}
className={cn(
"min-h-11 flex-1 whitespace-nowrap px-1 py-3 text-center text-xs sm:flex-none sm:px-4 sm:text-sm",
"focus-ring-tab min-h-11 flex-1 whitespace-nowrap border-b-2 px-1 py-3 text-center text-xs sm:flex-none sm:px-4 sm:text-sm",
isActive
? "border-b-2 border-[color:var(--clinical-accent)] text-[color:var(--clinical-accent)]"
: "hover:text-[color:var(--text-heading)]",
? "border-[color:var(--clinical-accent)] text-[color:var(--clinical-accent)]"
: "border-transparent hover:text-[color:var(--text-heading)]",
)}
>
{tab.label}
Expand Down
2 changes: 1 addition & 1 deletion src/components/services/service-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ function ReferralTable({
disabled={!hasText(row.value)}
onClick={() => onCopy(row.value, `${row.label} copied`)}
aria-label={`Copy ${row.label}`}
className="inline-grid h-10 w-10 place-items-center rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--clinical-accent-border)] hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] disabled:cursor-not-allowed disabled:opacity-40"
className="inline-grid h-11 w-11 place-items-center rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:border-[color:var(--clinical-accent-border)] hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] disabled:cursor-not-allowed disabled:opacity-40"
>
<Copy className="h-4 w-4" aria-hidden />
</button>
Expand Down
44 changes: 44 additions & 0 deletions tests/ui-tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,50 @@ test.describe("Clinical KB tools launcher", () => {
await expectNoPageHorizontalOverflow(page);
});

test("diagnosis detail actions stay tappable and tabs stay single-line", async ({ page }) => {
await page.setViewportSize({ width: 1024, height: 800 });
await gotoLauncher(page, "/differentials/diagnoses/delirium");
await expect(page.getByTestId("differential-detail-page")).toBeVisible();
// The desktop action cluster must keep its intrinsic width (shrink-0) so the
// icon action does not get crushed below the 44px tap standard.
await expectMinTouchTarget(page.getByRole("button", { name: "Save diagnosis" }));

// Tabs: no page overflow and single-line labels at the narrowest width.
await page.setViewportSize({ width: 320, height: 700 });
await expect(page.getByTestId("differential-detail-page")).toBeVisible();
await expectNoPageHorizontalOverflow(page);
const overviewTab = page.getByRole("tab", { name: "Overview" });
await expect(overviewTab).toBeVisible();
// Count rendered label lines from text-node rects (an icon rect would bridge
// two wrapped lines and mask a wrap); the tab label must stay on one line.
const overviewLineCount = await overviewTab.evaluate((element) => {
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
const rects: DOMRect[] = [];
while (walker.nextNode()) {
const node = walker.currentNode;
if (!(node.textContent ?? "").trim()) continue;
const range = document.createRange();
range.selectNodeContents(node);
for (const rect of range.getClientRects()) {
if (rect.height > 4 && rect.width > 1) rects.push(rect);
}
}
rects.sort((a, b) => a.top - b.top);
let lines = 0;
let lineBottom = Number.NEGATIVE_INFINITY;
for (const rect of rects) {
if (rect.top >= lineBottom - 4) {
lines += 1;
lineBottom = rect.bottom;
} else {
lineBottom = Math.max(lineBottom, rect.bottom);
}
}
return lines;
});
expect(overviewLineCount).toBe(1);
});

test("differentials presentation comparison page stays wired to differentials mode", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 920 });
const workflow = acuteConfusionPresentationWorkflow;
Expand Down