Skip to content
File

Blob: worker/src/client/exhibit/branch-panel.tsx

typescript66 lines
1import type { ReactNode } from 'react';
2import { Icon } from '@/client/ui/icons.tsx';
3import { anchors, branchStyles } from './diagram/diagram-model.ts';
4import type { Branch, ExplanationTrigger } from './diagram/diagram-model.ts';
5 
6type HeadingProps = ExplanationTrigger & {
7 branch: Branch;
8 title: string;
9 caption: string;
10 live: boolean;
11};
12 
13/** Common branch layout; each panel supplies its own controls and content. */
14export function BranchPanel({
15 children,
16 label,
17 ...heading
18}: HeadingProps & { label: string; children: ReactNode }) {
19 const { branch, expanded } = heading;
20 return (
21 <section
22 data-anchor={anchors.receivers[branch]}
23 className={`relative rounded-lg border border-line bg-leaf p-4 shadow-xs sm:p-5 before:absolute before:top-9 before:-left-3.5 before:h-px before:w-3.5 before:bg-(--branch) sm:before:-left-[18px] sm:before:w-[18px] lg:before:hidden ${branchStyles[branch].variable} ${expanded ? 'border-(--branch)' : ''}`}
24 aria-label={label}
25 >
26 <BranchHeading {...heading} />
27 {children}
28 </section>
29 );
30}
31 
32function BranchHeading({ branch, title, caption, expanded, onSelect, live }: HeadingProps) {
33 return (
34 <div className="mb-3.5 flex items-center justify-between gap-2">
35 <button
36 className="flex min-h-10 items-center gap-2 text-left sm:gap-2.5 [&_h2]:text-base [&_h2]:font-medium [&_h2]:leading-tight [&_h2]:tracking-tight sm:[&_h2]:text-lg"
37 aria-haspopup="dialog"
38 aria-expanded={expanded}
39 aria-controls={expanded ? 'signal-detail' : undefined}
40 onClick={(event) => onSelect(event.currentTarget)}
41 >
42 <span
43 className={`flex size-7 shrink-0 items-center justify-center rounded-md bg-[color-mix(in_oklch,var(--branch)_8%,transparent)] text-(--branch) sm:size-8 `}
44 >
45 <Icon name={branchStyles[branch].icon} />
46 </span>
47 <span>
48 <h2>{title}</h2>
49 <span className="mt-1 block font-mono text-[8px] tracking-wide text-muted">
50 {caption}
51 </span>
52 </span>
53 <span className="text-sm text-muted" aria-hidden="true">
54 ↗
55 </span>
56 </button>
57 <span
58 className={`flex items-center gap-1 font-mono text-[8px] whitespace-nowrap text-muted [&_i]:size-1 [&_i]:rounded-full [&_i]:bg-current ${live ? 'text-(--branch)' : ''}`}
59 >
60 <i />
61 {live ? 'LIVE' : 'IDLE'}
62 </span>
63 </div>
64 );
65}