import React, { FunctionComponent, ReactNode, useState, useEffect } from 'react'; import { styled } from '@storybook/theming'; import { logger } from '@storybook/client-logger'; // import { withState, lifecycle } from 'recompose'; import { document } from 'global'; import TooltipTrigger from 'react-popper-tooltip'; import { Modifiers, Placement } from 'popper.js'; import { Tooltip } from './Tooltip'; // A target that doesn't speak popper const TargetContainer = styled.div<{ mode: string }>` display: inline-block; cursor: ${props => (props.mode === 'hover' ? 'default' : 'pointer')}; `; const TargetSvgContainer = styled.g<{ mode: string }>` cursor: ${props => (props.mode === 'hover' ? 'default' : 'pointer')}; `; interface WithHideFn { onHide: () => void; } export interface WithTooltipPureProps { svg?: boolean; trigger?: 'none' | 'hover' | 'click' | 'right-click'; closeOnClick?: boolean; placement?: Placement; modifiers?: Modifiers; hasChrome?: boolean; tooltip: ReactNode | ((p: WithHideFn) => ReactNode); children: ReactNode; tooltipShown?: boolean; onVisibilityChange?: (visibility: boolean) => void; onDoubleClick?: () => void; } // Pure, does not bind to the body const WithTooltipPure: FunctionComponent = ({ svg, trigger, closeOnClick, placement, modifiers, hasChrome, tooltip, children, tooltipShown, onVisibilityChange, ...props }) => { const Container = svg ? TargetSvgContainer : TargetContainer; return ( ( {typeof tooltip === 'function' ? tooltip({ onHide: () => onVisibilityChange(false) }) : tooltip} )} > {({ getTriggerProps, triggerRef }) => ( // @ts-ignore {children} )} ); }; WithTooltipPure.defaultProps = { svg: false, trigger: 'hover', closeOnClick: false, placement: 'top', modifiers: {}, hasChrome: true, tooltipShown: false, }; const WithToolTipState: FunctionComponent = ({ startOpen, ...rest }) => { const [tooltipShown, onVisibilityChange] = useState(startOpen || false); useEffect(() => { const hide = () => onVisibilityChange(false); document.addEventListener('keydown', hide, false); // Find all iframes on the screen and bind to clicks inside them (waiting until the iframe is ready) const iframes: HTMLIFrameElement[] = Array.from(document.getElementsByTagName('iframe')); const unbinders: (() => void)[] = []; iframes.forEach(iframe => { const bind = () => { try { if (iframe.contentWindow.document) { iframe.contentWindow.document.addEventListener('click', hide); unbinders.push(() => { try { iframe.contentWindow.document.removeEventListener('click', hide); } catch (e) { logger.debug('Removing a click listener from iframe failed: ', e); } }); } } catch (e) { logger.debug('Adding a click listener to iframe failed: ', e); } }; bind(); // I don't know how to find out if it's already loaded so I potentially will bind twice iframe.addEventListener('load', bind); unbinders.push(() => { iframe.removeEventListener('load', bind); }); }); return () => { document.removeEventListener('keydown', hide); unbinders.forEach(unbind => { unbind(); }); }; }); return ( ); }; export { WithTooltipPure, WithToolTipState, WithToolTipState as WithTooltip };