"use client" /** * ExamLockInterruptionPanel — pause surface behind the sliding question card. * * Recoverable pauses use one generic screen. Action pairs match `DialogFooter`: * Retry (ghost) left, **Raise hand (filled primary) right** — pin with `sm:order-1` / `sm:order-2`. */ import * as React from "react" import { EXAM_LOCK_INTERRUPTION_CATEGORY_CHROME, EXAM_LOCK_INTERRUPTION_PRESETS, examLockInterruptionCategory, } from "@/components/exam-lock/exam-lock-interruption-presets" import { ExamLockCautionStrip } from "@/components/exam-lock/exam-lock-caution-strip" import { ExamLockTimerDisplay } from "@/components/exam-lock/exam-lock-timer-display" import type { ExamLockPauseReason } from "@/components/exam-lock/exam-lock-session-types" import { LocalBanner } from "@/components/ui/banner" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" export interface ExamLockInterruptionSupportActions { retry?: { label?: string onClick: () => void disabled?: boolean } connectWithPerson?: { label?: string onConnect: () => void } } export interface ExamLockInterruptionPanelProps { open: boolean pauseReason: ExamLockPauseReason severity?: "hard" | "terminal" title?: string description?: React.ReactNode pausedAtSeconds: number primaryAction?: { label: string onClick: () => void disabled?: boolean /** When true, shows the raised-hand confirmation state. */ raised?: boolean } supportActions?: ExamLockInterruptionSupportActions className?: string } export function ExamLockInterruptionPanel({ open, pauseReason, severity = "hard", title, description, pausedAtSeconds, primaryAction, supportActions, className, }: ExamLockInterruptionPanelProps) { const primaryRef = React.useRef(null) const preset = EXAM_LOCK_INTERRUPTION_PRESETS[pauseReason] const category = examLockInterruptionCategory(pauseReason, severity) const chrome = EXAM_LOCK_INTERRUPTION_CATEGORY_CHROME[category] const recoverable = category === "technical" || category === "integrity" React.useEffect(() => { if (!open) return const id = window.requestAnimationFrame(() => primaryRef.current?.focus()) return () => window.cancelAnimationFrame(id) }, [open, primaryAction?.label]) if (!open) return null const terminal = category === "terminal" const resolvedTitle = title ?? preset.title const resolvedDescription = description ?? preset.description const showCautionStrips = preset.cautionStrips === true const retry = !terminal ? supportActions?.retry : undefined const handRaised = primaryAction?.raised === true const showActions = !terminal && (primaryAction != null || retry != null) const retryLabel = retry?.label ?? preset.retryActionLabel ?? "Retry" const supportRequestedMessage = preset.supportRequestedMessage ?? "Hand raised. Someone from your program will check in with you shortly." return (
{showCautionStrips ? : null} {recoverable ? ( Timer stopped · Answers saved on this device ) : null}
{preset.steps && recoverable ? (

{preset.stepsHeading ?? "What to try"}

    {preset.steps.map((step, index) => (
  1. {step}
  2. ))}
) : null} {!terminal ? (

Time remaining

) : null} {showActions ? (
{retry ? (