优化tooltip组件提示框的弹出优化显示,解决鼠标从目标元素移动到提示框的过程中提示框消失
This commit is contained in:
@@ -320,6 +320,7 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
className="tooltip-custom-offset"
|
className="tooltip-custom-offset"
|
||||||
|
// fixedPlacement={true}
|
||||||
>
|
>
|
||||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
||||||
{content}
|
{content}
|
||||||
@@ -619,6 +620,7 @@ export function ReviewPointsList({
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
className="tooltip-custom-offset tooltip-top"
|
className="tooltip-custom-offset tooltip-top"
|
||||||
|
fixedPlacement={true}
|
||||||
>
|
>
|
||||||
<span className="status-badge status-success text-xs m-1">
|
<span className="status-badge status-success text-xs m-1">
|
||||||
<i className="ri-checkbox-circle-line mr-1"></i>通过
|
<i className="ri-checkbox-circle-line mr-1"></i>通过
|
||||||
@@ -642,6 +644,7 @@ export function ReviewPointsList({
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
className="tooltip-custom-offset tooltip-top"
|
className="tooltip-custom-offset tooltip-top"
|
||||||
|
fixedPlacement={true}
|
||||||
>
|
>
|
||||||
<span className="status-badge status-warning text-xs m-1">
|
<span className="status-badge status-warning text-xs m-1">
|
||||||
<i className="ri-alert-line mr-1"></i>警告
|
<i className="ri-alert-line mr-1"></i>警告
|
||||||
@@ -661,6 +664,7 @@ export function ReviewPointsList({
|
|||||||
trigger="hover"
|
trigger="hover"
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
className="tooltip-custom-offset tooltip-top"
|
className="tooltip-custom-offset tooltip-top"
|
||||||
|
fixedPlacement={true}
|
||||||
>
|
>
|
||||||
<span className="status-badge status-error text-xs m-1">
|
<span className="status-badge status-error text-xs m-1">
|
||||||
<i className="ri-close-circle-line mr-1"></i>不通过
|
<i className="ri-close-circle-line mr-1"></i>不通过
|
||||||
|
|||||||
+132
-26
@@ -23,6 +23,7 @@ export interface TooltipProps {
|
|||||||
maxWidth?: number | string; // 最大宽度
|
maxWidth?: number | string; // 最大宽度
|
||||||
className?: string; // 自定义类名
|
className?: string; // 自定义类名
|
||||||
onVisibleChange?: (visible: boolean) => void; // 显示状态变化回调
|
onVisibleChange?: (visible: boolean) => void; // 显示状态变化回调
|
||||||
|
fixedPlacement?: boolean; // 是否固定显示位置,不自动切换
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +44,8 @@ export function Tooltip({
|
|||||||
showArrow = true,
|
showArrow = true,
|
||||||
maxWidth = 320,
|
maxWidth = 320,
|
||||||
className = '',
|
className = '',
|
||||||
onVisibleChange
|
onVisibleChange,
|
||||||
|
fixedPlacement = false // 默认不固定位置
|
||||||
}: TooltipProps) {
|
}: TooltipProps) {
|
||||||
// 使用内部状态管理提示框显示状态(非受控模式)
|
// 使用内部状态管理提示框显示状态(非受控模式)
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
@@ -58,12 +60,20 @@ export function Tooltip({
|
|||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 保存当前实际显示位置
|
||||||
|
const [actualPlacement, setActualPlacement] = useState<TooltipPlacement>(placement);
|
||||||
|
|
||||||
// 处理显示状态变化
|
// 处理显示状态变化
|
||||||
const handleVisibleChange = (newVisible: boolean) => {
|
const handleVisibleChange = (newVisible: boolean) => {
|
||||||
if (!isControlled) {
|
if (!isControlled) {
|
||||||
setVisible(newVisible);
|
setVisible(newVisible);
|
||||||
}
|
}
|
||||||
onVisibleChange?.(newVisible);
|
onVisibleChange?.(newVisible);
|
||||||
|
|
||||||
|
// 当显示状态变为false时,重置actualPlacement为初始placement
|
||||||
|
if (!newVisible) {
|
||||||
|
setActualPlacement(placement);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 触发器事件处理
|
// 触发器事件处理
|
||||||
@@ -95,7 +105,10 @@ export function Tooltip({
|
|||||||
const arrowSize = 8; // 箭头大小
|
const arrowSize = 8; // 箭头大小
|
||||||
const gap = 10; // 提示框与触发元素的间距
|
const gap = 10; // 提示框与触发元素的间距
|
||||||
|
|
||||||
switch (placement) {
|
// 使用actualPlacement而不是placement来计算位置
|
||||||
|
let currentPlacement = actualPlacement;
|
||||||
|
|
||||||
|
switch (currentPlacement) {
|
||||||
case 'top':
|
case 'top':
|
||||||
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
||||||
top = triggerRect.top - tooltipRect.height - arrowSize;
|
top = triggerRect.top - tooltipRect.height - arrowSize;
|
||||||
@@ -104,7 +117,7 @@ export function Tooltip({
|
|||||||
break;
|
break;
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
|
||||||
top = triggerRect.bottom + arrowSize;
|
top = triggerRect.bottom + arrowSize - 8;
|
||||||
arrowLeft = tooltipRect.width / 2 - arrowSize;
|
arrowLeft = tooltipRect.width / 2 - arrowSize;
|
||||||
arrowTop = -arrowSize;
|
arrowTop = -arrowSize;
|
||||||
break;
|
break;
|
||||||
@@ -135,27 +148,105 @@ export function Tooltip({
|
|||||||
arrowLeft = Math.min(triggerRect.left + (triggerRect.width / 2) - left - arrowSize, tooltipRect.width - arrowSize * 2);
|
arrowLeft = Math.min(triggerRect.left + (triggerRect.width / 2) - left - arrowSize, tooltipRect.width - arrowSize * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top < 0) {
|
// 如果fixedPlacement为true,则强制使用指定的位置,不自动切换
|
||||||
if (placement === 'top') {
|
if (!fixedPlacement) {
|
||||||
// 如果上方放不下,切换到下方
|
// 自动调整位置逻辑
|
||||||
top = triggerRect.bottom + arrowSize;
|
if (top < 0) {
|
||||||
arrowTop = -arrowSize;
|
if (currentPlacement === 'top') {
|
||||||
tooltipRef.current.classList.remove('tooltip-top');
|
// 如果上方放不下,切换到下方
|
||||||
tooltipRef.current.classList.add('tooltip-bottom');
|
top = triggerRect.bottom + arrowSize;
|
||||||
} else {
|
arrowTop = -arrowSize;
|
||||||
top = gap;
|
|
||||||
arrowTop = Math.max(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, arrowSize * 2);
|
// 更新实际位置为bottom
|
||||||
|
setActualPlacement('bottom');
|
||||||
|
currentPlacement = 'bottom';
|
||||||
|
|
||||||
|
// 更新CSS类
|
||||||
|
tooltipRef.current.classList.remove('tooltip-top');
|
||||||
|
tooltipRef.current.classList.add('tooltip-bottom');
|
||||||
|
} else {
|
||||||
|
top = gap;
|
||||||
|
arrowTop = Math.max(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, arrowSize * 2);
|
||||||
|
}
|
||||||
|
} else if (top + tooltipRect.height > viewportHeight) {
|
||||||
|
if (currentPlacement === 'bottom') {
|
||||||
|
// 如果下方放不下,切换到上方
|
||||||
|
top = triggerRect.top - tooltipRect.height - arrowSize;
|
||||||
|
arrowTop = tooltipRect.height;
|
||||||
|
|
||||||
|
// 更新实际位置为top
|
||||||
|
setActualPlacement('top');
|
||||||
|
currentPlacement = 'top';
|
||||||
|
|
||||||
|
// 更新CSS类
|
||||||
|
tooltipRef.current.classList.remove('tooltip-bottom');
|
||||||
|
tooltipRef.current.classList.add('tooltip-top');
|
||||||
|
} else {
|
||||||
|
top = viewportHeight - tooltipRect.height - gap;
|
||||||
|
arrowTop = Math.min(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, tooltipRect.height - arrowSize * 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (top + tooltipRect.height > viewportHeight) {
|
|
||||||
if (placement === 'bottom') {
|
// 处理左右方向的切换
|
||||||
// 如果下方放不下,切换到上方
|
if (left < 0 && currentPlacement === 'left') {
|
||||||
top = triggerRect.top - tooltipRect.height - arrowSize;
|
// 如果左边放不下,切换到右边
|
||||||
arrowTop = tooltipRect.height;
|
left = triggerRect.right + arrowSize;
|
||||||
tooltipRef.current.classList.remove('tooltip-bottom');
|
arrowLeft = -arrowSize;
|
||||||
tooltipRef.current.classList.add('tooltip-top');
|
|
||||||
} else {
|
// 更新实际位置为right
|
||||||
top = viewportHeight - tooltipRect.height - gap;
|
setActualPlacement('right');
|
||||||
arrowTop = Math.min(triggerRect.top + (triggerRect.height / 2) - top - arrowSize, tooltipRect.height - arrowSize * 2);
|
currentPlacement = 'right';
|
||||||
|
|
||||||
|
// 更新CSS类
|
||||||
|
tooltipRef.current.classList.remove('tooltip-left');
|
||||||
|
tooltipRef.current.classList.add('tooltip-right');
|
||||||
|
} else if (left + tooltipRect.width > viewportWidth && currentPlacement === 'right') {
|
||||||
|
// 如果右边放不下,切换到左边
|
||||||
|
left = triggerRect.left - tooltipRect.width - arrowSize;
|
||||||
|
arrowLeft = tooltipRect.width;
|
||||||
|
|
||||||
|
// 更新实际位置为left
|
||||||
|
setActualPlacement('left');
|
||||||
|
currentPlacement = 'left';
|
||||||
|
|
||||||
|
// 更新CSS类
|
||||||
|
tooltipRef.current.classList.remove('tooltip-right');
|
||||||
|
tooltipRef.current.classList.add('tooltip-left');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 固定位置,但仍然需要确保在视口内可见
|
||||||
|
if (currentPlacement === 'top') {
|
||||||
|
// 如果提示框超出了顶部,增加偏移
|
||||||
|
if (top < 0) {
|
||||||
|
const originalTop = top;
|
||||||
|
top = gap;
|
||||||
|
// 调整箭头位置,使其指向触发元素
|
||||||
|
arrowTop = arrowTop + (originalTop - top);
|
||||||
|
}
|
||||||
|
} else if (currentPlacement === 'bottom') {
|
||||||
|
// 如果提示框超出了底部,减少偏移
|
||||||
|
if (top + tooltipRect.height > viewportHeight) {
|
||||||
|
const originalTop = top;
|
||||||
|
top = viewportHeight - tooltipRect.height - gap;
|
||||||
|
// 调整箭头位置,使其指向触发元素
|
||||||
|
arrowTop = arrowTop - (originalTop - top);
|
||||||
|
}
|
||||||
|
} else if (currentPlacement === 'left') {
|
||||||
|
// 如果提示框超出了左侧,增加偏移
|
||||||
|
if (left < 0) {
|
||||||
|
const originalLeft = left;
|
||||||
|
left = gap;
|
||||||
|
// 调整箭头位置,使其指向触发元素
|
||||||
|
arrowLeft = arrowLeft + (originalLeft - left);
|
||||||
|
}
|
||||||
|
} else if (currentPlacement === 'right') {
|
||||||
|
// 如果提示框超出了右侧,减少偏移
|
||||||
|
if (left + tooltipRect.width > viewportWidth) {
|
||||||
|
const originalLeft = left;
|
||||||
|
left = viewportWidth - tooltipRect.width - gap;
|
||||||
|
// 调整箭头位置,使其指向触发元素
|
||||||
|
arrowLeft = arrowLeft - (originalLeft - left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,35 +259,49 @@ export function Tooltip({
|
|||||||
// 定位箭头
|
// 定位箭头
|
||||||
const arrow = tooltipRef.current.querySelector('.tooltip-arrow') as HTMLElement;
|
const arrow = tooltipRef.current.querySelector('.tooltip-arrow') as HTMLElement;
|
||||||
if (arrow && showArrow) {
|
if (arrow && showArrow) {
|
||||||
switch (placement) {
|
// 使用当前实际的位置来定位箭头
|
||||||
|
switch (currentPlacement) {
|
||||||
case 'top':
|
case 'top':
|
||||||
arrow.style.bottom = `-${arrowSize}px`;
|
arrow.style.bottom = `-${arrowSize}px`;
|
||||||
arrow.style.left = `${arrowLeft}px`;
|
arrow.style.left = `${arrowLeft}px`;
|
||||||
arrow.style.top = 'auto';
|
arrow.style.top = 'auto';
|
||||||
arrow.style.right = 'auto';
|
arrow.style.right = 'auto';
|
||||||
|
// 设置箭头指向下方的样式
|
||||||
|
arrow.className = 'tooltip-arrow tooltip-arrow-bottom';
|
||||||
break;
|
break;
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
arrow.style.top = `-${arrowSize}px`;
|
arrow.style.top = `-${arrowSize}px`;
|
||||||
arrow.style.left = `${arrowLeft}px`;
|
arrow.style.left = `${arrowLeft}px`;
|
||||||
arrow.style.bottom = 'auto';
|
arrow.style.bottom = 'auto';
|
||||||
arrow.style.right = 'auto';
|
arrow.style.right = 'auto';
|
||||||
|
// 设置箭头指向上方的样式
|
||||||
|
arrow.className = 'tooltip-arrow tooltip-arrow-top';
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case 'left':
|
||||||
arrow.style.right = `-${arrowSize}px`;
|
arrow.style.right = `-${arrowSize}px`;
|
||||||
arrow.style.top = `${arrowTop}px`;
|
arrow.style.top = `${arrowTop}px`;
|
||||||
arrow.style.bottom = 'auto';
|
arrow.style.bottom = 'auto';
|
||||||
arrow.style.left = 'auto';
|
arrow.style.left = 'auto';
|
||||||
|
// 设置箭头指向右方的样式
|
||||||
|
arrow.className = 'tooltip-arrow tooltip-arrow-right';
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
arrow.style.left = `-${arrowSize}px`;
|
arrow.style.left = `-${arrowSize}px`;
|
||||||
arrow.style.top = `${arrowTop}px`;
|
arrow.style.top = `${arrowTop}px`;
|
||||||
arrow.style.bottom = 'auto';
|
arrow.style.bottom = 'auto';
|
||||||
arrow.style.right = 'auto';
|
arrow.style.right = 'auto';
|
||||||
|
// 设置箭头指向左方的样式
|
||||||
|
arrow.className = 'tooltip-arrow tooltip-arrow-left';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 当组件首次挂载或placement改变时,重置actualPlacement
|
||||||
|
useEffect(() => {
|
||||||
|
setActualPlacement(placement);
|
||||||
|
}, [placement]);
|
||||||
|
|
||||||
// 计算提示框位置
|
// 计算提示框位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVisible) return;
|
if (!isVisible) return;
|
||||||
@@ -217,7 +322,7 @@ export function Tooltip({
|
|||||||
window.removeEventListener('resize', updateTooltipPosition);
|
window.removeEventListener('resize', updateTooltipPosition);
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, [isVisible, placement, maxWidth, showArrow]);
|
}, [isVisible, placement, maxWidth, showArrow, fixedPlacement, actualPlacement]);
|
||||||
|
|
||||||
// 处理点击外部关闭
|
// 处理点击外部关闭
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -262,10 +367,11 @@ export function Tooltip({
|
|||||||
// 生成提示框类名
|
// 生成提示框类名
|
||||||
const tooltipClassNames = [
|
const tooltipClassNames = [
|
||||||
'tooltip-container',
|
'tooltip-container',
|
||||||
`tooltip-${placement}`,
|
`tooltip-${actualPlacement}`, // 使用actualPlacement而不是placement
|
||||||
`tooltip-${theme}`,
|
`tooltip-${theme}`,
|
||||||
rich ? 'tooltip-rich' : '',
|
rich ? 'tooltip-rich' : '',
|
||||||
isVisible ? 'tooltip-visible' : '',
|
isVisible ? 'tooltip-visible' : '',
|
||||||
|
fixedPlacement ? 'tooltip-fixed-placement' : '',
|
||||||
className
|
className
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
|
|||||||
@@ -103,27 +103,48 @@
|
|||||||
border-color: #3b82f6;
|
border-color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltip 位置基本样式 */
|
/* Tooltip 位置基本样式 - 根据箭头方向而不是容器位置 */
|
||||||
.tooltip-top .tooltip-arrow {
|
/* 箭头朝下 - 用于顶部提示框 */
|
||||||
|
.tooltip-arrow-bottom {
|
||||||
border-width: 8px 8px 0 8px;
|
border-width: 8px 8px 0 8px;
|
||||||
border-color: inherit transparent transparent transparent;
|
border-color: inherit transparent transparent transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-bottom .tooltip-arrow {
|
/* 箭头朝上 - 用于底部提示框 */
|
||||||
|
.tooltip-arrow-top {
|
||||||
border-width: 0 8px 8px 8px;
|
border-width: 0 8px 8px 8px;
|
||||||
border-color: transparent transparent inherit transparent;
|
border-color: transparent transparent inherit transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-left .tooltip-arrow {
|
/* 箭头朝右 - 用于左侧提示框 */
|
||||||
|
.tooltip-arrow-right {
|
||||||
border-width: 8px 0 8px 8px;
|
border-width: 8px 0 8px 8px;
|
||||||
border-color: transparent transparent transparent inherit;
|
border-color: transparent transparent transparent inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-right .tooltip-arrow {
|
/* 箭头朝左 - 用于右侧提示框 */
|
||||||
|
.tooltip-arrow-left {
|
||||||
border-width: 8px 8px 8px 0;
|
border-width: 8px 8px 8px 0;
|
||||||
border-color: transparent inherit transparent transparent;
|
border-color: transparent inherit transparent transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 浅色主题下的箭头样式修正 */
|
||||||
|
.tooltip-light .tooltip-arrow-bottom {
|
||||||
|
border-color: #ffffff transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-light .tooltip-arrow-top {
|
||||||
|
border-color: transparent transparent #ffffff transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-light .tooltip-arrow-right {
|
||||||
|
border-color: transparent transparent transparent #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-light .tooltip-arrow-left {
|
||||||
|
border-color: transparent #ffffff transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* 富文本提示框样式 */
|
/* 富文本提示框样式 */
|
||||||
.tooltip-rich .tooltip-content {
|
.tooltip-rich .tooltip-content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -168,23 +189,6 @@
|
|||||||
border-top-color: #e2e8f0;
|
border-top-color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 浅色主题下的箭头样式修正 */
|
|
||||||
.tooltip-light.tooltip-top .tooltip-arrow {
|
|
||||||
border-color: #ffffff transparent transparent transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-light.tooltip-bottom .tooltip-arrow {
|
|
||||||
border-color: transparent transparent #ffffff transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-light.tooltip-left .tooltip-arrow {
|
|
||||||
border-color: transparent transparent transparent #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-light.tooltip-right .tooltip-arrow {
|
|
||||||
border-color: transparent #ffffff transparent transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表格样式 */
|
/* 表格样式 */
|
||||||
.tooltip-content table {
|
.tooltip-content table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|||||||
Reference in New Issue
Block a user