Closed TFK70 closed 2 years ago
Сам тултип я вижу на 2 версии лаага, так как она предоставляет более гибкую работу с контролсами, что очень хорошо решает нашу проблему с отслеживанием состояния тултипа. Также на втором лааге не встречалась проблема описанная тут. Если вкратце - проблема заключается в том, что closeOnOutsideClick не обновлялся в тот момент, пока тултип был открыт. На втором лааге такой проблемы нет.
Как уже упоминалось, этот проп во второй версии лаага деприкейтед, но это потому что есть более гибкий способ управлять состоянием поповера. Пример:
use-click.hook.ts
import { useState } from 'react'
const useClick = () => {
const [isClicked, setClicked] = useState(false)
const close = () => setClicked(false)
const clickProps = {
onClick: () => setClicked(!isClicked)
}
return [isClicked, close, clickProps]
}
export { useClick }
const [isClicked, close, clickProps] = useClick()
const { triggerProps, layerProps, renderLayer } = useLayer({ isOpen: isClicked })
return (
<>
<div {...triggerProps}>Click me</div>
{isClicked && renderLayer(
<h1>Tooltip content</h1>
)}
</>
)
const [isClicked, close, clickProps] = useClick()
const { triggerProps, layerProps, renderLayer } = useLayer({ isOpen: isClicked, onOutsideClick: close })
return (
<>
<div {...triggerProps}>Click me</div>
{isClicked && renderLayer(
<h1>Tooltip content</h1>
)}
</>
)
В чем разница между первой версией?
Все переменные состояние мы контролируем сами, что дает нам возможность полностью контролировать эвент onOutsideClick. Если сделать его опциональным код будет выглядеть так:
onOutsideClick: closeOnOutsideClick ? close : () => {}
И он действительно будет обновляться даже когда поповер открыт.
Самый простой способ:
<Tooltip
trigger='click'
container={(close) => (
<>
<h1>You can click inside tooltip</h1>
<button onClick={close}>Or you may close it</button>
</>
)}
>
{(active, close) => (
<>
<h1>Now its {active ? 'active' : 'inactive'}</h1>
{active && <button onClick={close}>Close it</button>}
</>
)}
</Tooltip>
То есть мы обрабатываем дополнительный кейс когда чилдрен/контейнер - функция, и прокидываем туда как аргументы все необходимые контролсы
Похожее было реализовано тут
import React from 'react'
import { useLayer } from 'react-laag'
import { useHover } from 'react-laag'
import { useClick } from './hooks'
import { useContextMenu } from './hooks'
type Trigger = 'click' | 'hover' | 'menu'
const Tooltip = ({ trigger = 'hover', children, container, closeOnOutsideClick }) => {
const [isOver, hoverProps] = useHover()
const [isClicked, closeClick, clickProps] = useClick()
const [isContextMenu, closeContextMenu, contextMenuProps] = useContextMenu()
const close = trigger === 'click' ? closeClick : closeContextMenu
const getTrigger = () => {
if (trigger === 'hover') return isOver
if (trigger === 'click') return isClicked
if (trigger === 'menu') return isContextMenu
}
const { triggerProps, layerProps, renderLayer } = useLayer({
isOpen: getTrigger(),
onOutsideClick: closeOnOutsideClick ? close : () => {}
})
const getTriggerProps = () => {
if (trigger === 'hover') return { ...hoverProps, ...triggerProps }
if (trigger === 'click') return { ...clickProps, ...triggerProps }
if (trigger === 'menu') return { ...contextMenuProps, ...triggerProps }
return triggerProps
}
const getChildrenControls = () => {
if (trigger === 'hover') return [getTrigger(), () => {}]
if (trigger === 'click' || trigger === 'menu') return [getTrigger(), close]
}
const getContainerControls = () => {
if (trigger === 'hover') return []
if (trigger === 'click' || trigger === 'menu') return [close]
}
return (
<>
<div {...getTriggerProps()}>
{typeof children === 'function' ? children(...getChildrenControls()) : children}
</div>
{getTrigger() && renderLayer(<div {...layerProps}>{typeof container === 'function' ? container(...getContainerControls()) : container}</div>)}
</>
)
}
export { Tooltip }
Вот так выглядит его использование (сторисы):
import React, { useState } from 'react'
import { Tooltip } from './tooltip.component'
export const Hover = () => {
return (
<Tooltip
container={<div>Tooltip</div>}
>
Over me
</Tooltip>
)
}
export const Click = () => {
return (
<Tooltip
trigger='click'
container={<div>Tooltip</div>}
>
Click me
</Tooltip>
)
}
export const DynamicOutsideClick = () => {
const [outsideClick, setOutsideClick] = useState(false)
return (
<>
<Tooltip
closeOnOutsideClick={outsideClick}
trigger='click'
container={<div>Tooltip</div>}
>
Click me
</Tooltip>
<button onClick={() => setOutsideClick(!outsideClick)}>Change outside click. Now {outsideClick ? 'true' : 'false'}</button>
</>
)
}
export const TrackState = () => {
return (
<Tooltip
trigger='click'
container={(close) => (
<>
<h1>You can click inside tooltip</h1>
<button onClick={close}>Or you may close it</button>
</>
)}
>
{(active, close) => (
<>
<h1>Now its {active ? 'active' : 'inactive'}</h1>
{active && <button onClick={close}>Close it</button>}
</>
)}
</Tooltip>
)
}
export const ContextMenu = () => {
return (
<Tooltip
trigger='menu'
container={<h1>Menu</h1>}
closeOnOutsideClick={true}
>
<h1>Rght click</h1>
</Tooltip>
)
}
export default {
title: 'Components/Tooltip',
}
Компонент используется для создания поповеров
Примеры мест в дизайне: профиль, баланс и уведомления
Самая большая проблема - это поповер + модалка. Так как модалка в доме находится за пределами поповера, то любое нажатие в области модалки будет считаться как outsideClick для поповера, и он как следствие будет закрываться когда нам не надо. Но и так как модалка маунтится внутри поповера - то она исчезает вместе с поповером как его чилдрен (еще и без анимации потому что AnimatePresence маунтится там же)
С чем связан запрос на фичу?
Текущая реализация тултипа блокирует фичи
Расскажите как вы это себе видите
Приложите примеры реализаций
В итоге хотелось бы увидеть API похожее на Card