Merge pull request #239 from Worklenz/fix/task-drag-and-drop-improvement
refactor(components): enhance component structure and add forwardRef …
This commit is contained in:
@@ -1,25 +1,37 @@
|
||||
import React from 'react';
|
||||
import Tooltip from 'antd/es/tooltip';
|
||||
import Avatar from 'antd/es/avatar';
|
||||
|
||||
import { AvatarNamesMap } from '../shared/constants';
|
||||
|
||||
const CustomAvatar = ({ avatarName, size = 32 }: { avatarName: string; size?: number }) => {
|
||||
const avatarCharacter = avatarName[0].toUpperCase();
|
||||
interface CustomAvatarProps {
|
||||
avatarName: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={avatarName}>
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: AvatarNamesMap[avatarCharacter],
|
||||
verticalAlign: 'middle',
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
>
|
||||
{avatarCharacter}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
const CustomAvatar = React.forwardRef<HTMLDivElement, CustomAvatarProps>(
|
||||
({ avatarName, size = 32 }, ref) => {
|
||||
const avatarCharacter = avatarName[0].toUpperCase();
|
||||
|
||||
return (
|
||||
<Tooltip title={avatarName}>
|
||||
<div ref={ref} style={{ display: 'inline-block' }}>
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: AvatarNamesMap[avatarCharacter],
|
||||
verticalAlign: 'middle',
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
>
|
||||
{avatarCharacter}
|
||||
</Avatar>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
CustomAvatar.displayName = 'CustomAvatar';
|
||||
|
||||
export default CustomAvatar;
|
||||
|
||||
@@ -8,46 +8,51 @@ interface CustomColordLabelProps {
|
||||
isDarkMode?: boolean;
|
||||
}
|
||||
|
||||
const CustomColordLabel: React.FC<CustomColordLabelProps> = ({ label, isDarkMode = false }) => {
|
||||
const truncatedName =
|
||||
label.name && label.name.length > 10 ? `${label.name.substring(0, 10)}...` : label.name;
|
||||
const CustomColordLabel = React.forwardRef<HTMLSpanElement, CustomColordLabelProps>(
|
||||
({ label, isDarkMode = false }, ref) => {
|
||||
const truncatedName =
|
||||
label.name && label.name.length > 10 ? `${label.name.substring(0, 10)}...` : label.name;
|
||||
|
||||
// Ensure we have a valid color, fallback to a default if not
|
||||
const backgroundColor = label.color || '#6b7280'; // Default to gray-500 if no color
|
||||
|
||||
// Function to determine if we should use white or black text based on background color
|
||||
const getTextColor = (bgColor: string): string => {
|
||||
// Remove # if present
|
||||
const color = bgColor.replace('#', '');
|
||||
// Handle different color property names for different types
|
||||
const backgroundColor = (label as Label).color || (label as ITaskLabel).color_code || '#6b7280'; // Default to gray-500 if no color
|
||||
|
||||
// Convert to RGB
|
||||
const r = parseInt(color.substr(0, 2), 16);
|
||||
const g = parseInt(color.substr(2, 2), 16);
|
||||
const b = parseInt(color.substr(4, 2), 16);
|
||||
|
||||
// Calculate luminance
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
|
||||
// Return white for dark backgrounds, black for light backgrounds
|
||||
return luminance > 0.5 ? '#000000' : '#ffffff';
|
||||
};
|
||||
// Function to determine if we should use white or black text based on background color
|
||||
const getTextColor = (bgColor: string): string => {
|
||||
// Remove # if present
|
||||
const color = bgColor.replace('#', '');
|
||||
|
||||
// Convert to RGB
|
||||
const r = parseInt(color.substr(0, 2), 16);
|
||||
const g = parseInt(color.substr(2, 2), 16);
|
||||
const b = parseInt(color.substr(4, 2), 16);
|
||||
|
||||
// Calculate luminance
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
|
||||
// Return white for dark backgrounds, black for light backgrounds
|
||||
return luminance > 0.5 ? '#000000' : '#ffffff';
|
||||
};
|
||||
|
||||
const textColor = getTextColor(backgroundColor);
|
||||
const textColor = getTextColor(backgroundColor);
|
||||
|
||||
return (
|
||||
<Tooltip title={label.name}>
|
||||
<span
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium shrink-0 max-w-[120px]"
|
||||
style={{
|
||||
backgroundColor,
|
||||
color: textColor,
|
||||
border: `1px solid ${backgroundColor}`,
|
||||
}}
|
||||
>
|
||||
<span className="truncate">{truncatedName}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Tooltip title={label.name}>
|
||||
<span
|
||||
ref={ref}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium shrink-0 max-w-[120px]"
|
||||
style={{
|
||||
backgroundColor,
|
||||
color: textColor,
|
||||
border: `1px solid ${backgroundColor}`,
|
||||
}}
|
||||
>
|
||||
<span className="truncate">{truncatedName}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
CustomColordLabel.displayName = 'CustomColordLabel';
|
||||
|
||||
export default CustomColordLabel;
|
||||
|
||||
@@ -9,28 +9,28 @@ interface CustomNumberLabelProps {
|
||||
color?: string; // Add color prop for label color
|
||||
}
|
||||
|
||||
const CustomNumberLabel: React.FC<CustomNumberLabelProps> = ({
|
||||
labelList,
|
||||
namesString,
|
||||
isDarkMode = false,
|
||||
color,
|
||||
}) => {
|
||||
// Use provided color, or fall back to NumbersColorMap based on first digit
|
||||
const backgroundColor = color || (() => {
|
||||
const firstDigit = namesString.match(/\d/)?.[0] || '0';
|
||||
return NumbersColorMap[firstDigit] || NumbersColorMap['0'];
|
||||
})();
|
||||
|
||||
return (
|
||||
<Tooltip title={labelList.join(', ')}>
|
||||
<span
|
||||
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white cursor-help"
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
{namesString}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
const CustomNumberLabel = React.forwardRef<HTMLSpanElement, CustomNumberLabelProps>(
|
||||
({ labelList, namesString, isDarkMode = false, color }, ref) => {
|
||||
// Use provided color, or fall back to NumbersColorMap based on first digit
|
||||
const backgroundColor = color || (() => {
|
||||
const firstDigit = namesString.match(/\d/)?.[0] || '0';
|
||||
return NumbersColorMap[firstDigit] || NumbersColorMap['0'];
|
||||
})();
|
||||
|
||||
return (
|
||||
<Tooltip title={labelList.join(', ')}>
|
||||
<span
|
||||
ref={ref}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white cursor-help"
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
{namesString}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
CustomNumberLabel.displayName = 'CustomNumberLabel';
|
||||
|
||||
export default CustomNumberLabel;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import Icon, {
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
@@ -12,10 +13,23 @@ const iconMap = {
|
||||
'check-circle': CheckCircleOutlined,
|
||||
};
|
||||
|
||||
const ProjectStatusIcon = ({ iconName, color }: { iconName: string; color: string }) => {
|
||||
const IconComponent = iconMap[iconName as keyof typeof iconMap];
|
||||
if (!IconComponent) return null;
|
||||
return <IconComponent style={{ color: color }} />;
|
||||
};
|
||||
interface ProjectStatusIconProps {
|
||||
iconName: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const ProjectStatusIcon = React.forwardRef<HTMLSpanElement, ProjectStatusIconProps>(
|
||||
({ iconName, color }, ref) => {
|
||||
const IconComponent = iconMap[iconName as keyof typeof iconMap];
|
||||
if (!IconComponent) return null;
|
||||
return (
|
||||
<span ref={ref} style={{ display: 'inline-block' }}>
|
||||
<IconComponent style={{ color: color }} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ProjectStatusIcon.displayName = 'ProjectStatusIcon';
|
||||
|
||||
export default ProjectStatusIcon;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, TooltipProps } from 'antd';
|
||||
|
||||
interface TooltipWrapperProps extends Omit<TooltipProps, 'children'> {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* TooltipWrapper - A wrapper component that helps avoid findDOMNode warnings in React StrictMode
|
||||
*
|
||||
* This component ensures that the child element can properly receive refs from Ant Design's Tooltip
|
||||
* by wrapping it in a div with a ref when necessary.
|
||||
*/
|
||||
const TooltipWrapper = React.forwardRef<HTMLDivElement, TooltipWrapperProps>(
|
||||
({ children, ...tooltipProps }, ref) => {
|
||||
return (
|
||||
<Tooltip {...tooltipProps}>
|
||||
<div ref={ref} style={{ display: 'inline-block' }}>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
TooltipWrapper.displayName = 'TooltipWrapper';
|
||||
|
||||
export default TooltipWrapper;
|
||||
@@ -54,7 +54,7 @@ const TaskLabelsCell: React.FC<TaskLabelsCellProps> = memo(({ labels, isDarkMode
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-0.5 flex-wrap">
|
||||
{labels.map((label, index) => {
|
||||
const extendedLabel = label as any;
|
||||
return extendedLabel.end && extendedLabel.names && extendedLabel.name ? (
|
||||
@@ -73,7 +73,7 @@ const TaskLabelsCell: React.FC<TaskLabelsCellProps> = memo(({ labels, isDarkMode
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -322,9 +322,19 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
||||
{isSubtask && <div className="w-2" />}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300 truncate">
|
||||
{taskDisplayName}
|
||||
</span>
|
||||
<Tooltip title={taskDisplayName}>
|
||||
<span
|
||||
className="text-sm text-gray-700 dark:text-gray-300 truncate cursor-pointer"
|
||||
style={{
|
||||
maxWidth: '200px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{taskDisplayName}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/* Subtask count indicator - only show if count > 1 */}
|
||||
{!isSubtask && task.sub_tasks_count != null && task.sub_tasks_count !== 0 && (
|
||||
@@ -552,7 +562,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
||||
|
||||
case 'labels':
|
||||
return (
|
||||
<div className="flex items-center gap-1 flex-wrap min-w-0" style={{ ...baseStyle, minWidth: '200px' }}>
|
||||
<div className="flex items-center gap-0.5 flex-wrap min-w-0" style={{ ...baseStyle, minWidth: '150px' }}>
|
||||
<TaskLabelsCell labels={task.labels} isDarkMode={isDarkMode} />
|
||||
<LabelsSelector task={labelsAdapter} isDarkMode={isDarkMode} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user