feat: Refactor sidebar props to simplify data flow

This commit is contained in:
2025-12-06 23:04:49 +01:00
parent 54d72138d2
commit bd1cdc0817
8 changed files with 111 additions and 247 deletions
@@ -3,7 +3,7 @@ import { PlusIcon, SettingsIcon } from '../../components/icons';
import type { Identifier } from '../../types/identifiers';
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
export interface CorrespondentEntry {
interface CorrespondentEntry {
id: Identifier;
name: string;
}
@@ -2,10 +2,11 @@ import React, { useCallback } from 'react';
import type { ReactNode } from 'react';
import type { Identifier } from '../../types/identifiers';
import { FolderPlusIcon } from '../../components/icons';
import FolderNode, { FolderIdentifier, FolderTreeNode } from './SidebarFolderNode';
import FolderNode, { FolderIdentifier } from './SidebarFolderNode';
import type { FolderTreeNode } from '../../lib/api/apiTypes';
interface SidebarFolderListProps {
folderNodes: Map<FolderIdentifier, FolderTreeNode>;
roots: FolderTreeNode[];
selectedFolder: FolderIdentifier | null;
onToggle: (folderId: FolderIdentifier) => void;
onSelect: (folderId: FolderIdentifier) => void;
@@ -22,7 +23,7 @@ interface SidebarFolderListProps {
}
const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
folderNodes,
roots,
selectedFolder,
onToggle,
onSelect,
@@ -37,20 +38,44 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
onCreateFolder,
creatingFolder,
}) => {
const rootNode = folderNodes.get('root');
// Local expansion state for the UI
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set(['root']));
// Override validation or external toggle if needed
React.useEffect(() => {
if (selectedFolder) {
// ensure parent paths are expanded?
// Without a parent map, we can't easily auto-expand up.
// Rely on user or initial state for now, or add parent pointers.
}
}, [selectedFolder]);
const handleToggle = useCallback((folderId: FolderIdentifier) => {
setExpandedIds((prev) => {
const next = new Set(prev);
const id = String(folderId);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
onToggle(folderId); // propagate event just in case parent needs it
}, [onToggle]);
const renderNodes = useCallback(
(ids: FolderIdentifier[], depth: number): ReactNode =>
ids.map((id) => {
const node = folderNodes.get(id);
if (!node) return null;
(nodes: FolderTreeNode[], depth: number): ReactNode =>
nodes.map((node) => {
const isExpanded = expandedIds.has(String(node.id));
return (
<FolderNode
key={id}
key={node.id}
node={node}
depth={depth}
isSelected={selectedFolder === id}
onToggle={onToggle}
isSelected={selectedFolder === node.id}
onToggle={handleToggle}
expanded={isExpanded}
onSelect={onSelect}
onDrop={onDrop}
onDragOver={onDragOver}
@@ -66,9 +91,9 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
);
}),
[
folderNodes,
expandedIds,
selectedFolder,
onToggle,
handleToggle,
onSelect,
onDrop,
onDragOver,
@@ -101,7 +126,7 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
) : null}
</div>
<ul className="folder-tree">
{rootNode && renderNodes([rootNode.id], 0)}
{renderNodes(roots, 0)}
</ul>
</div>
);
@@ -9,16 +9,9 @@ import {
} from '../../components/icons';
import type { Identifier } from '../../types/identifiers';
export type FolderIdentifier = Identifier | 'root';
import type { FolderTreeNode } from '../../lib/api/apiTypes';
export interface FolderTreeNode {
id: FolderIdentifier;
name?: string;
children: FolderIdentifier[];
hasChildren?: boolean;
loaded?: boolean;
expanded?: boolean;
}
export type FolderIdentifier = Identifier | 'root';
interface FolderNodeProps {
node: FolderTreeNode;
@@ -31,7 +24,8 @@ interface FolderNodeProps {
onDragLeave: (event: React.DragEvent<HTMLDivElement>) => void;
onDelete: (folderId: FolderIdentifier) => void;
onRename?: (folderId: FolderIdentifier, name: string) => void;
renderChildren: (ids: FolderIdentifier[], depth: number) => ReactNode;
renderChildren: (nodes: FolderTreeNode[], depth: number) => ReactNode;
expanded: boolean;
onFolderDragStart?: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
onFolderDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
draggingFolderId?: FolderIdentifier | null;
@@ -50,20 +44,21 @@ const FolderNode: React.FC<FolderNodeProps> = ({
onDelete,
onRename,
renderChildren,
expanded,
onFolderDragStart,
onFolderDragEnd,
draggingFolderId,
onCreateFolder,
}) => {
const isRoot = node.id === 'root';
const childIds = Array.isArray(node.children) ? node.children : [];
const hasChildren = Boolean(node.hasChildren || childIds.length);
const canToggle = hasChildren || !node.loaded;
const childNodes = node.children || [];
const hasChildren = childNodes.length > 0;
const canToggle = hasChildren; // Simplified: only toggle if we have children to show
const showChevron = canToggle;
const icon = showChevron ? <ChevronIcon className="toggle-icon" /> : null;
const canDrag = !isRoot;
const isDragging = draggingFolderId === node.id;
const isExpanded = Boolean(node.expanded);
const isExpanded = expanded;
const rowClasses = ['folder-row'];
if (isSelected) {
rowClasses.push('active');
@@ -162,9 +157,9 @@ const FolderNode: React.FC<FolderNodeProps> = ({
)}
</div>
</div>
{isExpanded && childIds.length > 0 && (
{isExpanded && childNodes.length > 0 && (
<ul className={`folder-children${depth === 0 ? ' folder-children--level1' : ''}`}>
{renderChildren(childIds, depth + 1)}
{renderChildren(childNodes, depth + 1)}
</ul>
)}
</li>
@@ -4,7 +4,7 @@ import { getTagColorStyle } from '../../utils/colors';
import type { Identifier } from '../../types/identifiers';
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
export interface TagEntry {
interface TagEntry {
id: Identifier;
label: string;
color?: string | null;