diff --git a/src/components/clinical-dashboard/cross-mode-links.tsx b/src/components/clinical-dashboard/cross-mode-links.tsx index 233c659d4..7fcebb0e5 100644 --- a/src/components/clinical-dashboard/cross-mode-links.tsx +++ b/src/components/clinical-dashboard/cross-mode-links.tsx @@ -23,6 +23,8 @@ function badgeChipTone(tone: CrossModeLinkBadge["tone"]): SemanticChipTone | nul return tone === "clinical" ? "info" : tone; } +type CrossModeLinksVariant = "card" | "compact"; + type CrossModeLinkCardProps = { link: CrossModeLink; Icon: LucideIcon; @@ -77,6 +79,47 @@ function CrossModeLinkCard({ link, Icon, query, onModeSearch }: CrossModeLinkCar ); } +// Compact one-line variant of the card: a small rectangle with the entity +// name and mode label linking to the reference, plus a square trailing +// button that re-runs the search inside that mode. Used where vertical +// space matters (e.g. the documents search page). +function CrossModeLinkChip({ link, Icon, query, onModeSearch }: CrossModeLinkCardProps) { + return ( +
+ logCrossModeLinkOpen(query, link)} + className="inline-flex min-h-11 min-w-0 items-center gap-2 px-2.5 transition hover:text-[color:var(--clinical-accent)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[color:var(--focus)] md:min-h-9" + > + + + + + {link.title} + + + {link.modeLabel} + + + +
+ ); +} + // Self-contained cross-mode links surface: owns the catalog fetching (same // owner-scoped APIs the modes use; fixtures in demo mode), entity matching, // and the strip. Mount it under any search-results surface and pass the @@ -85,11 +128,13 @@ export function CrossModeLinksSection({ queries, enabled = true, onModeSearch, + variant = "card", }: { queries: Array; enabled?: boolean; // Defaults to navigating to the target mode with the search pre-run. onModeSearch?: (mode: AppModeId, query: string) => void; + variant?: CrossModeLinksVariant; }) { const router = useRouter(); const services = useRegistryRecords("service", { enabled }); @@ -131,28 +176,36 @@ export function CrossModeLinksSection({ router.push(appModeHomeHref(mode, { query, focus: true, run: true })); }); - return ; + return ; } export function CrossModeLinksStrip({ links, onModeSearch, query = "", + variant = "card", }: { links: CrossModeLink[]; onModeSearch: (mode: AppModeId, query: string) => void; // The search text that produced the links; used only for click telemetry. query?: string; + variant?: CrossModeLinksVariant; }) { if (links.length === 0) return null; + const compact = variant === "compact"; + const LinkItem = compact ? CrossModeLinkChip : CrossModeLinkCard; + return (
-

+

Also in your library {links.length > 1 ? ( @@ -164,13 +217,20 @@ export function CrossModeLinksStrip({

1 ? 0 : undefined} + aria-label={compact && links.length > 1 ? "Related library matches; scroll horizontally for more" : undefined} + className={cn( + "cross-mode-links-rail", + compact + ? "polished-scroll flex min-w-0 items-center gap-2 overflow-x-auto overscroll-x-contain pb-1 md:pb-0" + : "grid min-w-0 gap-1.5 md:flex md:max-w-full md:flex-wrap md:gap-2", + )} data-testid="cross-mode-links-rail" > {links.map((link) => { const Icon = appModeIcons[link.modeId]; return ( - +
- ); -} - -function DocumentSearchCategoryRail() { - return ( - - ); -} - function DocumentEvidencePills({ row }: { row: DocumentSearchRow }) { const hasEvidence = row.tableCount > 0 || row.imageCount > 0; return ( @@ -461,7 +384,7 @@ function SearchResultMobileCard({ row, selected }: { row: DocumentSearchRow; sel : "border-[color:var(--border)]", )} > -
+
- {selected ? Best match : } + {selected ? : null}
- +
-
- {!hideSidebar ? ( - - ) : null} -
{children}
-
+
{children}
); } @@ -782,8 +693,7 @@ export function MasterDocumentSearch() { return ( -
- +
@@ -803,20 +713,6 @@ export function MasterDocumentSearch() { Sample · 2,065 indexed - - Save search - -
@@ -831,194 +727,153 @@ export function MasterDocumentSearch() { key={key} type="button" onClick={() => setType(key as "all" | EvidenceType)} + aria-pressed={type === key} className={cn( - "inline-flex min-h-9 items-center gap-2 rounded-lg border px-3 text-xs font-bold shadow-[var(--shadow-inset)]", + "inline-flex min-h-11 shrink-0 items-center gap-2 rounded-lg border px-3 text-xs font-bold shadow-[var(--shadow-inset)] transition sm:min-h-9", focusRing, type === key ? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]" - : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-heading)]", + : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-heading)] hover:border-[color:var(--border-strong)]", )} > {label} ))} - - -
- - - Table - - - - List - -
-
-
-

- {loading ? "Searching…" : `${filtered.length} results`} -

-
+

+ {loading ? ( + "Searching…" + ) : ( + <> + {filtered.length}{" "} + {filtered.length === 1 ? "result" : "results"} + + )} +

- +
-
- Document - Evidence - Status - Relevance - Actions -
- {loading ? ( -
-
- ) : errored ? ( -
-

- Search is unavailable right now -

-

Retry in a moment.

-
- ) : filtered.length === 0 ? ( -
-

- No documents match “{query}” -

-

- Try a broader search term{type !== "all" ? " or reset the evidence filter" : ""}. -

- {type !== "all" ? ( - - ) : null} +
+
+ Document + Evidence + Status + Relevance + Action
- ) : ( - filtered.map((row, index) => ( -
-
- -
-
- {index === 0 ? Best match : Relevant} -
- - {row.title} - -

- {row.metaLine} - {row.version ? ` · ${row.version}` : ""} -

-
-
-
- -

{row.snippet}

-
-
- {row.statusLabel ? ( - {row.statusLabel} - ) : ( - - )} - {row.reviewLine ? ( -

{row.reviewLine}

- ) : null} - {row.updatedLine ? ( -

{row.updatedLine}

- ) : null} -
-
-

- {row.relevance}% -

-
-
-
-
-
- +
+ ) : errored ? ( +
+

+ Search is unavailable right now +

+

Retry in a moment.

+
+ ) : filtered.length === 0 ? ( +
+

+ No documents match “{query}” +

+

+ Try a broader search term{type !== "all" ? " or reset the evidence filter" : ""}. +

+ {type !== "all" ? ( + -
-
- )) - )} + ) : null} +
+ ) : ( + filtered.map((row, index) => { + const featured = index === 0; + return ( +
+
+ +
+ {featured ? : null} + + {row.title} + + +
+
+
+ +

+ {row.snippet} +

+
+
+ {row.statusLabel ? ( + {row.statusLabel} + ) : ( + + )} + {row.reviewLine ? ( +

{row.reviewLine}

+ ) : null} +
+
+

+ {row.relevance}% +

+ +
+ + Open +
+
+ ); + }) + )} +
@@ -1680,7 +1535,7 @@ export function MasterEvidenceDetail() { if (!evidence) { return ( - +

No extracted evidence

@@ -1730,7 +1585,7 @@ function MasterEvidenceDetailContent({ ); return ( - +
@@ -2061,7 +1916,7 @@ function MasterEvidenceDetailContent({ export function MasterDocumentIndex() { return ( - +