diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index 3c97e63937..9e79fed409 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -976,7 +976,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `AnswerFooter` | answer | yes | yes | inherited-global-root | yes | no | 1 | | `AsyncButton` | controls | yes | yes | inherited-global-root | yes | no | 4 | | `Breadcrumb` | layout | yes | yes | inherited-global-root | yes | no | 1 | -| `Button` | controls | yes | yes | inherited-global-root | yes | no | 16 | +| `Button` | controls | yes | yes | inherited-global-root | yes | no | 17 | | `Checkbox` | controls | yes | yes | no | yes | no | 0 | | `Chip` | controls | yes | yes | inherited-global-root | yes | no | 5 | | `ChoiceChip` | controls | yes | yes | inherited-global-root | yes | no | 4 | @@ -1012,7 +1012,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `SearchField` | controls | yes | yes | no | yes | no | 0 | | `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 8 | | `Select` | controls | yes | yes | inherited-global-root | yes | no | 2 | -| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 30 | +| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 31 | | `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 6 | | `SourceDesignationBadge` | source | yes | yes | inherited-global-root | yes | no | 4 | | `SourceProvenance` | source | yes | yes | inherited-global-root | yes | no | 1 | diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index efa4e37396..05be5ff13c 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -281,6 +281,7 @@ "src/components/AccessibleTable.tsx", "src/components/clinical-dashboard/signed-image.tsx", "src/components/privacy-quiet-signal-page.tsx", + "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/compare-action.tsx", "src/components/therapy-compass/screens/brief-screen.tsx", "src/components/therapy-compass/screens/compare-screen.tsx", @@ -299,6 +300,7 @@ "src/components/AccessibleTable.tsx", "src/components/clinical-dashboard/signed-image.tsx", "src/components/privacy-quiet-signal-page.tsx", + "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/compare-action.tsx", "src/components/therapy-compass/screens/brief-screen.tsx", "src/components/therapy-compass/screens/compare-screen.tsx", @@ -423,13 +425,13 @@ "directImportFiles": [ "src/components/clinical-dashboard/patient-profile-panel.tsx", "src/components/clinical-dashboard/result-filter-control.tsx", - "src/components/therapy-compass/screens/recommend-screen.tsx", + "src/components/therapy-compass/recommend-scenario-fields.tsx", "src/components/therapy-compass/screens/sheets-screen.tsx" ], "productImportFiles": [ "src/components/clinical-dashboard/patient-profile-panel.tsx", "src/components/clinical-dashboard/result-filter-control.tsx", - "src/components/therapy-compass/screens/recommend-screen.tsx", + "src/components/therapy-compass/recommend-scenario-fields.tsx", "src/components/therapy-compass/screens/sheets-screen.tsx" ], "designSync": { @@ -1661,6 +1663,7 @@ "src/components/forms/form-priority-facts-section.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", + "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/key-facts.tsx", "src/components/therapy-compass/therapy-compare-tray.tsx", "src/components/tools/tools-search-results-page.tsx", @@ -1694,6 +1697,7 @@ "src/components/forms/form-priority-facts-section.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", + "src/components/therapy-compass/recommend-scenario-control.tsx", "src/components/therapy-compass/record/key-facts.tsx", "src/components/therapy-compass/therapy-compare-tray.tsx", "src/components/tools/tools-search-results-page.tsx", diff --git a/src/components/therapy-compass/recommend-scenario-control.tsx b/src/components/therapy-compass/recommend-scenario-control.tsx new file mode 100644 index 0000000000..994fb6bf5d --- /dev/null +++ b/src/components/therapy-compass/recommend-scenario-control.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useId, useRef, useState } from "react"; +import { Pencil, Sparkles } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { InteractiveRow } from "@/components/ui/interactive-row"; +import { Sheet } from "@/components/ui/sheet"; +import { cn } from "@/components/ui-primitives"; + +import { RECOMMEND_CONSTRAINTS } from "./data/select"; +import { RecommendScenarioFields, type RecommendScenarioFieldsProps } from "./recommend-scenario-fields"; + +type RecommendScenarioControlProps = RecommendScenarioFieldsProps & { + loading: boolean; + matchCount: number; +}; + +const PLACEHOLDER = "Describe the clinical situation…"; + +function truncatePreview(text: string, maxLength = 72) { + const trimmed = text.trim(); + if (!trimmed) return ""; + if (trimmed.length <= maxLength) return trimmed; + return `${trimmed.slice(0, maxLength - 1).trimEnd()}…`; +} + +export function RecommendScenarioControl({ + recQuery, + setRecQuery, + isConstraintActive, + isConstraintInferred, + toggleConstraint, + idPrefix, + loading, + matchCount, +}: RecommendScenarioControlProps) { + const [sheetOpen, setSheetOpen] = useState(false); + const panelId = useId(); + const triggerRef = useRef(null); + + const activeConstraintCount = RECOMMEND_CONSTRAINTS.filter((constraint) => isConstraintActive(constraint.key)).length; + const preview = truncatePreview(recQuery); + const matchLabel = loading + ? "Ranking clinical matches…" + : `${matchCount} ranked ${matchCount === 1 ? "match" : "matches"}`; + + return ( +
+ setSheetOpen(true)} + className="w-full gap-3 px-3 py-2.5" + > + + + Clinical scenario + + {preview || PLACEHOLDER} + + + {activeConstraintCount > 0 ? ( + + {activeConstraintCount} + + ) : null} + + {activeConstraintCount > 0 + ? `${activeConstraintCount} constraint${activeConstraintCount === 1 ? "" : "s"} active` + : "No constraints active"} + . Opens clinical scenario editor. + + + + setSheetOpen(false)} + returnFocusRef={triggerRef} + title="Clinical scenario" + descriptionContent={ +

+ Describe the presentation and constraints. Rankings update as you type. +

+ } + headerClassName="items-start px-4 py-3 sm:px-5 sm:py-3.5" + titleClassName="text-base sm:text-lg" + closeButtonClassName={cn( + "grid h-tap w-tap shrink-0 place-items-center rounded-lg text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)] motion-reduce:transition-none", + "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]", + )} + portal + placement="responsive-right" + id={panelId} + testId="therapy-recommend-scenario-panel" + footer={ +
+ + {matchLabel} + + +
+ } + > +
event.preventDefault()}> + + +
+
+ ); +} diff --git a/src/components/therapy-compass/recommend-scenario-fields.tsx b/src/components/therapy-compass/recommend-scenario-fields.tsx new file mode 100644 index 0000000000..01a9275faa --- /dev/null +++ b/src/components/therapy-compass/recommend-scenario-fields.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { cn, eyebrowText, searchShell, searchShellInput } from "@/components/ui-primitives"; +import { ChoiceChip } from "@/components/ui/chip"; + +import { RECOMMEND_CONSTRAINT_GROUPS, RECOMMEND_CONSTRAINTS } from "./data/select"; + +export type RecommendScenarioFieldsProps = { + recQuery: string; + setRecQuery: (q: string) => void; + isConstraintActive: (key: string) => boolean; + isConstraintInferred: (key: string) => boolean; + toggleConstraint: (key: string) => void; + /** Prefix for label/input ids — must be unique per mount (desktop vs sheet). */ + idPrefix: string; +}; + +export function RecommendScenarioFields({ + recQuery, + setRecQuery, + isConstraintActive, + isConstraintInferred, + toggleConstraint, + idPrefix, +}: RecommendScenarioFieldsProps) { + const situationId = `${idPrefix}-rec-q`; + + return ( + <> + +
+