import { useState, type ReactNode } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCircleCheck, faCircle } from '@fortawesome/pro-duotone-svg-icons'; import { Button } from '@/components/base/buttons/button'; import { cx } from '@/utils/cx'; import { SETUP_STEP_NAMES, SETUP_STEP_LABELS, type SetupStepName, type SetupState } from '@/lib/setup-state'; import { WizardLayoutContext } from './wizard-layout-context'; type WizardShellProps = { state: SetupState; activeStep: SetupStepName; onSelectStep: (step: SetupStepName) => void; onDismiss: () => void; // Form column (middle pane). The active step component renders // its form into this slot. The right pane is filled via the // WizardLayoutContext + a portal — see wizard-step.tsx. children: ReactNode; }; // Layout shell for the onboarding wizard. Three-pane layout: // left — step navigator (fixed width) // middle — form (flexible, the focus column) // right — preview pane fed by the active step component (sticky, // hides below xl breakpoint) // // The whole shell is `fixed inset-0` so the document body cannot // scroll while the wizard is mounted — fixes the double-scrollbar // bug where the body was rendered taller than the viewport and // scrolled alongside the form column. The form and preview columns // each scroll independently inside the shell. // // The header has a "Skip for now" affordance that dismisses the // wizard for this workspace; once dismissed it never auto-shows // again on login. export const WizardShell = ({ state, activeStep, onSelectStep, onDismiss, children, }: WizardShellProps) => { const completedCount = SETUP_STEP_NAMES.filter((s) => state.steps[s].completed).length; const totalSteps = SETUP_STEP_NAMES.length; const progressPct = Math.round((completedCount / totalSteps) * 100); // Callback ref → state — guarantees that consumers re-render once // the aside is mounted (a plain useRef would not propagate the // attached node back through the context). The element is also // updated to null on unmount so the context is always honest about // whether the slot is currently available for portals. const [rightPaneEl, setRightPaneEl] = useState(null); return (
{/* Header — pinned. Progress bar always visible (grey track when 0%), sits flush under the title row. */}

Set up your hospital

{completedCount} of {totalSteps} steps complete · finish setup to start using your workspace

{/* Body — three columns inside a fixed-height flex row. min-h-0 on the row + each column lets the inner overflow-y-auto actually take effect. */}
{/* Left — step navigator. Scrolls if it overflows on very short viewports, but in practice it fits. */} {/* Middle — form column. min-w-0 prevents children from forcing the column wider than its flex basis (long inputs, etc.). overflow-y-auto so it scrolls independently of the right pane. */}
{children}
{/* Right — preview pane. Always rendered as a stable portal target (so the active step's WizardStep can createPortal into it via WizardLayoutContext). Hidden below xl breakpoint (1280px) so the wizard collapses cleanly to two columns on smaller screens. Independent scroll. */}
); };