refactor(components): enhance component structure and add forwardRef support

- Refactored CustomAvatar, CustomColordLabel, CustomNumberLabel, and ProjectStatusIcon components to utilize React.forwardRef for improved ref handling.
- Introduced TooltipWrapper component to avoid findDOMNode warnings in React StrictMode, ensuring better compatibility with Ant Design's Tooltip.
- Updated TaskRow component to enhance layout and tooltip functionality for task display names, improving user experience and accessibility.
This commit is contained in:
chamiakJ
2025-07-07 07:16:10 +05:30
parent f9926e7a5d
commit 5d9e96033e
6 changed files with 157 additions and 88 deletions

View File

@@ -1,13 +1,21 @@
import React from 'react';
import Tooltip from 'antd/es/tooltip'; import Tooltip from 'antd/es/tooltip';
import Avatar from 'antd/es/avatar'; import Avatar from 'antd/es/avatar';
import { AvatarNamesMap } from '../shared/constants'; import { AvatarNamesMap } from '../shared/constants';
const CustomAvatar = ({ avatarName, size = 32 }: { avatarName: string; size?: number }) => { interface CustomAvatarProps {
avatarName: string;
size?: number;
}
const CustomAvatar = React.forwardRef<HTMLDivElement, CustomAvatarProps>(
({ avatarName, size = 32 }, ref) => {
const avatarCharacter = avatarName[0].toUpperCase(); const avatarCharacter = avatarName[0].toUpperCase();
return ( return (
<Tooltip title={avatarName}> <Tooltip title={avatarName}>
<div ref={ref} style={{ display: 'inline-block' }}>
<Avatar <Avatar
style={{ style={{
backgroundColor: AvatarNamesMap[avatarCharacter], backgroundColor: AvatarNamesMap[avatarCharacter],
@@ -18,8 +26,12 @@ const CustomAvatar = ({ avatarName, size = 32 }: { avatarName: string; size?: nu
> >
{avatarCharacter} {avatarCharacter}
</Avatar> </Avatar>
</div>
</Tooltip> </Tooltip>
); );
}; }
);
CustomAvatar.displayName = 'CustomAvatar';
export default CustomAvatar; export default CustomAvatar;

View File

@@ -8,12 +8,13 @@ interface CustomColordLabelProps {
isDarkMode?: boolean; isDarkMode?: boolean;
} }
const CustomColordLabel: React.FC<CustomColordLabelProps> = ({ label, isDarkMode = false }) => { const CustomColordLabel = React.forwardRef<HTMLSpanElement, CustomColordLabelProps>(
({ label, isDarkMode = false }, ref) => {
const truncatedName = const truncatedName =
label.name && label.name.length > 10 ? `${label.name.substring(0, 10)}...` : label.name; 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 // Handle different color property names for different types
const backgroundColor = label.color || '#6b7280'; // Default to gray-500 if no color const backgroundColor = (label as Label).color || (label as ITaskLabel).color_code || '#6b7280'; // Default to gray-500 if no color
// Function to determine if we should use white or black text based on background color // Function to determine if we should use white or black text based on background color
const getTextColor = (bgColor: string): string => { const getTextColor = (bgColor: string): string => {
@@ -37,6 +38,7 @@ const CustomColordLabel: React.FC<CustomColordLabelProps> = ({ label, isDarkMode
return ( return (
<Tooltip title={label.name}> <Tooltip title={label.name}>
<span <span
ref={ref}
className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium shrink-0 max-w-[120px]" className="inline-flex items-center px-2 py-0.5 rounded-sm text-xs font-medium shrink-0 max-w-[120px]"
style={{ style={{
backgroundColor, backgroundColor,
@@ -48,6 +50,9 @@ const CustomColordLabel: React.FC<CustomColordLabelProps> = ({ label, isDarkMode
</span> </span>
</Tooltip> </Tooltip>
); );
}; }
);
CustomColordLabel.displayName = 'CustomColordLabel';
export default CustomColordLabel; export default CustomColordLabel;

View File

@@ -9,12 +9,8 @@ interface CustomNumberLabelProps {
color?: string; // Add color prop for label color color?: string; // Add color prop for label color
} }
const CustomNumberLabel: React.FC<CustomNumberLabelProps> = ({ const CustomNumberLabel = React.forwardRef<HTMLSpanElement, CustomNumberLabelProps>(
labelList, ({ labelList, namesString, isDarkMode = false, color }, ref) => {
namesString,
isDarkMode = false,
color,
}) => {
// Use provided color, or fall back to NumbersColorMap based on first digit // Use provided color, or fall back to NumbersColorMap based on first digit
const backgroundColor = color || (() => { const backgroundColor = color || (() => {
const firstDigit = namesString.match(/\d/)?.[0] || '0'; const firstDigit = namesString.match(/\d/)?.[0] || '0';
@@ -24,6 +20,7 @@ const CustomNumberLabel: React.FC<CustomNumberLabelProps> = ({
return ( return (
<Tooltip title={labelList.join(', ')}> <Tooltip title={labelList.join(', ')}>
<span <span
ref={ref}
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white cursor-help" className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white cursor-help"
style={{ backgroundColor }} style={{ backgroundColor }}
> >
@@ -31,6 +28,9 @@ const CustomNumberLabel: React.FC<CustomNumberLabelProps> = ({
</span> </span>
</Tooltip> </Tooltip>
); );
}; }
);
CustomNumberLabel.displayName = 'CustomNumberLabel';
export default CustomNumberLabel; export default CustomNumberLabel;

View File

@@ -1,3 +1,4 @@
import React from 'react';
import Icon, { import Icon, {
CheckCircleOutlined, CheckCircleOutlined,
ClockCircleOutlined, ClockCircleOutlined,
@@ -12,10 +13,23 @@ const iconMap = {
'check-circle': CheckCircleOutlined, 'check-circle': CheckCircleOutlined,
}; };
const ProjectStatusIcon = ({ iconName, color }: { iconName: string; color: string }) => { interface ProjectStatusIconProps {
iconName: string;
color: string;
}
const ProjectStatusIcon = React.forwardRef<HTMLSpanElement, ProjectStatusIconProps>(
({ iconName, color }, ref) => {
const IconComponent = iconMap[iconName as keyof typeof iconMap]; const IconComponent = iconMap[iconName as keyof typeof iconMap];
if (!IconComponent) return null; if (!IconComponent) return null;
return <IconComponent style={{ color: color }} />; return (
}; <span ref={ref} style={{ display: 'inline-block' }}>
<IconComponent style={{ color: color }} />
</span>
);
}
);
ProjectStatusIcon.displayName = 'ProjectStatusIcon';
export default ProjectStatusIcon; export default ProjectStatusIcon;

View File

@@ -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;

View File

@@ -54,7 +54,7 @@ const TaskLabelsCell: React.FC<TaskLabelsCellProps> = memo(({ labels, isDarkMode
} }
return ( return (
<> <div className="flex items-center gap-0.5 flex-wrap">
{labels.map((label, index) => { {labels.map((label, index) => {
const extendedLabel = label as any; const extendedLabel = label as any;
return extendedLabel.end && extendedLabel.names && extendedLabel.name ? ( 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" />} {isSubtask && <div className="w-2" />}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-gray-700 dark:text-gray-300 truncate"> <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} {taskDisplayName}
</span> </span>
</Tooltip>
{/* Subtask count indicator - only show if count > 1 */} {/* Subtask count indicator - only show if count > 1 */}
{!isSubtask && task.sub_tasks_count != null && task.sub_tasks_count !== 0 && ( {!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': case 'labels':
return ( 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} /> <TaskLabelsCell labels={task.labels} isDarkMode={isDarkMode} />
<LabelsSelector task={labelsAdapter} isDarkMode={isDarkMode} /> <LabelsSelector task={labelsAdapter} isDarkMode={isDarkMode} />
</div> </div>