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