diff --git a/web/chat/tooltip-provider.js b/web/chat/tooltip-provider.js index d311f239a..41f17626c 100644 --- a/web/chat/tooltip-provider.js +++ b/web/chat/tooltip-provider.js @@ -1,140 +1,172 @@ // @flow +import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import type { TooltipPositionStyle } from './tooltip-utils'; +import css from './tooltip.css'; const onMouseLeaveSourceDisappearTimeoutMs = 200; const onMouseLeaveTooltipDisappearTimeoutMs = 100; export type RenderTooltipParams = { +newNode: React.Node, +tooltipPositionStyle: TooltipPositionStyle, }; export type RenderTooltipResult = { +onMouseLeaveCallback: () => mixed, +clearTooltip: () => mixed, }; type TooltipContextType = { +renderTooltip: (params: RenderTooltipParams) => RenderTooltipResult, +clearTooltip: () => mixed, }; const TooltipContext: React.Context = React.createContext( { renderTooltip: () => ({ onMouseLeaveCallback: () => {}, clearTooltip: () => {}, }), clearTooltip: () => {}, }, ); type Props = { +children: React.Node, }; function TooltipProvider(props: Props): React.Node { const { children } = props; // eslint-disable-next-line no-unused-vars const tooltipSymbol = React.useRef(null); // eslint-disable-next-line no-unused-vars const tooltipCancelTimer = React.useRef(null); // eslint-disable-next-line no-unused-vars const [tooltipNode, setTooltipNode] = React.useState(null); const [ // eslint-disable-next-line no-unused-vars tooltipPosition, // eslint-disable-next-line no-unused-vars setTooltipPosition, ] = React.useState(null); const clearTooltip = React.useCallback((tooltipToClose: symbol) => { if (tooltipSymbol.current !== tooltipToClose) { return; } tooltipCancelTimer.current = null; setTooltipNode(null); setTooltipPosition(null); tooltipSymbol.current = null; }, []); const clearCurrentTooltip = React.useCallback(() => { if (tooltipSymbol.current) { clearTooltip(tooltipSymbol.current); } }, [clearTooltip]); const renderTooltip = React.useCallback( ({ newNode, tooltipPositionStyle: newTooltipPosition, }: RenderTooltipParams): RenderTooltipResult => { setTooltipNode(newNode); setTooltipPosition(newTooltipPosition); const newNodeSymbol = Symbol(); tooltipSymbol.current = newNodeSymbol; if (tooltipCancelTimer.current) { clearTimeout(tooltipCancelTimer.current); } return { onMouseLeaveCallback: () => { const newTimer = setTimeout( () => clearTooltip(newNodeSymbol), onMouseLeaveSourceDisappearTimeoutMs, ); tooltipCancelTimer.current = newTimer; }, clearTooltip: () => clearTooltip(newNodeSymbol), }; }, [clearTooltip], ); // eslint-disable-next-line no-unused-vars const onMouseEnterTooltip = React.useCallback(() => { if (tooltipSymbol.current) { clearTimeout(tooltipCancelTimer.current); } }, []); // eslint-disable-next-line no-unused-vars const onMouseLeaveTooltip = React.useCallback(() => { const timer = setTimeout( clearCurrentTooltip, onMouseLeaveTooltipDisappearTimeoutMs, ); tooltipCancelTimer.current = timer; }, [clearCurrentTooltip]); - const tooltip = React.useMemo(() => {}, []); + const tooltip = React.useMemo(() => { + if (!tooltipNode || !tooltipPosition) { + return null; + } + const tooltipContainerStyle = { + position: 'absolute', + top: tooltipPosition.yCoord, + left: tooltipPosition.xCoord, + }; + + const { verticalPosition, horizontalPosition } = tooltipPosition; + + const tooltipClassName = classNames(css.tooltipAbsolute, { + [css.tooltipAbsoluteLeft]: horizontalPosition === 'right', + [css.tooltipAbsoluteRight]: horizontalPosition === 'left', + [css.tooltipAbsoluteTop]: verticalPosition === 'bottom', + [css.tooltipAbsoluteBottom]: verticalPosition === 'top', + }); + + return ( +
+
+ {tooltipNode} +
+
+ ); + }, [onMouseEnterTooltip, onMouseLeaveTooltip, tooltipNode, tooltipPosition]); const value = React.useMemo( () => ({ renderTooltip, clearTooltip: clearCurrentTooltip, }), [renderTooltip, clearCurrentTooltip], ); return ( {children} {tooltip} ); } function useTooltipContext(): TooltipContextType { const context = React.useContext(TooltipContext); invariant(context, 'TooltipContext not found'); return context; } export { TooltipProvider, useTooltipContext }; diff --git a/web/chat/tooltip.css b/web/chat/tooltip.css index 518002577..2f2f190b8 100644 --- a/web/chat/tooltip.css +++ b/web/chat/tooltip.css @@ -1,48 +1,68 @@ div.messageTooltipMenu { position: absolute; background-color: var(--tool-tip-bg); color: var(--tool-tip-color); padding: 5px; border-radius: 5px; font-size: 14px; z-index: 1; white-space: nowrap; } div.messageTooltipMenu ul { list-style: none; } div.messageTooltipMenu li:not(:last-child) { padding-bottom: 5px; } div.messageTooltipMenu button { border: none; cursor: pointer; font-size: 14px; outline: none; background-color: black; color: white; text-decoration: underline; } div.messageTooltipMenu button:hover { background-color: black; color: #129aff; } div.messageLeftTooltip { transform: translate(0%, -50%); } div.messageRightTooltip { transform: translate(0%, -50%); } div.messageTimestampTooltip { display: flex; flex-direction: row; align-items: center; justify-content: center; padding: 6px; background-color: var(--message-action-tooltip-bg); border-radius: 8px; overflow: auto; min-width: 72px; } + +div.tooltipAbsolute { + position: absolute; +} + +div.tooltipAbsoluteLeft { + left: 0; +} + +div.tooltipAbsoluteRight { + right: 0; +} + +div.tooltipAbsoluteTop { + top: 0; +} + +div.tooltipAbsoluteBottom { + bottom: 0; +}