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
37 changes: 20 additions & 17 deletions docs/redesign/09-ui-primitives-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,22 @@ Values are tokens; the target sizes follow the `-11`/`-12` spacing scale (L6).

### Interactive controls (tap targets)

| Recipe | Purpose | Tap | Hover | Active | Focus-visible | Disabled |
| ------------------------- | --------------------------------------- | ------------------- | ----------------- | ------ | ------------------------- | -------- |
| `controlBase` | Base for buttons (composed by others) | `min-h-11` | — (subclass adds) | ✅ | ✅ | ✅ |
| `primaryControl` | Primary command button | ✅ | ✅ | ✅ | ✅ | ✅ |
| `floatingControl` | Secondary / floating button | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `toolbarButton` | Square icon button | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `navPill` | Nav / segmented pill | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `chatMicroAction` | Small chat action (copy, retry…) | `min-h-11 min-w-11` | ✅ | ❌ | ✅ | ✅ |
| `sourceCapsule` | Inline citation capsule (button shell) | `min-h-11` | ✅ | ❌ | ✅ (`focus-ring-premium`) | ❌ |
| `sourceCapsuleCountBadge` | Inset count chip inside `sourceCapsule` | — (non-tap) | ❌ | ❌ | ❌ | ❌ |
| `chatComposerIconButton` | Composer icon button | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `chatSendButton` | Composer send (accent) | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `sidebarItem` | Sidebar nav row | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `sidebarToolTile` | Sidebar tool tile | `min-h-[64px]` | ✅ | ❌ | ✅ | ❌ |
| `shellChip` | Filter / mode chip | `min-h-11` | ✅ | ❌ | ❌ | ❌ |
| Recipe | Purpose | Tap | Hover | Active | Focus-visible | Disabled |
| ------------------------- | --------------------------------------------- | ------------------- | ----------------- | ------ | ---------------------------------------- | -------- |
| `controlBase` | Base for buttons (composed by others) | `min-h-11` | — (subclass adds) | ✅ | ✅ | ✅ |
| `primaryControl` | Primary command button | ✅ | ✅ | ✅ | ✅ | ✅ |
| `floatingControl` | Secondary / floating button | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `toolbarButton` | Square icon button | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `navPill` | Nav / segmented pill | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `chatMicroAction` | Small chat action (copy, retry…) | `min-h-11 min-w-11` | ✅ | ❌ | ✅ | ✅ |
| `sourceCapsuleHit` | Citation capsule tap target (button) | `min-h-11` | ✅ (drives face) | ❌ | ✅ (`.source-capsule-hit:focus-visible`) | ❌ |
| `sourceCapsule` | Compact visible pill (`.source-capsule-face`) | — (inside hit) | ✅ (via hit) | ❌ | ✅ (via hit) | ❌ |
| `sourceCapsuleCountBadge` | Inset count chip inside `sourceCapsule` | — (non-tap) | ❌ | ❌ | ❌ | ❌ |
| `chatComposerIconButton` | Composer icon button | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `chatSendButton` | Composer send (accent) | `h-11 w-11` | ✅ | ❌ | ✅ | ✅ |
| `sidebarItem` | Sidebar nav row | `min-h-11` | ✅ | ❌ | ✅ | ✅ |
| `sidebarToolTile` | Sidebar tool tile | `min-h-[64px]` | ✅ | ❌ | ✅ | ❌ |
| `shellChip` | Filter / mode chip | `min-h-11` | ✅ | ❌ | ❌ | ❌ |

### Form fields

Expand Down Expand Up @@ -93,8 +94,10 @@ Overlay: **`sheetSurface`** / `sheetHandle` for the mobile bottom sheet
### Pills, badges, dots, tones

- **Pills:** `metadataPill`, `subtleStatusPill` (`min-h-7`, non-tap labels).
Compose `sourceCapsule` + `sourceCapsuleCountBadge` for the answer sources
disclosure (icon, label, tabular count, chevron).
Compose `sourceCapsuleHit` (44px tap target) wrapping `sourceCapsule` +
`sourceCapsuleCountBadge` for the answer sources disclosure (icon, label,
tabular count, chevron). The visible pill reads compact while the hit target
keeps the WCAG touch size.
- **Status dots:** `statusDotBase` + `statusDotReady` (success) / `statusDotReview`
(warning) / `statusDotMuted`.
- **Semantic tone triads** (border+bg+text, dark-mode-safe): `toneSuccess`,
Expand Down
35 changes: 24 additions & 11 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1947,29 +1947,42 @@ summary::-webkit-details-marker {
box-shadow: none !important;
}

/* Premium hover transitions for source capsules */
.source-capsule-hover {
/* Source capsule: a compact visible face inside an invisible 44px tap target.
Border/shadow/transform live here (component layer) so the face reads small
while the hover, expanded, and focus chrome stay premium. Border colour is
owned here — the face carries no border-colour utility — so these rules win
without !important. */
.source-capsule-face {
border-color: var(--border);
transition:
border-color 150ms ease,
box-shadow 150ms ease,
transform 150ms ease !important;
transform 150ms ease;
}

.source-capsule-hover:hover {
transform: translateY(-1px) !important;
box-shadow: var(--shadow-tight) !important;
.source-capsule-hit:hover .source-capsule-face {
transform: translateY(-1px);
border-color: var(--clinical-accent-border);
box-shadow: var(--shadow-tight);
}

.source-capsule-hover[aria-expanded="true"] {
.source-capsule-hit[aria-expanded="true"] .source-capsule-face {
border-color: var(--clinical-accent);
box-shadow: var(--shadow-tight), var(--shadow-inset);
}

.source-capsule-hover[aria-expanded="true"]:hover {
transform: translateY(-1px) !important;
.source-capsule-hit[aria-expanded="true"]:hover .source-capsule-face {
transform: translateY(-1px);
border-color: var(--clinical-accent);
box-shadow: var(--shadow-tight), var(--shadow-inset);
}

.source-capsule-hit:focus-visible .source-capsule-face {
outline: 2px solid var(--focus);
outline-offset: 2px;
box-shadow: 0 0 0 4px color-mix(in srgb, var(--focus) 25%, transparent);
}

.polished-scroll {
scrollbar-color: color-mix(in srgb, var(--border-strong) 65%, transparent) transparent;
scrollbar-width: thin;
Expand Down Expand Up @@ -2036,8 +2049,8 @@ summary::-webkit-details-marker {
transition-duration: 0.01ms !important;
}

.source-capsule-hover:hover,
.source-capsule-hover[aria-expanded="true"]:hover {
.source-capsule-hit:hover .source-capsule-face,
.source-capsule-hit[aria-expanded="true"]:hover .source-capsule-face {
transform: none !important;
}
}
Expand Down
25 changes: 14 additions & 11 deletions src/components/clinical-dashboard/answer-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
cn,
sourceCapsule,
sourceCapsuleCountBadge,
sourceCapsuleHit,
statusDotMuted,
statusDotReady,
statusDotReview,
Expand Down Expand Up @@ -602,23 +603,25 @@ export function NaturalLanguageAnswer({
<button
type="button"
ref={sourceCapsuleRef}
className={cn(sourceCapsule, "w-fit")}
className={sourceCapsuleHit}
aria-label="Open answer sources"
aria-expanded={sourcePreviewOpen}
onClick={() => {
if (canOpenSourcePreview) setSourcePreviewOpen((current) => !current);
}}
>
<Layers className="h-3.5 w-3.5 shrink-0" aria-hidden />
<span className="min-w-0 truncate">{capsuleDisplay.label}</span>
{capsuleDisplay.showCountBadge ? <span className={sourceCapsuleCountBadge}>{sourceCount}</span> : null}
{canOpenSourcePreview ? (
<ChevronDown
className={cn("h-3.5 w-3.5 shrink-0 transition-transform", sourcePreviewOpen && "rotate-180")}
strokeWidth={2.25}
aria-hidden
/>
) : null}
<span className={sourceCapsule}>
<Layers className="h-3 w-3 shrink-0" aria-hidden />
<span className="min-w-0 truncate">{capsuleDisplay.label}</span>
{capsuleDisplay.showCountBadge ? <span className={sourceCapsuleCountBadge}>{sourceCount}</span> : null}
{canOpenSourcePreview ? (
<ChevronDown
className={cn("h-3 w-3 shrink-0 transition-transform", sourcePreviewOpen && "rotate-180")}
strokeWidth={2.25}
aria-hidden
/>
) : null}
</span>
</button>
);

Expand Down
11 changes: 9 additions & 2 deletions src/components/ui-primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,17 @@ export const chatActionRow =
"flex min-h-tap flex-wrap items-center gap-1.5 text-xs font-semibold text-[color:var(--text-heading)] sm:min-h-8";
export const chatMicroAction =
"inline-flex min-h-tap min-w-tap items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold text-[color:var(--text-muted)] transition 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-50";
// Answer "Sources" capsule. `sourceCapsuleHit` is an invisible 44px WCAG touch
// target that wraps the compact visible pill `sourceCapsule` (`.source-capsule-face`),
// so the control reads smaller and lighter without shrinking the tap area. Hover,
// expanded, and focus chrome are driven from the hit target's :hover /
// [aria-expanded] / :focus-visible in globals.css (@layer components).
export const sourceCapsuleHit =
"source-capsule-hit inline-flex min-h-tap w-fit items-center justify-center rounded-full outline-none";
export const sourceCapsule =
"source-capsule-hover focus-ring-premium inline-flex min-h-tap items-center gap-1.5 rounded-full border border-[color:var(--border)] bg-[color-mix(in_srgb,var(--clinical-accent-soft)_55%,var(--surface))] px-3 text-xs font-medium text-[color:var(--clinical-accent)] transition hover:border-[color:var(--clinical-accent-border)]";
"source-capsule-face inline-flex items-center gap-1.5 rounded-full border bg-[color-mix(in_srgb,var(--clinical-accent-soft)_55%,var(--surface))] px-2.5 py-1 text-2xs font-medium text-[color:var(--clinical-accent)]";
export const sourceCapsuleCountBadge =
"nums inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-[color:var(--surface-raised)] px-1.5 text-2xs font-semibold text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]";
"nums inline-flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full bg-[color:var(--surface-raised)] px-1 text-[10px] font-semibold leading-none text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]";
export const evidenceRow =
"flex min-h-12 w-full items-center justify-between gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-left shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]";
export const clinicalNotesRow =
Expand Down