feat(ui): Enhance Graph and Kanban UX (bb-18e)

- feat(kanban): Add progressive disclosure to task details drawer
- feat(kanban): Fix title layout on mobile (remove flex-row constraint)
- feat(kanban): Add bead count and metadata to epics
- style(globals): Add status color tokens and refined scrollbars
- deps: Add dagre for true DAG layout in graph view
- chore: Update capture scripts
This commit is contained in:
zenchantlive 2026-02-12 23:37:27 -08:00
parent 8490cb1d8c
commit e1f3d48f6e
10 changed files with 5591 additions and 103 deletions

View file

@ -10,11 +10,19 @@ interface KanbanControlsProps {
filters: KanbanFilterOptions;
stats: KanbanStats;
onFiltersChange: (filters: KanbanFilterOptions) => void;
onNextActionable: () => void;
nextActionableFeedback?: string | null;
}
export function KanbanControls({ filters, stats, onFiltersChange }: KanbanControlsProps) {
export function KanbanControls({
filters,
stats,
onFiltersChange,
onNextActionable,
nextActionableFeedback = null,
}: KanbanControlsProps) {
const inputClass =
'rounded-xl border border-border-soft bg-surface-muted/78 px-3 py-2.5 text-sm text-text-strong outline-none transition placeholder:text-text-muted focus:border-border-strong focus:ring-2 focus:ring-white/10';
'ui-field rounded-xl px-3 py-2.5 text-sm outline-none transition';
return (
<section className="grid gap-3">
@ -29,28 +37,28 @@ export function KanbanControls({ filters, stats, onFiltersChange }: KanbanContro
<select
value={filters.type ?? ''}
onChange={(event) => onFiltersChange({ ...filters, type: event.target.value })}
className={`${inputClass} w-full sm:w-44`}
className={`${inputClass} ui-select w-full sm:w-44`}
aria-label="Type filter"
>
<option value="">All types</option>
<option value="task">Task</option>
<option value="bug">Bug</option>
<option value="feature">Feature</option>
<option value="epic">Epic</option>
<option value="chore">Chore</option>
<option className="ui-option" value="">All types</option>
<option className="ui-option" value="task">Task</option>
<option className="ui-option" value="bug">Bug</option>
<option className="ui-option" value="feature">Feature</option>
<option className="ui-option" value="epic">Epic</option>
<option className="ui-option" value="chore">Chore</option>
</select>
<select
value={filters.priority ?? ''}
onChange={(event) => onFiltersChange({ ...filters, priority: event.target.value })}
className={`${inputClass} w-full sm:w-36`}
className={`${inputClass} ui-select w-full sm:w-36`}
aria-label="Priority filter"
>
<option value="">All priorities</option>
<option value="0">P0</option>
<option value="1">P1</option>
<option value="2">P2</option>
<option value="3">P3</option>
<option value="4">P4</option>
<option className="ui-option" value="">All priorities</option>
<option className="ui-option" value="0">P0</option>
<option className="ui-option" value="1">P1</option>
<option className="ui-option" value="2">P2</option>
<option className="ui-option" value="3">P3</option>
<option className="ui-option" value="4">P4</option>
</select>
<label className="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-border-soft bg-surface-muted/60 px-3 py-2 text-sm text-text-body sm:w-auto sm:justify-start">
<input
@ -61,15 +69,25 @@ export function KanbanControls({ filters, stats, onFiltersChange }: KanbanContro
/>
Show closed
</label>
<button
type="button"
onClick={onNextActionable}
className="w-full rounded-xl border border-border-soft bg-surface-muted/70 px-3 py-2 text-sm font-semibold text-text-body transition hover:border-border-strong hover:bg-surface-raised sm:w-auto"
>
Next Actionable
</button>
</motion.div>
<motion.div layout className="flex flex-wrap gap-2">
<StatPill label="Total" value={stats.total} />
<StatPill label="Open" value={stats.open} />
<StatPill label="Ready" value={stats.ready} />
<StatPill label="Active" value={stats.active} />
<StatPill label="Blocked" value={stats.blocked} />
<StatPill label="Done" value={stats.done} />
<StatPill label="P0" value={stats.p0} tone={stats.p0 > 0 ? 'critical' : 'default'} />
</motion.div>
{nextActionableFeedback ? (
<p className="text-xs text-text-muted">{nextActionableFeedback}</p>
) : null}
</section>
);
}