Tooltip shouldWrapChildren 的運作原理
Tooltip 預設行為是用 React.cloneElement 把 hover 事件直接注入到 child element:
// Chakra Tooltip 內部大致做法
const child = React.Children.only(children);
return React.cloneElement(child, { onMouseEnter, onMouseLeave, ref });沒有 shouldWrapChildren 的情況
// 結構
<Tooltip label="...">
<ProjectLockGuard> {/* ← Tooltip 把 onMouseEnter/onMouseLeave 加到這裡 */}
<Button>...</Button>
</ProjectLockGuard>
</Tooltip>ProjectLockGuard 的 props 介面只有 children:
interface ProjectLockGuardProps {
children: React.ReactElement;
}
const ProjectLockGuard: React.FC<ProjectLockGuardProps> = ({ children }) => {
// onMouseEnter, onMouseLeave 被丟掉了,沒有 forward 給 Button
return children;
};Tooltip 注入的 onMouseEnter / onMouseLeave 被 ProjectLockGuard 吃掉,不會傳到 Button → hover 無反應,tooltip 不會出現。
加了 shouldWrapChildren 的情況
// 結構
<Tooltip label="..." shouldWrapChildren>
<span> {/* ← Tooltip 自動產生的 <span>,hover 事件掛在這 */}
<ProjectLockGuard>
<Button>...</Button>
</ProjectLockGuard>
</span>
</Tooltip>Tooltip 改成在外面包一個 <span>,hover 事件掛在 <span> 上,不需要對 child 做 cloneElement,所以不管裡面是什麼 component 都能正常觸發。
前後對比
Before — tooltip 不會出現:
<Tooltip label={t('tooltipBulkSetAllocation')}>
<ProjectLockGuard> {/* ← cloneElement 加在這,props 被丟掉 */}
<Button>...</Button>
</ProjectLockGuard>
</Tooltip>After — tooltip 正常運作:
<Tooltip
label={t('tooltipBulkSetAllocation')}
shouldWrapChildren // ← 多包一層 <span> 接收 hover 事件
isDisabled={isLocked} // ← locked 時不顯示此 tooltip,改由 ProjectLockGuard 顯示 lock tooltip
>
<ProjectLockGuard>
<Button>...</Button>
</ProjectLockGuard>
</Tooltip>狀態對照表
| 狀態 | 外層 Tooltip | ProjectLockGuard | 結果 |
|---|---|---|---|
| Not locked | isDisabled=false → 顯示業務 tooltip | 直接 return Button | hover 顯示 tooltipBulkSetAllocation |
| Locked | isDisabled=true → 不顯示 | 包自己的 lock Tooltip + disabled Button | hover 顯示 lock 提示,按鈕不可點擊 |
Related
- Chakra Tooltip 邊界顯示時 scrollbar 閃爍的修法 — 同一個 Tooltip 的另一個坑(popper 預設
absolutestrategy 導致 scrollbar 閃爍)