feat(kanban): wide board animates, keeps header width and caps column width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-31 10:04:16 +01:00
1 parent 25bb787a8e
commit 945be2c1ad
6 files changed
+30 -19

No files matched your search

+1 -6
View File
@@ -164,8 +164,6 @@ export const App: React.FC = () => {
const [deck, setDeck] = useState<Thought[]>([]);
const [lists, setLists] = useState<SharedList[]>([]);
const [activeTab, setActiveTab] = useState<ActiveTab>('triage');
// Kanban asked for the full window width (reported by the tasks view)
const [tasksWide, setTasksWide] = useState(false);
const [previousTab, setPreviousTab] = useState<ActiveTab>('triage');
const [selectedDocId, setSelectedDocId] = useState<string | null>(null);
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null);
@@ -1170,9 +1168,7 @@ export const App: React.FC = () => {
{/* Main Container */}
{/* The workspace fills the window exactly and scrolls only inside the note, never the page */}
<main
className={`flex-1 w-full mx-auto px-3 sm:px-4 pt-3 pb-24 ${
activeTab === 'tasks' && tasksWide ? 'lg:max-w-none lg:px-6' : 'max-w-6xl'
} ${
className={`flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 ${
activeTab === 'workspace' ? 'min-h-0 flex flex-col sm:pb-0' : 'sm:pb-6'
}`}
>
@@ -1196,7 +1192,6 @@ export const App: React.FC = () => {
refreshTrigger={refreshRevision}
initialSelectedTaskId={selectedTaskId}
openTasksCount={stats.tasksOpenCount}
onWideChange={setTasksWide}
/>
)}
+18 -8
View File
@@ -62,10 +62,25 @@ interface TasksViewProps {
refreshTrigger?: number;
initialSelectedTaskId?: string | null;
openTasksCount?: number; // all open tasks incl. shared lists (same as the nav bar counter)
onWideChange?: (wide: boolean) => void; // Kanban stretched to the full window width
}
const KANBAN_WIDE_KEY = 'mindsift:kanban_wide';
// Wide Kanban: a column grows to at most this width, the board stays centred
const KANBAN_COL_MAX = 380;
const KANBAN_GAP = 12; // gap-3
// Only the board leaves the page column (header and "new task" keep their width): its width follows
// the columns (at most KANBAN_COL_MAX each, at most the window) and a margin keeps it centred – a
// negative one when it is wider than the page column. Width and margin are animated.
const kanbanBoardStyle = (wide: boolean, columns: number): React.CSSProperties => {
if (!wide) return { '--kanban-w': '100%', '--kanban-ml': '0px' } as React.CSSProperties;
const w = `min(100vw - 4rem, ${columns * KANBAN_COL_MAX + (columns - 1) * KANBAN_GAP}px)`;
return {
'--kanban-cols': columns,
'--kanban-w': w,
'--kanban-ml': `calc((100% - ${w}) / 2)`,
} as React.CSSProperties;
};
type TasksSession = { listId: string; viewMode: 'list' | 'kanban' };
@@ -76,7 +91,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
refreshTrigger = 0,
initialSelectedTaskId,
openTasksCount,
onWideChange,
}) => {
// Open list and view survive switching to other tabs (like the workspace's open note)
const [viewMode, setViewMode] = useState<'list' | 'kanban'>(() =>
@@ -100,10 +114,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
}
};
const isKanbanWide = viewMode === 'kanban' && kanbanWide;
useEffect(() => {
onWideChange?.(isKanbanWide);
}, [isKanbanWide, onWideChange]);
useEffect(() => () => onWideChange?.(false), [onWideChange]);
const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open');
const [tasks, setTasks] = useState<Thought[]>([]);
const [taskLists, setTaskLists] = useState<TaskList[]>([]);
@@ -2214,10 +2224,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
/* DYNAMIC KANBAN BOARD */
<div className="space-y-2">
<div
className={`grid grid-cols-1 md:grid-cols-2 gap-3 pt-1 ${
className={`grid grid-cols-1 md:grid-cols-2 gap-3 pt-1 lg:w-[var(--kanban-w)] lg:ml-[var(--kanban-ml)] transition-[width,margin] duration-300 ease-out ${
isKanbanWide ? 'lg:grid-cols-[repeat(var(--kanban-cols),minmax(0,1fr))]' : 'lg:grid-cols-4'
}`}
style={isKanbanWide ? ({ '--kanban-cols': activeColumns.length } as React.CSSProperties) : undefined}
style={kanbanBoardStyle(isKanbanWide, activeColumns.length)}
>
{activeColumns.map((col, colIdx) => {
const colTasks = kanbanTasks.filter((t) => {