From 374595261f15b4a92df7fd0f485b4ddd85feb440 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Wed, 30 Jul 2025 16:25:29 +0530 Subject: [PATCH] feat(task-list-v2): enhance sticky column behavior and dark mode support - Updated DropSpacer and EmptyGroupMessage components to accept an optional isDarkMode prop for improved styling in dark mode. - Enhanced task rendering in TaskRow to dynamically adjust background colors based on dark mode and drag states. - Refactored useTaskRowColumns to support sticky column positioning and hover effects, ensuring a consistent user experience across different themes. - Improved overall visual feedback during task interactions, including drag-and-drop operations. --- .../task-list-v2/TaskListV2Table.tsx | 112 +++++++++++++++--- .../src/components/task-list-v2/TaskRow.tsx | 25 +++- .../task-list-v2/hooks/useTaskRowColumns.tsx | 65 ++++++++-- 3 files changed, 167 insertions(+), 35 deletions(-) diff --git a/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx b/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx index 07b25ba3..36b8182a 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx @@ -69,7 +69,11 @@ import ConvertToSubtaskDrawer from '@/components/task-list-common/convert-to-sub import EmptyListPlaceholder from '@/components/EmptyListPlaceholder'; // Drop Spacer Component - creates space between tasks when dragging -const DropSpacer: React.FC<{ isVisible: boolean; visibleColumns: any[] }> = ({ isVisible, visibleColumns }) => { +const DropSpacer: React.FC<{ isVisible: boolean; visibleColumns: any[]; isDarkMode?: boolean }> = ({ + isVisible, + visibleColumns, + isDarkMode = false +}) => { if (!isVisible) return null; return ( @@ -83,17 +87,34 @@ const DropSpacer: React.FC<{ isVisible: boolean; visibleColumns: any[] }> = ({ i overflow: 'hidden', }} > - {visibleColumns.map((column) => { + {visibleColumns.map((column, index) => { + // Calculate left position for sticky columns + let leftPosition = 0; + if (column.isSticky) { + for (let i = 0; i < index; i++) { + const prevColumn = visibleColumns[i]; + if (prevColumn.isSticky) { + leftPosition += parseInt(prevColumn.width.replace('px', '')); + } + } + } + const columnStyle = { width: column.width, flexShrink: 0, + ...(column.isSticky && { + position: 'sticky' as const, + left: leftPosition, + zIndex: 5, + backgroundColor: 'inherit', // Inherit from parent spacer + }), }; if (column.id === 'title') { return (
@@ -116,13 +137,33 @@ const DropSpacer: React.FC<{ isVisible: boolean; visibleColumns: any[] }> = ({ i }; // Empty Group Message Component -const EmptyGroupMessage: React.FC<{ visibleColumns: any[] }> = ({ visibleColumns }) => { +const EmptyGroupMessage: React.FC<{ visibleColumns: any[]; isDarkMode?: boolean }> = ({ + visibleColumns, + isDarkMode = false +}) => { return (
- {visibleColumns.map((column) => { + {visibleColumns.map((column, index) => { + // Calculate left position for sticky columns + let leftPosition = 0; + if (column.isSticky) { + for (let i = 0; i < index; i++) { + const prevColumn = visibleColumns[i]; + if (prevColumn.isSticky) { + leftPosition += parseInt(prevColumn.width.replace('px', '')); + } + } + } + const emptyColumnStyle = { width: column.width, flexShrink: 0, + ...(column.isSticky && { + position: 'sticky' as const, + left: leftPosition, + zIndex: 5, + backgroundColor: 'inherit', // Inherit from parent container + }), }; // Show text in the title column @@ -130,7 +171,7 @@ const EmptyGroupMessage: React.FC<{ visibleColumns: any[] }> = ({ visibleColumns return (
@@ -550,7 +591,7 @@ const TaskListV2Section: React.FC = () => { projectId={urlProjectId || ''} /> {isGroupEmpty && !isGroupCollapsed && ( - + )}
); @@ -596,19 +637,40 @@ const TaskListV2Section: React.FC = () => { const renderColumnHeaders = useCallback( () => (
{visibleColumns.map((column, index) => { + // Calculate left position for sticky columns + let leftPosition = 0; + if (column.isSticky) { + for (let i = 0; i < index; i++) { + const prevColumn = visibleColumns[i]; + if (prevColumn.isSticky) { + leftPosition += parseInt(prevColumn.width.replace('px', '')); + } + } + } + const columnStyle: ColumnStyle = { width: column.width, flexShrink: 0, ...((column as any).minWidth && { minWidth: (column as any).minWidth }), ...((column as any).maxWidth && { maxWidth: (column as any).maxWidth }), + ...(column.isSticky && { + position: 'sticky' as const, + left: leftPosition, + zIndex: 10, + backgroundColor: isDarkMode ? '#141414' : '#f9fafb', // custom dark header : bg-gray-50 + }), }; return ( @@ -773,14 +835,25 @@ const TaskListV2Section: React.FC = () => { } return ( - + <> + {/* CSS for sticky column hover effects */} + + +
{/* Table Container */}
{ return (
- {showDropSpacerBefore && } + {showDropSpacerBefore && } {renderTask(globalTaskIndex, isFirstTaskInGroup)} - {showDropSpacerAfter && } + {showDropSpacerAfter && }
); }) @@ -917,6 +990,7 @@ const TaskListV2Section: React.FC = () => { {createPortal(, document.body, 'convert-to-subtask-drawer')}
+ ); }; diff --git a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx index c2cbdd96..be2cec2c 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx @@ -131,11 +131,26 @@ const TaskRow: React.FC = memo(({ isOver && !isDragging ? 'bg-blue-50 dark:bg-blue-900/20' : '' }`} > - {visibleColumns.map((column, index) => ( - - {renderColumn(column.id, column.width, column.isSticky, index)} - - ))} + {visibleColumns.map((column, index) => { + // Calculate background state for sticky columns - custom dark mode colors + const rowBackgrounds = { + normal: isDarkMode ? '#1e1e1e' : '#ffffff', // custom dark : bg-white + hover: isDarkMode ? '#1f2937' : '#f9fafb', // slightly lighter dark : bg-gray-50 + dragOver: isDarkMode ? '#1e3a8a33' : '#dbeafe', // bg-blue-900/20 : bg-blue-50 + }; + + let currentBg = rowBackgrounds.normal; + if (isOver && !isDragging) { + currentBg = rowBackgrounds.dragOver; + } + // Note: hover state is handled by CSS, so we'll use a CSS custom property + + return ( + + {renderColumn(column.id, column.width, column.isSticky, index, currentBg, rowBackgrounds)} + + ); + })}
); }); diff --git a/worklenz-frontend/src/components/task-list-v2/hooks/useTaskRowColumns.tsx b/worklenz-frontend/src/components/task-list-v2/hooks/useTaskRowColumns.tsx index 6359deb3..38e61c19 100644 --- a/worklenz-frontend/src/components/task-list-v2/hooks/useTaskRowColumns.tsx +++ b/worklenz-frontend/src/components/task-list-v2/hooks/useTaskRowColumns.tsx @@ -90,17 +90,39 @@ export const useTaskRowColumns = ({ depth = 0, }: UseTaskRowColumnsProps) => { - const renderColumn = useCallback((columnId: string, width: string, isSticky?: boolean, index?: number) => { - switch (columnId) { - case 'dragHandle': - return ( - - ); + const renderColumn = useCallback((columnId: string, width: string, isSticky?: boolean, index?: number, currentBg?: string, rowBackgrounds?: any) => { + // Calculate left position for sticky columns + let leftPosition = 0; + if (isSticky && typeof index === 'number') { + for (let i = 0; i < index; i++) { + const prevColumn = visibleColumns[i]; + if (prevColumn.isSticky) { + leftPosition += parseInt(prevColumn.width.replace('px', '')); + } + } + } + + // Create wrapper style for sticky positioning + const wrapperStyle = isSticky ? { + position: 'sticky' as const, + left: leftPosition, + zIndex: 5, // Lower than header but above regular content + backgroundColor: currentBg || (isDarkMode ? '#1e1e1e' : '#ffffff'), // Use dynamic background or fallback + overflow: 'hidden', // Prevent content from spilling over + width: width, // Ensure the wrapper respects column width + } : {}; + + const renderColumnContent = () => { + switch (columnId) { + case 'dragHandle': + return ( + + ); case 'checkbox': return ( @@ -294,7 +316,27 @@ export const useTaskRowColumns = ({ ); } return null; + } + }; + + // Wrap content with sticky positioning if needed + const content = renderColumnContent(); + if (isSticky) { + const hoverBg = rowBackgrounds?.hover || (isDarkMode ? '#2a2a2a' : '#f9fafb'); + return ( +
+ {content} +
+ ); } + + return content; }, [ task, projectId, @@ -319,6 +361,7 @@ export const useTaskRowColumns = ({ handleTaskNameEdit, attributes, listeners, + depth, ]); return { renderColumn };