From 452a620257e11b26dbd18d82dca728c741153ff7 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Wed, 24 Apr 2024 16:06:16 +0200 Subject: [PATCH] tooltip arrow --- .../features/map/popups/MapPopups.tsx | 16 +++- .../features/map/popups/Popup.module.css | 31 ++++++++ .../features/map/popups/PopupWrapper.tsx | 76 ++++++++++++++----- 3 files changed, 103 insertions(+), 20 deletions(-) diff --git a/apps/fishing-map/features/map/popups/MapPopups.tsx b/apps/fishing-map/features/map/popups/MapPopups.tsx index 9f2f784b85..ea5dc4edd5 100644 --- a/apps/fishing-map/features/map/popups/MapPopups.tsx +++ b/apps/fishing-map/features/map/popups/MapPopups.tsx @@ -1,17 +1,27 @@ -import { Fragment } from 'react' +import { Fragment, useCallback } from 'react' import { useSelector } from 'react-redux' import { useMapHoverInteraction } from '@globalfishingwatch/deck-layer-composer' import PopupWrapper from 'features/map/popups/PopupWrapper' import { selectClickedEvent } from '../map.slice' +import { useClickedEventConnect } from '../map-interactions.hooks' function MapPopups() { const hoverInteraction = useMapHoverInteraction() const clickInteraction = useSelector(selectClickedEvent) + const { dispatchClickedEvent, cancelPendingInteractionRequests } = useClickedEventConnect() + + const closePopup = useCallback(() => { + // cleanFeatureState('click') + dispatchClickedEvent(null) + cancelPendingInteractionRequests() + }, [cancelPendingInteractionRequests, dispatchClickedEvent]) return ( - {/* {hoverInteraction && } */} - {clickInteraction && } + {hoverInteraction && } + {clickInteraction && ( + + )} ) } diff --git a/apps/fishing-map/features/map/popups/Popup.module.css b/apps/fishing-map/features/map/popups/Popup.module.css index 391d1a1c95..73a6043cfa 100644 --- a/apps/fishing-map/features/map/popups/Popup.module.css +++ b/apps/fishing-map/features/map/popups/Popup.module.css @@ -4,6 +4,37 @@ z-index: 2; } +.arrow { + position: absolute; + width: 14px; + height: 14px; + z-index: 1; + pointer-events: none; + background: var(--color-white); + transform: rotate(45deg); +} + +.close { + position: absolute; + width: var(--size-S); + height: var(--size-S); + border-radius: 50%; + font: var(--font-L); + line-height: 1; + margin: var(--space-XS); + transition: opacity 0.15s linear, color 0.15s linear; + z-index: 1; + top: -2rem; + right: -2rem; + background-color: var(--color-primary-blue); + color: rgba(var(--white-rgb), 0.8); + opacity: 1; + border: var(--border-white); + display: flex; + align-items: center; + justify-content: center; +} + .popup.click { pointer-events: all; } diff --git a/apps/fishing-map/features/map/popups/PopupWrapper.tsx b/apps/fishing-map/features/map/popups/PopupWrapper.tsx index d50816e7b5..3299ceb553 100644 --- a/apps/fishing-map/features/map/popups/PopupWrapper.tsx +++ b/apps/fishing-map/features/map/popups/PopupWrapper.tsx @@ -1,4 +1,4 @@ -import { Fragment } from 'react' +import { Fragment, forwardRef, useRef } from 'react' import cx from 'classnames' import { groupBy } from 'lodash' import { useSelector } from 'react-redux' @@ -8,9 +8,10 @@ import { useFloating, detectOverflow, Middleware, + arrow, } from '@floating-ui/react' import { DataviewCategory } from '@globalfishingwatch/api-types' -import { Spinner } from '@globalfishingwatch/ui-components' +import { IconButton, Spinner } from '@globalfishingwatch/ui-components' import { InteractionEvent } from '@globalfishingwatch/deck-layer-composer' import { ContextPickingObject, VesselEventPickingObject } from '@globalfishingwatch/deck-layers' import { POPUP_CATEGORY_ORDER } from 'data/config' @@ -59,14 +60,43 @@ type PopupWrapperProps = { type?: 'hover' | 'click' } -function PopupWrapper({ - interaction, - closeButton = false, - closeOnClick = false, - type = 'hover', - className = '', - onClose, -}: PopupWrapperProps) { +const PopupArrow = forwardRef(function ( + { + arrow = {}, + placement, + }: { + arrow?: { x?: number; y?: number } + placement: string + }, + ref: any +) { + const { x, y } = arrow + const side = placement.split('-')[0] + const staticSide = { + top: 'bottom', + right: 'left', + bottom: 'top', + left: 'right', + }[side] as string + const arrowLen = ref?.current?.clientWidth || 0 + return ( +
+ ) +}) + +function PopupWrapper({ interaction, type = 'hover', className = '', onClose }: PopupWrapperProps) { // Assuming only timeComparison heatmap is visible, so timerange description apply to all const timeCompareTimeDescription = useTimeCompareTimeDescription() const mapViewport = useMapViewport() @@ -74,12 +104,20 @@ function PopupWrapper({ const activityInteractionStatus = useSelector(selectFishingInteractionStatus) const apiEventStatus = useSelector(selectApiEventStatus) - const { refs, floatingStyles /*, update, elements */ } = useFloating({ + const arrowRef = useRef(null) + const { refs, floatingStyles, placement, middlewareData /*, update, elements */ } = useFloating({ whileElementsMounted: autoUpdate, - middleware: [autoPlacement({ padding: 10 }), overflowMiddlware], + middleware: [ + autoPlacement({ padding: 10 }), + overflowMiddlware, + arrow({ + element: arrowRef, + padding: -5, + }), + ], }) - if (!mapViewport || !interaction || !interaction.features) return null + if (!mapViewport || !interaction || !interaction.features?.length) return null // const visibleFeatures = interaction.features.filter( // (feature) => feature.visible || feature.source === WORKSPACE_GENERATOR_ID @@ -104,7 +142,6 @@ function PopupWrapper({ // const cleanup = autoUpdate(elements.reference, elements.floating, update) // return cleanup // } - // }, [left, top, elements, update]) return (
- {/*
- -
*/} + {type === 'click' && ( + + )} + {type === 'click' && onClose !== undefined && ( +
+ +
+ )}
{timeCompareTimeDescription && (
{timeCompareTimeDescription}