Files
react/components/tooltip/tooltip.tsx
2020-05-06 14:25:10 +08:00

120 lines
3.0 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react'
import withDefaults from '../utils/with-defaults'
import TooltipContent from './tooltip-content'
import useClickAway from '../utils/use-click-away'
import { TriggerTypes, Placement, SnippetTypes } from '../utils/prop-types'
export type TooltipOnVisibleChange = (visible: boolean) => void
interface Props {
text: string | React.ReactNode
type?: SnippetTypes
placement?: Placement
visible?: boolean
initialVisible?: boolean
hideArrow?: boolean
trigger?: TriggerTypes
enterDelay?: number
leaveDelay?: number
offset?: number
className?: string
portalClassName?: string
onVisibleChange?: TooltipOnVisibleChange
}
const defaultProps = {
initialVisible: false,
hideArrow: false,
type: 'default' as SnippetTypes,
trigger: 'hover' as TriggerTypes,
placement: 'top' as Placement,
enterDelay: 100,
leaveDelay: 0,
offset: 12,
className: '',
portalClassName: '',
onVisibleChange: (() => {}) as TooltipOnVisibleChange,
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TooltipProps = Props & typeof defaultProps & NativeAttrs
const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
children,
initialVisible,
text,
offset,
placement,
portalClassName,
enterDelay,
leaveDelay,
trigger,
type,
className,
onVisibleChange,
hideArrow,
visible: customVisible,
...props
}) => {
const timer = useRef<number>()
const ref = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState<boolean>(initialVisible)
const contentProps = {
type,
visible,
offset,
placement,
hideArrow,
parent: ref,
className: portalClassName,
}
const changeVisible = (nextState: boolean) => {
const clear = () => {
clearTimeout(timer.current)
timer.current = undefined
}
const handler = (nextState: boolean) => {
setVisible(nextState)
onVisibleChange(nextState)
clear()
}
clear()
if (nextState) {
timer.current = window.setTimeout(() => handler(true), enterDelay)
return
}
timer.current = window.setTimeout(() => handler(false), leaveDelay)
}
const mouseEventHandler = (next: boolean) => trigger === 'hover' && changeVisible(next)
const clickEventHandler = () => trigger === 'click' && changeVisible(!visible)
useClickAway(ref, () => trigger === 'click' && changeVisible(false))
useEffect(() => {
if (customVisible === undefined) return
changeVisible(customVisible)
}, [customVisible])
return (
<div
ref={ref}
className={`tooltip ${className}`}
onClick={clickEventHandler}
onMouseEnter={() => mouseEventHandler(true)}
onMouseLeave={() => mouseEventHandler(false)}
{...props}>
{children}
<TooltipContent {...contentProps}>{text}</TooltipContent>
<style jsx>{`
.tooltip {
width: max-content;
}
`}</style>
</div>
)
}
export default withDefaults(Tooltip, defaultProps)