SetupRail.tsx3.7 KBView on GitHub import {
RailActiveIndicator,
railIconClass,
railRowClass,
} from '@/modules/conversations/components/rail-primitives';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import type { SetupSectionId, SetupStep } from '../types';
import { SETUP_SECTIONS } from '../constants';
import { cn } from '@/lib/utils';
interface SetupRailProps {
/** The steps actually in play — connect steps depend on the org's integrations. */
steps: SetupStep[];
currentIndex: number;
/** Jump back to an already-reached step. Future steps are inert. */
onSelectStep: (index: number) => void;
className?: string;
}
/**
* The setup rail — the app's own collapsed left rail, standing in for a progress
* bar. Same row class, same sliding indicator, same icon colours as the real one,
* so a user who learns this rail has learned the app's.
*
* Sections ahead of the user are blurred and inert rather than hidden: hiding them
* would make the flow feel open-ended, while leaving them legible-but-out-of-focus
* shows the whole shape of the product and says which part is next.
*/
export function SetupRail({ steps, currentIndex, onSelectStep, className }: SetupRailProps) {
const currentSection = steps[currentIndex]?.section;
const indexed = steps.map((step, index) => ({ step, index }));
const stepsIn = (section: SetupSectionId) =>
indexed.filter(({ step }) => step.section === section);
return (
// Its own provider: onboarding renders under the full-width layout, which mounts
// none (only `(routes)/layout.tsx` does), and a bare Radix Tooltip throws without
// one. Same reason ComposerActionButton carries its own.
<TooltipProvider delayDuration={0}>
<nav
className={cn(
'bg-sidebar dark:bg-sidebar flex h-full shrink-0 flex-col items-center gap-2 p-2',
className,
)}
aria-label="Setup steps"
>
<img
src="/CedarLogoTransparent.png"
alt="Cedar"
className="mb-2 h-6 w-6 shrink-0 brightness-0 dark:invert"
/>
{SETUP_SECTIONS.map((section) => {
const sectionSteps = stepsIn(section.id);
// A section whose steps were all filtered out (no CRM installed, say) is not
// a stage of this user's setup and must not appear at all.
if (sectionSteps.length === 0) return null;
const entryIndex = sectionSteps[0]!.index;
const isCurrent = section.id === currentSection;
const isFuture = currentIndex < entryIndex;
const Icon = section.icon;
return (
<Tooltip key=[redacted]
<TooltipTrigger asChild>
<button
type="button"
disabled={isFuture}
onClick={() => onSelectStep(entryIndex)}
aria-label={section.label}
aria-current={isCurrent ? 'step' : undefined}
className={cn(
railRowClass(false),
isFuture && 'pointer-events-none blur-[2px]',
!isFuture && 'cursor-pointer',
)}
>
{isCurrent && <RailActiveIndicator layoutId="setup-rail-indicator" />}
<Icon className={railIconClass(isCurrent, section.isCustomIcon)} />
</button>
</TooltipTrigger>
{!isFuture && (
<TooltipContent side="right" variant="sidebar">
<span className="text-sm font-medium">{section.label}</span>
</TooltipContent>
)}
</Tooltip>
);
})}
</nav>
</TooltipProvider>
);
}