feat(frontend): add SubjectSidebar visibility panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Experiments DB Dev
2026-06-01 09:30:51 -04:00
parent 0f7f6fbbe3
commit 7b34c2fa52
2 changed files with 163 additions and 0 deletions
@@ -0,0 +1,59 @@
import React from 'react';
import { groupLines, groupVisibilityState } from '../lib/crossSubjectChart';
// Presentational panel listing subjects grouped by their group, with per-subject
// and per-group visibility checkboxes. All state lives in the parent; this component
// only renders and calls handlers.
export function SubjectSidebar({
lines, hiddenSubjects, onToggleSubject, onToggleGroup, onShowAll, onHighlight,
}) {
const groups = groupLines(lines);
const anyHidden = lines.some((l) => hiddenSubjects.has(l.id));
return (
<div className="w-[210px] shrink-0 border-l border-gray-100 pl-3">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Subjects</span>
{anyHidden && (
<button type="button" onClick={onShowAll} aria-label="Show all subjects"
className="text-xs text-indigo-500 hover:text-indigo-700">Show all</button>
)}
</div>
<div className="space-y-3 max-h-[320px] overflow-y-auto pr-1">
{groups.map(({ group, subjects }) => {
const ids = subjects.map((s) => s.id);
const state = groupVisibilityState(ids, hiddenSubjects);
return (
<div key={group}>
<label className="flex items-center gap-1.5 text-xs font-medium text-gray-600 cursor-pointer">
<input type="checkbox"
checked={state === 'all'}
ref={(el) => { if (el) el.indeterminate = state === 'some'; }}
onChange={() => onToggleGroup(ids, state === 'all')}
className="accent-indigo-500" />
<span className="truncate">{group}</span>
<span className="text-gray-400">({subjects.length})</span>
</label>
<div className="mt-1 space-y-0.5 pl-1">
{subjects.map((s) => (
<label key={s.id}
onMouseEnter={() => onHighlight(s.id)}
onMouseLeave={() => onHighlight(null)}
className="flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer hover:bg-gray-50 rounded px-1 py-0.5">
<input type="checkbox"
checked={!hiddenSubjects.has(s.id)}
onChange={() => onToggleSubject(s.id)}
className="accent-indigo-500" />
<span className="inline-block w-2.5 h-2.5 rounded-full shrink-0"
style={{ backgroundColor: s.color }} />
<span className="truncate">{s.name}</span>
</label>
))}
</div>
</div>
);
})}
</div>
</div>
);
}