"use client"; /** * Floating bulk-action bar for the vault's multi-select mode. Appears once * at least one document is selected, pinned above the bottom nav on phones * and near the bottom edge on desktop. Carries the selected count and the * bulk verbs — set type, link condition, file against a visit, share (one * link for the whole selection), delete (undo-able), clear — all driving the * page's handlers. * * The two link verbs are the same shape and stay that way: each renders only * when the account HAS something to link to, so an empty menu is never * offered. * * A `role="toolbar"` with proper labels; the destructive verb sits last * before Clear so a stray tap sequence never ends on Delete. Share sits just * before Delete (the safe actions lead). The bar is a deliberate hand-rolled * shell (a floating toolbar is not a Card surface): dense-tile padding `p-3` * per the standards. */ import { CalendarClock, FolderPlus, Share2, Tag, Trash2, X, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import type { InboundDocumentKindValue } from "@/lib/validations/inbound-documents"; import { DOCUMENT_KIND_ICONS, DOCUMENT_KIND_ORDER } from "./document-kind-meta"; export interface BulkEpisodeOption { id: string; label: string; } /** One visit the selection can be filed against. Label resolved by the page. */ export interface BulkEncounterOption { id: string; label: string; } export function DocumentBulkBar({ selectedCount, episodes, encounters, busy, onSetKind, onLinkEpisode, onLinkEncounter, onShare, onDelete, onClear, }: { selectedCount: number; /** The caller's live illness episodes — the link-condition targets. */ episodes: BulkEpisodeOption[]; /** The caller's recent visits — the file-against-a-visit targets. */ encounters: BulkEncounterOption[]; /** A bulk call is in flight — the verbs disable, Clear stays live. */ busy: boolean; onSetKind: (kind: InboundDocumentKindValue) => void; onLinkEpisode: (episodeId: string) => void; onLinkEncounter: (encounterId: string) => void; /** * Share the whole selection as ONE documents-only link. The page caps the * selection at `SHARE_LINK_MAX_DOCUMENTS` and surfaces the over-cap hint — * this handler just opens the share sheet seeded with the selection. */ onShare: () => void; onDelete: () => void; onClear: () => void; }) { const { t } = useTranslations(); return (

{t("documents.selection.count", { count: selectedCount })}

{/* v1.30 mobile audit (DO-1) — five labelled buttons wrapped into a 2–3-row floating slab at ~360 px. Below `sm` every verb collapses to icon-only (`aria-label` carries the accessible name; the visible label returns at `sm+`) — the `document-detail-sheet` footer's established icon-collapse pattern. */} {DOCUMENT_KIND_ORDER.map((kind) => { const Icon = DOCUMENT_KIND_ICONS[kind]; return ( onSetKind(kind)}> {t(`documents.kind.${kind}`)} ); })} {episodes.length > 0 ? ( {episodes.map((episode) => ( onLinkEpisode(episode.id)} > {episode.label} ))} ) : null} {encounters.length > 0 ? ( {encounters.map((encounter) => ( onLinkEncounter(encounter.id)} > {encounter.label} ))} ) : null} {/* Solid destructive (matching the detail sheet's Delete) — the outline variant's destructive text on the card surface fails the WCAG contrast gate. */}
); }