feat(task-management): enhance task row and virtualized list components for improved layout and performance

- Added support for customizable columns in `TaskRow` component, allowing for fixed and scrollable columns.
- Implemented synchronized scrolling between header and body in `VirtualizedTaskList` for better user experience.
- Refactored column header rendering to dynamically generate based on column definitions, improving maintainability.
- Enhanced styles for task group headers and column headers to ensure consistent appearance and responsiveness.
This commit is contained in:
chamikaJ
2025-06-23 16:49:57 +05:30
parent 2dd756bbb8
commit 95d0985f3d
2 changed files with 370 additions and 344 deletions

View File

@@ -26,6 +26,9 @@ interface TaskRowProps {
index?: number;
onSelect?: (taskId: string, selected: boolean) => void;
onToggleSubtasks?: (taskId: string) => void;
columns?: Array<{ key: string; label: string; width: number; fixed?: boolean }>;
fixedColumns?: Array<{ key: string; label: string; width: number; fixed?: boolean }>;
scrollableColumns?: Array<{ key: string; label: string; width: number; fixed?: boolean }>;
}
// Priority and status colors - moved outside component to avoid recreation
@@ -52,6 +55,9 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
index,
onSelect,
onToggleSubtasks,
columns,
fixedColumns,
scrollableColumns,
}) => {
const { socket, connected } = useSocket();
@@ -217,189 +223,222 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(({
>
<div className="flex h-10 max-h-10 overflow-visible relative min-w-[1200px]">
{/* Fixed Columns */}
<div className={fixedColumnsClasses}>
{/* Drag Handle */}
<div className={`w-10 flex items-center justify-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<Button
variant="text"
size="small"
icon={<HolderOutlined />}
className="opacity-40 hover:opacity-100 cursor-grab active:cursor-grabbing"
isDarkMode={isDarkMode}
{...attributes}
{...listeners}
/>
</div>
{/* Selection Checkbox */}
<div className={`w-10 flex items-center justify-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<Checkbox
checked={isSelected}
onChange={handleSelectChange}
isDarkMode={isDarkMode}
/>
</div>
{/* Task Key */}
<div className={`w-20 flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<Tag
backgroundColor={isDarkMode ? "#374151" : "#f0f0f0"}
color={isDarkMode ? "#d1d5db" : "#666"}
className="truncate whitespace-nowrap max-w-full"
>
{task.task_key}
</Tag>
</div>
{/* Task Name */}
<div className={`w-[475px] flex items-center px-2 ${editTaskName ? (isDarkMode ? 'bg-blue-900/10 border border-blue-500' : 'bg-blue-50/20 border border-blue-500') : ''}`}>
<div className="flex-1 min-w-0 flex flex-col justify-center h-full overflow-hidden">
<div className="flex items-center gap-2 h-5 overflow-hidden">
<div ref={wrapperRef} className="flex-1 min-w-0">
{!editTaskName ? (
<Typography.Text
ellipsis={{ tooltip: task.title }}
onClick={() => setEditTaskName(true)}
className={taskNameClasses}
style={{ cursor: 'pointer' }}
>
{task.title}
</Typography.Text>
) : (
<Input
ref={inputRef}
variant="borderless"
value={taskName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTaskName(e.target.value)}
onPressEnter={handleTaskNameSave}
className={`${isDarkMode ? 'bg-gray-800 text-gray-100 border-gray-600' : 'bg-white text-gray-900 border-gray-300'}`}
style={{
width: '100%',
padding: '2px 4px',
fontSize: '14px',
fontWeight: 500,
}}
<div
className="fixed-columns-row"
style={{
display: 'flex',
position: 'sticky',
left: 0,
zIndex: 2,
background: isDarkMode ? '#1a1a1a' : '#fff',
width: fixedColumns?.reduce((sum, col) => sum + col.width, 0) || 0,
}}
>
{fixedColumns?.map(col => {
switch (col.key) {
case 'drag':
return (
<div key={col.key} className="w-10 flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
<Button
variant="text"
size="small"
icon={<HolderOutlined />}
className="opacity-40 hover:opacity-100 cursor-grab active:cursor-grabbing"
isDarkMode={isDarkMode}
{...attributes}
{...listeners}
/>
)}
</div>
</div>
</div>
</div>
</div>
);
case 'select':
return (
<div key={col.key} className="w-10 flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
<Checkbox
checked={isSelected}
onChange={handleSelectChange}
isDarkMode={isDarkMode}
/>
</div>
);
case 'key':
return (
<div key={col.key} className="w-20 flex items-center px-2 border-r" style={{ width: col.width }}>
<Tag
backgroundColor={isDarkMode ? "#374151" : "#f0f0f0"}
color={isDarkMode ? "#d1d5db" : "#666"}
className="truncate whitespace-nowrap max-w-full"
>
{task.task_key}
</Tag>
</div>
);
case 'task':
return (
<div key={col.key} className="flex items-center px-2" style={{ width: col.width }}>
<div className="flex-1 min-w-0 flex flex-col justify-center h-full overflow-hidden">
<div className="flex items-center gap-2 h-5 overflow-hidden">
<div ref={wrapperRef} className="flex-1 min-w-0">
{!editTaskName ? (
<Typography.Text
ellipsis={{ tooltip: task.title }}
onClick={() => setEditTaskName(true)}
className={taskNameClasses}
style={{ cursor: 'pointer' }}
>
{task.title}
</Typography.Text>
) : (
<Input
ref={inputRef}
variant="borderless"
value={taskName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTaskName(e.target.value)}
onPressEnter={handleTaskNameSave}
className={`${isDarkMode ? 'bg-gray-800 text-gray-100 border-gray-600' : 'bg-white text-gray-900 border-gray-300'}`}
style={{
width: '100%',
padding: '2px 4px',
fontSize: '14px',
fontWeight: 500,
}}
/>
)}
</div>
</div>
</div>
</div>
);
default:
return null;
}
})}
</div>
{/* Scrollable Columns */}
<div className="flex flex-1 min-w-0">
{/* Progress */}
<div className={`w-[90px] flex items-center justify-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
{task.progress !== undefined && task.progress >= 0 && (
<Progress
type="circle"
percent={task.progress}
size={24}
strokeColor={task.progress === 100 ? '#52c41a' : '#1890ff'}
strokeWidth={2}
showInfo={true}
isDarkMode={isDarkMode}
/>
)}
</div>
{/* Members */}
<div className={`w-[150px] flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<div className="flex items-center gap-2">
{avatarGroupMembers.length > 0 && (
<AvatarGroup
members={avatarGroupMembers}
size={24}
maxCount={3}
isDarkMode={isDarkMode}
/>
)}
<button
className={`
w-6 h-6 rounded-full border border-dashed flex items-center justify-center
transition-colors duration-200
${isDarkMode
? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400'
: 'border-gray-300 hover:border-gray-400 hover:bg-gray-100 text-gray-600'
}
`}
onClick={() => {
// TODO: Implement assignee selector functionality
console.log('Add assignee clicked for task:', task.id);
}}
>
<span className="text-xs">+</span>
</button>
</div>
</div>
{/* Labels */}
<div className={`w-[200px] max-w-[200px] flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<div className="flex items-center gap-1 flex-wrap h-full w-full overflow-visible relative">
{task.labels?.map((label, index) => (
label.end && label.names && label.name ? (
<CustomNumberLabel
key={`${label.id}-${index}`}
labelList={label.names}
namesString={label.name}
isDarkMode={isDarkMode}
/>
) : (
<CustomColordLabel
key={`${label.id}-${index}`}
label={label}
isDarkMode={isDarkMode}
/>
)
))}
<LabelsSelector
task={taskAdapter}
isDarkMode={isDarkMode}
/>
</div>
</div>
{/* Status */}
<div className={`w-[100px] flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<Tag
backgroundColor={getStatusColor(task.status)}
color="white"
className="text-xs font-medium uppercase"
>
{task.status}
</Tag>
</div>
{/* Priority */}
<div className={`w-[100px] flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<div className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: getPriorityColor(task.priority) }}
/>
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
{task.priority}
</span>
</div>
</div>
{/* Time Tracking */}
<div className={`w-[120px] flex items-center px-2 border-r ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<div className="flex items-center gap-2 h-full overflow-hidden">
{task.timeTracking?.logged && task.timeTracking.logged > 0 && (
<div className="flex items-center gap-1">
<ClockCircleOutlined className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`} />
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
{typeof task.timeTracking.logged === 'number'
? `${task.timeTracking.logged}h`
: task.timeTracking.logged
}
</span>
</div>
)}
</div>
</div>
<div className="scrollable-columns-row" style={{ display: 'flex', minWidth: scrollableColumns?.reduce((sum, col) => sum + col.width, 0) || 0 }}>
{scrollableColumns?.map(col => {
switch (col.key) {
case 'progress':
return (
<div key={col.key} className="flex items-center justify-center px-2 border-r" style={{ width: col.width }}>
{task.progress !== undefined && task.progress >= 0 && (
<Progress
type="circle"
percent={task.progress}
size={24}
strokeColor={task.progress === 100 ? '#52c41a' : '#1890ff'}
strokeWidth={2}
showInfo={true}
isDarkMode={isDarkMode}
/>
)}
</div>
);
case 'members':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div className="flex items-center gap-2">
{avatarGroupMembers.length > 0 && (
<AvatarGroup
members={avatarGroupMembers}
size={24}
maxCount={3}
isDarkMode={isDarkMode}
/>
)}
<button
className={`
w-6 h-6 rounded-full border border-dashed flex items-center justify-center
transition-colors duration-200
${isDarkMode
? 'border-gray-600 hover:border-gray-500 hover:bg-gray-800 text-gray-400'
: 'border-gray-300 hover:border-gray-400 hover:bg-gray-100 text-gray-600'
}
`}
onClick={() => {
// TODO: Implement assignee selector functionality
console.log('Add assignee clicked for task:', task.id);
}}
>
<span className="text-xs">+</span>
</button>
</div>
</div>
);
case 'labels':
return (
<div key={col.key} className="max-w-[200px] flex items-center px-2 border-r" style={{ width: col.width }}>
<div className="flex items-center gap-1 flex-wrap h-full w-full overflow-visible relative">
{task.labels?.map((label, index) => (
label.end && label.names && label.name ? (
<CustomNumberLabel
key={`${label.id}-${index}`}
labelList={label.names}
namesString={label.name}
isDarkMode={isDarkMode}
/>
) : (
<CustomColordLabel
key={`${label.id}-${index}`}
label={label}
isDarkMode={isDarkMode}
/>
)
))}
<LabelsSelector
task={taskAdapter}
isDarkMode={isDarkMode}
/>
</div>
</div>
);
case 'status':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<Tag
backgroundColor={getStatusColor(task.status)}
color="white"
className="text-xs font-medium uppercase"
>
{task.status}
</Tag>
</div>
);
case 'priority':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: getPriorityColor(task.priority) }}
/>
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
{task.priority}
</span>
</div>
</div>
);
case 'timeTracking':
return (
<div key={col.key} className="flex items-center px-2 border-r" style={{ width: col.width }}>
<div className="flex items-center gap-2 h-full overflow-hidden">
{task.timeTracking?.logged && task.timeTracking.logged > 0 && (
<div className="flex items-center gap-1">
<ClockCircleOutlined className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`} />
<span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}>
{typeof task.timeTracking.logged === 'number'
? `${task.timeTracking.logged}h`
: task.timeTracking.logged
}
</span>
</div>
)}
</div>
</div>
);
default:
return null;
}
})}
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useCallback, useEffect } from 'react';
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
import { FixedSizeList as List } from 'react-window';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useSelector } from 'react-redux';
@@ -64,120 +64,146 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
});
}, [group.id, groupTasks.length, height, listHeight]);
// Row renderer for virtualization
const scrollContainerRef = useRef<HTMLDivElement>(null);
const headerScrollRef = useRef<HTMLDivElement>(null);
// Synchronize header scroll with body scroll
useEffect(() => {
const handleScroll = () => {
if (headerScrollRef.current && scrollContainerRef.current) {
headerScrollRef.current.scrollLeft = scrollContainerRef.current.scrollLeft;
}
};
const scrollDiv = scrollContainerRef.current;
if (scrollDiv) {
scrollDiv.addEventListener('scroll', handleScroll);
}
return () => {
if (scrollDiv) {
scrollDiv.removeEventListener('scroll', handleScroll);
}
};
}, []);
// Define columns array for alignment
const columns = [
{ key: 'drag', label: '', width: 40, fixed: true },
{ key: 'select', label: '', width: 40, fixed: true },
{ key: 'key', label: 'KEY', width: 80, fixed: true },
{ key: 'task', label: 'TASK', width: 475, fixed: true },
{ key: 'progress', label: 'PROGRESS', width: 90 },
{ key: 'members', label: 'MEMBERS', width: 150 },
{ key: 'labels', label: 'LABELS', width: 200 },
{ key: 'status', label: 'STATUS', width: 100 },
{ key: 'priority', label: 'PRIORITY', width: 100 },
{ key: 'timeTracking', label: 'TIME TRACKING', width: 120 },
];
const fixedColumns = columns.filter(col => col.fixed);
const scrollableColumns = columns.filter(col => !col.fixed);
const fixedWidth = fixedColumns.reduce((sum, col) => sum + col.width, 0);
const scrollableWidth = scrollableColumns.reduce((sum, col) => sum + col.width, 0);
const totalTableWidth = fixedWidth + scrollableWidth;
// Row renderer for virtualization (remove header/column header rows)
const Row = useCallback(({ index, style }: { index: number; style: React.CSSProperties }) => {
// Header row
if (index === 0) {
return (
<div style={style}>
<div className="task-group-header">
<div className="task-group-header-row">
<div
className="task-group-header-content"
style={{
backgroundColor: group.color || '#f0f0f0',
borderLeft: `4px solid ${group.color || '#f0f0f0'}`
}}
>
<span className="task-group-header-text">
{group.title} ({groupTasks.length})
</span>
</div>
</div>
</div>
</div>
);
}
// Column headers row
if (index === 1) {
return (
<div style={style}>
<div
className="task-group-column-headers"
style={{ borderLeft: `4px solid ${group.color || '#f0f0f0'}` }}
>
<div className="task-group-column-headers-row">
<div className="task-table-fixed-columns">
<div className="task-table-cell task-table-header-cell" style={{ width: '40px' }}></div>
<div className="task-table-cell task-table-header-cell" style={{ width: '40px' }}></div>
<div className="task-table-cell task-table-header-cell" style={{ width: '80px' }}>
<span className="column-header-text">Key</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '475px' }}>
<span className="column-header-text">Task</span>
</div>
</div>
<div className="task-table-scrollable-columns">
<div className="task-table-cell task-table-header-cell" style={{ width: '90px' }}>
<span className="column-header-text">Progress</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '150px' }}>
<span className="column-header-text">Members</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '200px' }}>
<span className="column-header-text">Labels</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '100px' }}>
<span className="column-header-text">Status</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '100px' }}>
<span className="column-header-text">Priority</span>
</div>
<div className="task-table-cell task-table-header-cell" style={{ width: '120px' }}>
<span className="column-header-text">Time Tracking</span>
</div>
</div>
</div>
</div>
</div>
);
}
// Task rows
const taskIndex = index - 2;
if (taskIndex >= 0 && taskIndex < groupTasks.length) {
const task = groupTasks[taskIndex];
return (
<div
className="task-row-container"
style={{
...style,
'--group-color': group.color || '#f0f0f0'
} as React.CSSProperties}
>
<TaskRow
task={task}
projectId={projectId}
groupId={group.id}
currentGrouping={currentGrouping}
isSelected={selectedTaskIds.includes(task.id)}
index={taskIndex}
onSelect={onSelectTask}
onToggleSubtasks={onToggleSubtasks}
/>
</div>
);
}
return null;
const task = groupTasks[index];
if (!task) return null;
return (
<div
className="task-row-container"
style={{
...style,
'--group-color': group.color || '#f0f0f0'
} as React.CSSProperties}
>
<TaskRow
task={task}
projectId={projectId}
groupId={group.id}
currentGrouping={currentGrouping}
isSelected={selectedTaskIds.includes(task.id)}
index={index}
onSelect={onSelectTask}
onToggleSubtasks={onToggleSubtasks}
fixedColumns={fixedColumns}
scrollableColumns={scrollableColumns}
/>
</div>
);
}, [group, groupTasks, projectId, currentGrouping, selectedTaskIds, onSelectTask, onToggleSubtasks]);
return (
<div className="virtualized-task-list" style={{ height: height }}>
<SortableContext items={group.taskIds} strategy={verticalListSortingStrategy}>
<List
height={listHeight}
width={width}
itemCount={getItemCount()}
itemSize={TASK_ROW_HEIGHT}
overscanCount={15} // Render 15 extra items for smooth scrolling
className="react-window-list"
{/* Group Header */}
<div className="task-group-header">
<div className="task-group-header-row">
<div
className="task-group-header-content"
style={{
backgroundColor: group.color || '#f0f0f0',
borderLeft: `4px solid ${group.color || '#f0f0f0'}`
}}
>
<span className="task-group-header-text">
{group.title} ({groupTasks.length})
</span>
</div>
</div>
</div>
{/* Column Headers (sync scroll) */}
<div
className="task-group-column-headers-scroll"
ref={headerScrollRef}
style={{ overflowX: 'auto', overflowY: 'hidden' }}
>
<div
className="task-group-column-headers"
style={{ borderLeft: `4px solid ${group.color || '#f0f0f0'}`, minWidth: totalTableWidth, display: 'flex', position: 'relative' }}
>
{Row}
</List>
</SortableContext>
<div className="fixed-columns-header" style={{ display: 'flex', position: 'sticky', left: 0, zIndex: 2, background: 'inherit', width: fixedWidth }}>
{fixedColumns.map(col => (
<div
key={col.key}
className="task-table-cell task-table-header-cell fixed-column"
style={{ width: col.width }}
>
<span className="column-header-text">{col.label}</span>
</div>
))}
</div>
<div className="scrollable-columns-header" style={{ display: 'flex', minWidth: scrollableWidth }}>
{scrollableColumns.map(col => (
<div
key={col.key}
className="task-table-cell task-table-header-cell"
style={{ width: col.width }}
>
<span className="column-header-text">{col.label}</span>
</div>
))}
</div>
</div>
</div>
{/* Scrollable List */}
<div
className="task-list-scroll-container"
ref={scrollContainerRef}
style={{ overflowX: 'auto', overflowY: 'hidden', width: '100%', minWidth: totalTableWidth }}
>
<SortableContext items={group.taskIds} strategy={verticalListSortingStrategy}>
<List
height={listHeight}
width={width}
itemCount={groupTasks.length}
itemSize={TASK_ROW_HEIGHT}
overscanCount={15}
className="react-window-list"
style={{ minWidth: totalTableWidth }}
>
{Row}
</List>
</SortableContext>
</div>
{/* Add Task Row - Always show at the bottom */}
<div
className="task-group-add-task"
@@ -185,7 +211,6 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
>
<AddTaskListRow groupId={group.id} />
</div>
<style>{`
.virtualized-task-list {
border: 1px solid var(--task-border-primary, #e8e8e8);
@@ -199,11 +224,23 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
display: flex;
flex-direction: column;
}
.virtualized-task-list:last-child {
margin-bottom: 0;
.task-group-header {
position: relative;
z-index: 20;
}
.task-group-column-headers-scroll {
width: 100%;
}
.task-group-column-headers {
background: var(--task-bg-secondary, #f5f5f5);
border-bottom: 1px solid var(--task-border-tertiary, #d9d9d9);
margin: 0;
padding: 0;
min-width: 1200px;
}
.task-list-scroll-container {
width: 100%;
}
.react-window-list {
outline: none;
flex: 1;
@@ -211,19 +248,16 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
margin: 0;
padding: 0;
}
.react-window-list-item {
contain: layout style;
margin: 0;
padding: 0;
}
/* Task row container styles */
.task-row-container {
position: relative;
background: var(--task-bg-primary, white);
}
.task-row-container::before {
content: '';
position: absolute;
@@ -234,24 +268,12 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
background-color: var(--group-color, #f0f0f0);
z-index: 10;
}
/* Ensure no gaps between list items */
.react-window-list > div {
margin: 0;
padding: 0;
}
/* Task group header styles */
.task-group-header {
background: var(--task-bg-primary, white);
transition: background-color 0.3s ease;
margin: 0;
padding: 0;
position: sticky;
top: 0;
z-index: 20;
}
.task-group-header-row {
display: inline-flex;
height: auto;
@@ -260,7 +282,6 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
margin: 0;
padding: 0;
}
.task-group-header-content {
display: inline-flex;
align-items: center;
@@ -273,37 +294,13 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
margin: 0;
border: none;
}
.task-group-header-text {
color: white !important;
font-size: 13px !important;
font-weight: 600 !important;
margin: 0 !important;
}
/* Column headers styles */
.task-group-column-headers {
background: var(--task-bg-secondary, #f5f5f5);
border-bottom: 1px solid var(--task-border-tertiary, #d9d9d9);
transition: background-color 0.3s ease;
margin: 0;
padding: 0;
position: sticky;
top: 40px;
z-index: 19;
}
.task-group-column-headers-row {
display: flex;
height: 40px;
max-height: 40px;
overflow: visible;
position: relative;
min-width: 1200px;
margin: 0;
padding: 0;
}
.task-table-header-cell {
background: var(--task-bg-secondary, #f5f5f5);
font-weight: 600;
@@ -316,7 +313,6 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
overflow: hidden;
transition: all 0.3s ease;
}
.column-header-text {
font-size: 11px;
font-weight: 600;
@@ -325,7 +321,6 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
letter-spacing: 0.5px;
transition: color 0.3s ease;
}
/* Add task row styles */
.task-group-add-task {
background: var(--task-bg-primary, white);
@@ -338,11 +333,9 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
align-items: center;
flex-shrink: 0;
}
.task-group-add-task:hover {
background: var(--task-hover-bg, #fafafa);
}
.task-table-fixed-columns {
display: flex;
background: var(--task-bg-secondary, #f5f5f5);
@@ -353,13 +346,11 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.task-table-scrollable-columns {
display: flex;
flex: 1;
min-width: 0;
}
.task-table-cell {
display: flex;
align-items: center;
@@ -374,16 +365,13 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
color: var(--task-text-primary, #262626);
transition: all 0.3s ease;
}
.task-table-cell:last-child {
border-right: none;
}
/* Performance optimizations */
.virtualized-task-list {
contain: layout style paint;
}
/* Dark mode support */
:root {
--task-bg-primary: #ffffff;
@@ -402,7 +390,6 @@ const VirtualizedTaskList: React.FC<VirtualizedTaskListProps> = React.memo(({
--task-drag-over-bg: #f0f8ff;
--task-drag-over-border: #40a9ff;
}
.dark .virtualized-task-list,
[data-theme="dark"] .virtualized-task-list {
--task-bg-primary: #1f1f1f;