mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-01 22:41:03 +08:00
120 lines
3.0 KiB
TypeScript
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)
|