UNPKG

3.35 kBPlain TextView Raw
1import * as React from "react";
2import {
3 useSealedState,
4 SealedInitialState,
5} from "reakit-utils/useSealedState";
6import {
7 PopoverState,
8 PopoverActions,
9 PopoverInitialState,
10 usePopoverState,
11 PopoverStateReturn,
12} from "../Popover/PopoverState";
13import globalState from "./__globalState";
14
15export type TooltipState = Omit<PopoverState, "modal"> & {
16 /**
17 * @private
18 */
19 unstable_timeout: number;
20};
21
22export type TooltipActions = Omit<PopoverActions, "setModal"> & {
23 /**
24 * @private
25 */
26 unstable_setTimeout: React.Dispatch<
27 React.SetStateAction<TooltipState["unstable_timeout"]>
28 >;
29};
30
31export type TooltipInitialState = Omit<PopoverInitialState, "modal"> &
32 Pick<Partial<TooltipState>, "unstable_timeout">;
33
34export type TooltipStateReturn = Omit<
35 PopoverStateReturn,
36 "modal" | "setModal"
37> &
38 TooltipState &
39 TooltipActions;
40
41export function useTooltipState(
42 initialState: SealedInitialState<TooltipInitialState> = {}
43): TooltipStateReturn {
44 const {
45 placement = "top",
46 unstable_timeout: initialTimeout = 0,
47 ...sealed
48 } = useSealedState(initialState);
49 const [timeout, setTimeout] = React.useState(initialTimeout);
50 const showTimeout = React.useRef<number | null>(null);
51 const hideTimeout = React.useRef<number | null>(null);
52
53 const { modal, setModal, ...popover } = usePopoverState({
54 ...sealed,
55 placement,
56 });
57
58 const clearTimeouts = React.useCallback(() => {
59 if (showTimeout.current !== null) {
60 window.clearTimeout(showTimeout.current);
61 }
62 if (hideTimeout.current !== null) {
63 window.clearTimeout(hideTimeout.current);
64 }
65 }, []);
66
67 const hide = React.useCallback(() => {
68 clearTimeouts();
69 popover.hide();
70 // Let's give some time so people can move from a reference to another
71 // and still show tooltips immediately
72 hideTimeout.current = window.setTimeout(() => {
73 globalState.hide(popover.baseId);
74 }, timeout);
75 }, [clearTimeouts, popover.hide, timeout, popover.baseId]);
76
77 const show = React.useCallback(() => {
78 clearTimeouts();
79 if (!timeout || globalState.currentTooltipId) {
80 // If there's no timeout or a tooltip visible already, we can show this
81 // immediately
82 globalState.show(popover.baseId);
83 popover.show();
84 } else {
85 // There may be a reference with focus whose tooltip is still not visible
86 // In this case, we want to update it before it gets shown.
87 globalState.show(null);
88 // Otherwise, wait a little bit to show the tooltip
89 showTimeout.current = window.setTimeout(() => {
90 globalState.show(popover.baseId);
91 popover.show();
92 }, timeout);
93 }
94 }, [clearTimeouts, timeout, popover.show, popover.baseId]);
95
96 React.useEffect(() => {
97 return globalState.subscribe((id) => {
98 if (id !== popover.baseId) {
99 clearTimeouts();
100 if (popover.visible) {
101 // Make sure there will be only one tooltip visible
102 popover.hide();
103 }
104 }
105 });
106 }, [popover.baseId, clearTimeouts, popover.visible, popover.hide]);
107
108 React.useEffect(
109 () => () => {
110 clearTimeouts();
111 globalState.hide(popover.baseId);
112 },
113 [clearTimeouts, popover.baseId]
114 );
115
116 return {
117 ...popover,
118 hide,
119 show,
120 unstable_timeout: timeout,
121 unstable_setTimeout: setTimeout,
122 };
123}