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
106 changes: 106 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,112 @@ summary::-webkit-details-marker {
color: var(--clinical-accent);
}

.answer-suggestion-row {
display: flex;
min-height: 1.5rem;
align-items: center;
gap: 0.5rem;
padding-inline: 0.125rem;
}

.answer-suggestion-row-scroll {
min-width: 0;
}

.answer-suggestion-label {
color: var(--text-soft);
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.01em;
}

.answer-suggestion-chips {
display: flex;
min-width: 0;
gap: 0.375rem;
}

.answer-suggestion-chips-wrap {
flex-wrap: wrap;
}

.answer-suggestion-chips-scroll {
flex-wrap: nowrap;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
-ms-overflow-style: none;
mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent);
-webkit-mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent);
}

@media (min-width: 640px) {
.answer-suggestion-chips-scroll {
flex-wrap: wrap;
overflow-x: visible;
mask-image: none;
-webkit-mask-image: none;
}
}

.answer-suggestion-chips-scroll::-webkit-scrollbar {
display: none;
}

.answer-suggestion-chip {
display: inline-flex;
max-width: min(100%, 18rem);
min-height: 1.75rem;
flex-shrink: 0;
align-items: center;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--border) 88%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--surface) 92%, transparent);
padding-inline: 0.625rem;
color: var(--text-muted);
font-size: 0.6875rem;
font-weight: 600;
line-height: 1.25rem;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
transition:
border-color 160ms ease,
background-color 160ms ease,
color 160ms ease;
}

.answer-suggestion-chip:hover:not(:disabled) {
border-color: var(--clinical-accent-border);
background: var(--clinical-accent-soft);
color: var(--clinical-accent);
}

.answer-suggestion-chip:disabled {
cursor: not-allowed;
}

.answer-footer-search-edge .answer-suggestion-row {
padding-inline: 0.25rem;
}

.answer-footer-search-edge .answer-suggestion-row-composer-followups {
margin-bottom: -0.125rem;
}

@media (min-width: 640px) {
.answer-suggestion-chip {
font-size: 0.75rem;
}
}

@media (max-width: 639px) {
.answer-footer-search-dock .answer-suggestion-chip {
background: var(--surface);
}
}

.dark .answer-footer-search-action,
.dark .answer-footer-search-chip {
color: var(--text-muted);
Expand Down
12 changes: 10 additions & 2 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3190,13 +3190,16 @@
});
}

function stageAnswerFollowUpDraft(draft: string) {

Check warning on line 3193 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'stageAnswerFollowUpDraft' is defined but never used
setQuery(draft);
focusComposerInput();
}

function handleFollowUpQuote(quote: QuoteCard) {
stageAnswerFollowUpDraft(createQuoteFollowUp(quote));
setQuery(createQuoteFollowUp(quote));
window.requestAnimationFrame(() => {
window.setTimeout(() => focusComposerInput(), 120);
});
}

function handlePickFollowUpSuggestion(suggestion: string) {
Expand Down Expand Up @@ -3787,6 +3790,9 @@
void ask();
}}
onCrossModeSearch={crossModeSearch}
composerFollowUpSuggestions={searchMode === "answer" ? answerFollowUpSuggestions : undefined}
onPickComposerFollowUpSuggestion={handlePickFollowUpSuggestion}
composerFollowUpSuggestionsDisabled={loading}
composerPlaceholder={searchMode === "answer" && latestAnswerQuery ? "Ask a follow-up..." : undefined}
mobileSearchPlacement={hasMobileBottomSearch ? "bottom" : "default"}
mobileBottomSearchVariant={compactMobileBottomSearch ? "compact" : "default"}
Expand All @@ -3810,7 +3816,9 @@
searchMode === "answer"
? compactMobileModeHome
? "mb-0"
: "mb-[calc(5.25rem+env(safe-area-inset-bottom))] sm:mb-24"
: answer && answerFollowUpSuggestions.length > 0
? "mb-[calc(11.5rem+env(safe-area-inset-bottom))] sm:mb-24"
: "mb-[calc(6.5rem+env(safe-area-inset-bottom))] sm:mb-24"
: hasMobileBottomSearch
? compactMobileBottomSearch
? differentialsCompareAddonActive
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,31 @@
"use client";

import { Sparkles } from "lucide-react";

import { cn, subtleStatusPill } from "@/components/ui-primitives";
import { AnswerSuggestionChips } from "@/components/clinical-dashboard/answer-suggestion-chips";

export function AnswerFollowUpSuggestions({
suggestions,
onPick,
disabled = false,
className,
testId = "answer-follow-up-suggestions",
layout = "wrap",
}: {
suggestions: string[];
onPick: (suggestion: string) => void;
disabled?: boolean;
className?: string;
testId?: string;
layout?: "wrap" | "scroll";
}) {
if (!suggestions.length) return null;

return (
<div
data-testid="answer-follow-up-suggestions"
className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-subtle)]/70 p-2.5 sm:p-3"
>
<div className="mb-2 flex items-center gap-2">
<span className={cn(subtleStatusPill, "inline-flex min-h-6 items-center gap-1 px-2 text-[11px]")}>
<Sparkles className="h-3 w-3" aria-hidden="true" />
Suggested follow-ups
</span>
</div>
<div className="flex flex-wrap gap-2">
{suggestions.map((suggestion) => (
<button
key={suggestion}
type="button"
disabled={disabled}
onClick={() => onPick(suggestion)}
className={cn(
"max-w-full rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1.5 text-left text-xs font-semibold leading-5 text-[color:var(--text)] transition",
"hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--clinical-accent-soft)] 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-60",
)}
>
{suggestion}
</button>
))}
</div>
</div>
<AnswerSuggestionChips
suggestions={suggestions}
onPick={onPick}
disabled={disabled}
label="Try next"
testId={testId}
layout={layout}
className={className}
/>
);
}
19 changes: 13 additions & 6 deletions src/components/clinical-dashboard/answer-result-surface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export function StagedAnswerResultSurface({
setEvidenceInitialTab(null);
restoreFocusToTrigger(evidenceTriggerRef);
}
function handleQuoteFollowUp(quote: QuoteCard) {
setEvidenceOpen(false);
setEvidenceInitialTab(null);
onFollowUpQuote?.(quote);
}
function openTableEvidence() {
setClinicalNotesOpen(false);
setSafetyFindingsOpen(false);
Expand Down Expand Up @@ -230,11 +235,13 @@ export function StagedAnswerResultSurface({
) : null}

{followUpSuggestions?.length && onPickFollowUpSuggestion ? (
<AnswerFollowUpSuggestions
suggestions={followUpSuggestions}
onPick={onPickFollowUpSuggestion}
disabled={followUpSuggestionsDisabled}
/>
<div className="hidden sm:block">
<AnswerFollowUpSuggestions
suggestions={followUpSuggestions}
onPick={onPickFollowUpSuggestion}
disabled={followUpSuggestionsDisabled}
/>
</div>
) : null}
</div>

Expand Down Expand Up @@ -327,7 +334,7 @@ export function StagedAnswerResultSurface({
copiedQuotes={copiedQuotes}
onCopyQuotes={copyQuotes}
onSubmitFeedback={onSubmitFeedback}
onFollowUpQuote={onFollowUpQuote}
onFollowUpQuote={handleQuoteFollowUp}
onScopeDocument={onScopeDocument}
/>
</Sheet>
Expand Down
54 changes: 31 additions & 23 deletions src/components/clinical-dashboard/answer-status.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";

import { Clipboard, ClipboardCheck, MessageSquareText, Search, ShieldCheck, Sparkles, UploadCloud } from "lucide-react";
import { Clipboard, ClipboardCheck, MessageSquareText, ShieldCheck } from "lucide-react";

import { AnswerSuggestionChips } from "@/components/clinical-dashboard/answer-suggestion-chips";
import { ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { cn, floatingControl, sourceCard } from "@/components/ui-primitives";
import { answerEmptyState, answerLoading, copyButton } from "@/lib/ui-copy";
Expand Down Expand Up @@ -34,7 +35,7 @@
}

export function AnswerEmptyState({
onPickSample,
onPickSample: _onPickSample,

Check warning on line 38 in src/components/clinical-dashboard/answer-status.tsx

View workflow job for this annotation

GitHub Actions / verify

'_onPickSample' is defined but never used
onSearchDocuments,
onUploadDocument,
desktopComposerSlotId,
Expand All @@ -44,6 +45,21 @@
onUploadDocument: () => void;
desktopComposerSlotId?: string;
}) {
const quickActions = [
answerEmptyState.starters.searchDocuments.title,
answerEmptyState.starters.uploadDocument.title,
];

function handleQuickAction(action: string) {
if (action === answerEmptyState.starters.searchDocuments.title) {
onSearchDocuments();
return;
}
if (action === answerEmptyState.starters.uploadDocument.title) {
onUploadDocument();
}
}

return (
<ModeHomeTemplate
testId="answer-empty-state"
Expand All @@ -53,27 +69,19 @@
headingLevel={2}
desktopComposerSlotId={desktopComposerSlotId}
actionsLabel={answerEmptyState.starterActionsLabel}
actions={[
{
title: answerEmptyState.starters.ask.title,
description: answerEmptyState.starters.ask.description,
icon: Sparkles,
onClick: () => onPickSample(answerEmptyState.starters.ask.samplePrompt),
},
{
title: answerEmptyState.starters.searchDocuments.title,
description: answerEmptyState.starters.searchDocuments.description,
icon: Search,
onClick: onSearchDocuments,
},
{
title: answerEmptyState.starters.uploadDocument.title,
description: answerEmptyState.starters.uploadDocument.description,
icon: UploadCloud,
onClick: onUploadDocument,
},
]}
footer={<ModeHomeVerificationFooter icon={ShieldCheck} label="Source backed" body="Clinical Guide library" />}
actions={[]}
footer={
<div className="grid w-full gap-3">
<AnswerSuggestionChips
suggestions={quickActions}
onPick={handleQuickAction}
label={answerEmptyState.quickActionsLabel}
layout="wrap"
className="justify-center"
/>
<ModeHomeVerificationFooter icon={ShieldCheck} label="Source backed" body="Clinical Guide library" />
</div>
}
/>
);
}
Expand Down
55 changes: 55 additions & 0 deletions src/components/clinical-dashboard/answer-suggestion-chips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";

import { cn } from "@/components/ui-primitives";

const focusRing =
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]";

export function AnswerSuggestionChips({
suggestions,
onPick,
disabled = false,
label,
testId,
layout = "wrap",
className,
}: {
suggestions: string[];
onPick: (suggestion: string) => void;
disabled?: boolean;
label?: string;
testId?: string;
layout?: "wrap" | "scroll";
className?: string;
}) {
if (!suggestions.length) return null;

return (
<div
data-testid={testId}
className={cn("answer-suggestion-row", layout === "scroll" && "answer-suggestion-row-scroll", className)}
>
{label ? <span className="answer-suggestion-label shrink-0">{label}</span> : null}
<div
className={cn(
"answer-suggestion-chips",
layout === "scroll" ? "answer-suggestion-chips-scroll" : "answer-suggestion-chips-wrap",
)}
role={label ? undefined : "group"}
aria-label={label ? undefined : "Suggested questions"}
>
{suggestions.map((suggestion) => (
<button
key={suggestion}
type="button"
disabled={disabled}
onClick={() => onPick(suggestion)}
className={cn("answer-suggestion-chip", focusRing, disabled && "opacity-60")}
>
{suggestion}
</button>
))}
</div>
</div>
);
}
Loading
Loading