mirror of
https://github.com/penpot/penpot.git
synced 2025-07-18 00:58:12 +02:00
🎉 Create tooltip DS component (#6340)
* ✨ Add new tooltip DS component * 🎉 Add delay * 🎉 Update docs and stories * 🎉 Add configurable delay * ♻️ Fix comments * ♻️ Fix comments
This commit is contained in:
parent
c45187eedd
commit
e2918f4148
8 changed files with 628 additions and 2 deletions
202
frontend/src/app/main/ui/ds/tooltip/tooltip.cljs
Normal file
202
frontend/src/app/main/ui/ds/tooltip/tooltip.cljs
Normal file
|
@ -0,0 +1,202 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.tooltip.tooltip
|
||||
(:require-macros
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.timers :as ts]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- calculate-tooltip-rect [tooltip trigger-rect placement offset]
|
||||
(let [{trigger-top :top
|
||||
trigger-left :left
|
||||
trigger-right :right
|
||||
trigger-bottom :bottom
|
||||
trigger-width :width
|
||||
trigger-height :height} trigger-rect
|
||||
|
||||
{tooltip-width :width
|
||||
tooltip-height :height} (dom/get-bounding-rect tooltip)
|
||||
|
||||
offset (d/nilv offset 2)
|
||||
overlay-offset 32]
|
||||
|
||||
(case placement
|
||||
"bottom"
|
||||
{:top (+ trigger-bottom offset)
|
||||
:left (- (+ trigger-left (/ trigger-width 2)) (/ tooltip-width 2))
|
||||
:right (+ (- (+ trigger-left (/ trigger-width 2)) (/ tooltip-width 2)) tooltip-width)
|
||||
:bottom (+ (- trigger-bottom offset) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"left"
|
||||
{:top (- (+ trigger-top (/ trigger-height 2) 8) (/ tooltip-height 2))
|
||||
:left (- trigger-left tooltip-width 12)
|
||||
:right (+ (- trigger-left tooltip-width 12) tooltip-width)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2) 12) (/ tooltip-height 2)) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"right"
|
||||
{:top (- (+ trigger-top (/ trigger-height 2) 4) (/ tooltip-height 2))
|
||||
:left (+ trigger-right offset 4)
|
||||
:right (+ trigger-right offset tooltip-width 4)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2) 4) (/ tooltip-height 2)) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"bottom-right"
|
||||
{:top (+ trigger-bottom offset)
|
||||
:left (- trigger-right overlay-offset)
|
||||
:right (+ (- trigger-right overlay-offset) tooltip-width)
|
||||
:bottom (+ (- trigger-bottom offset) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"bottom-left"
|
||||
{:top (+ trigger-bottom offset)
|
||||
:left (+ (- trigger-left tooltip-width) overlay-offset)
|
||||
:right (+ (- trigger-left tooltip-width) overlay-offset tooltip-width)
|
||||
:bottom (+ (- trigger-bottom offset) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"top-right"
|
||||
{:top (- trigger-top offset tooltip-height)
|
||||
:left (- trigger-right overlay-offset)
|
||||
:right (+ (- trigger-right overlay-offset) tooltip-width)
|
||||
:bottom (+ (- trigger-top offset tooltip-height) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"top-left"
|
||||
{:top (- trigger-top offset tooltip-height)
|
||||
:left (+ (- trigger-left tooltip-width) overlay-offset)
|
||||
:right (+ (- trigger-left tooltip-width) overlay-offset tooltip-width)
|
||||
:bottom (+ (- trigger-top offset tooltip-height) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
{:top (- trigger-top offset tooltip-height)
|
||||
:left (- (+ trigger-left (/ trigger-width 2)) (/ tooltip-width 2))
|
||||
:right (+ (- (+ trigger-left (/ trigger-width 2)) (/ tooltip-width 2)) tooltip-width)
|
||||
:bottom (+ (- trigger-top offset tooltip-height) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height})))
|
||||
|
||||
(defn- get-fallback-order [placement]
|
||||
(case placement
|
||||
"top" ["top" "right" "bottom" "left" "top-right" "bottom-right" "bottom-left" "top-left"]
|
||||
"bottom" ["bottom" "left" "top" "right" "bottom-right" "bottom-left" "top-left" "top-right"]
|
||||
"left" ["left" "top" "right" "bottom" "top-left" "top-right" "bottom-right" "bottom-left"]
|
||||
"right" ["right" "bottom" "left" "top" "bottom-left" "top-left" "top-right" "bottom-right"]
|
||||
"top-right" ["top-right" "right" "bottom" "left" "top" "bottom-right" "bottom-left" "top-left"]
|
||||
"bottom-right" ["bottom-right" "bottom" "left" "top" "right" "bottom-left" "top-left" "top-right"]
|
||||
"bottom-left" ["bottom-left" "left" "top" "right" "bottom" "top-left" "top-right" "bottom-right"]
|
||||
"top-left" ["top-left" "top" "right" "bottom" "left" "bottom-left" "top-right" "bottom-right"]))
|
||||
|
||||
(def ^:private schema:tooltip
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:id :string]
|
||||
[:offset {:optional true} :int]
|
||||
[:delay {:optional true} :int]
|
||||
[:placement {:optional true}
|
||||
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]])
|
||||
|
||||
(mf/defc tooltip*
|
||||
{::mf/props :obj
|
||||
::mf/schema schema:tooltip}
|
||||
[{:keys [class id children tooltip-content placement offset delay] :rest props}]
|
||||
(let [placement* (mf/use-state #(d/nilv placement "top"))
|
||||
placement (deref placement*)
|
||||
delay (d/nilv delay 300)
|
||||
|
||||
schedule-ref (mf/use-ref nil)
|
||||
|
||||
position-tooltip
|
||||
(fn [^js tooltip trigger-rect]
|
||||
(let [all-placements (get-fallback-order placement)]
|
||||
(.showPopover ^js tooltip)
|
||||
(loop [[current-placement & remaining-placements] all-placements]
|
||||
(when current-placement
|
||||
(reset! placement* current-placement)
|
||||
(let [tooltip-rect (calculate-tooltip-rect tooltip trigger-rect current-placement offset)]
|
||||
(if (dom/is-bounding-rect-outside? tooltip-rect)
|
||||
(recur remaining-placements)
|
||||
(do (dom/set-css-property! tooltip "display" "grid")
|
||||
(dom/set-css-property! tooltip "top" (dm/str (:top tooltip-rect) "px"))
|
||||
(dom/set-css-property! tooltip "left" (dm/str (:left tooltip-rect) "px")))))))))
|
||||
|
||||
on-show
|
||||
(mf/use-fn
|
||||
(mf/deps id placement)
|
||||
(fn [event]
|
||||
(when-let [schedule (mf/ref-val schedule-ref)]
|
||||
(ts/dispose! schedule)
|
||||
(mf/set-ref-val! schedule-ref nil))
|
||||
(when-let [tooltip (dom/get-element id)]
|
||||
(let [trigger-rect (->> (dom/get-current-target event)
|
||||
(dom/get-bounding-rect))]
|
||||
(mf/set-ref-val!
|
||||
schedule-ref
|
||||
(ts/schedule
|
||||
delay
|
||||
#(position-tooltip tooltip trigger-rect)))))))
|
||||
|
||||
on-hide
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn [] (when-let [tooltip (dom/get-element id)]
|
||||
(when-let [schedule (mf/ref-val schedule-ref)]
|
||||
(ts/dispose! schedule)
|
||||
(mf/set-ref-val! schedule-ref nil))
|
||||
(dom/set-css-property! tooltip "display" "none")
|
||||
(.hidePopover ^js tooltip))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps on-hide)
|
||||
(fn [event]
|
||||
(when (kbd/esc? event)
|
||||
(on-hide))))
|
||||
|
||||
class (d/append-class class (stl/css-case
|
||||
:tooltip true
|
||||
:tooltip-top (= placement "top")
|
||||
:tooltip-bottom (= placement "bottom")
|
||||
:tooltip-left (= placement "left")
|
||||
:tooltip-right (= placement "right")
|
||||
:tooltip-top-right (= placement "top-right")
|
||||
:tooltip-bottom-right (= placement "bottom-right")
|
||||
:tooltip-bottom-left (= placement "bottom-left")
|
||||
:tooltip-top-left (= placement "top-left")))
|
||||
|
||||
props (mf/spread-props props {:on-mouse-enter on-show
|
||||
:on-mouse-leave on-hide
|
||||
:on-focus on-show
|
||||
:on-blur on-hide
|
||||
:on-key-down handle-key-down
|
||||
:class (stl/css :tooltip-trigger)
|
||||
:aria-describedby id})]
|
||||
[:> :div props
|
||||
children
|
||||
[:div {:class class
|
||||
:id id
|
||||
:popover "auto"
|
||||
:role "tooltip"}
|
||||
[:div {:class (stl/css :tooltip-content)}
|
||||
(if (fn? tooltip-content)
|
||||
(tooltip-content)
|
||||
tooltip-content)]
|
||||
[:div {:class (stl/css :tooltip-arrow)
|
||||
:id "tooltip-arrow"}]]]))
|
Loading…
Add table
Add a link
Reference in a new issue