mirror of
https://github.com/penpot/penpot.git
synced 2026-04-27 12:18:32 +00:00
🐛 Fix tooltip position on absolute positioned elements (#8509)
* 🐛 Fix tooltip position on absolute positioned elements * 🐛 Fix tests
This commit is contained in:
parent
0ceadada35
commit
c59cc4dff4
@ -94,7 +94,7 @@ test("Create a LINEAR gradient", async ({ page }) => {
|
||||
await expect(inputOpacityGlobal).toBeVisible();
|
||||
|
||||
await expect(
|
||||
workspacePage.page.getByText("Linear gradient").nth(1),
|
||||
workspacePage.page.getByText("Linear gradient")
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@ -178,7 +178,7 @@ test("Create a RADIAL gradient", async ({ page }) => {
|
||||
await expect(inputOpacityGlobal).toBeVisible();
|
||||
|
||||
await expect(
|
||||
workspacePage.page.getByText("Radial gradient").nth(1),
|
||||
workspacePage.page.getByText("Radial gradient")
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await brTokenPillSM.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const brTokenOptionXl = borderRadiusSection.getByLabel("borderRadius.xl");
|
||||
const brTokenOptionXl = borderRadiusSection.getByRole('option', { name: 'borderRadius.xl' })
|
||||
await expect(brTokenOptionXl).toBeVisible();
|
||||
await brTokenOptionXl.click();
|
||||
|
||||
@ -149,7 +149,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await detachButton.click();
|
||||
|
||||
// Open dropdown from input
|
||||
const dropdownBtn = layerMenuSection.getByLabel("Open token list");
|
||||
const dropdownBtn = layerMenuSection.getByRole('button', { name: 'Open token list' })
|
||||
await expect(dropdownBtn).toBeVisible();
|
||||
await dropdownBtn.click();
|
||||
|
||||
@ -225,8 +225,8 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(firstShadowFields).toBeVisible();
|
||||
|
||||
// Fill in the shadow values
|
||||
const offsetXInput = firstShadowFields.getByLabel("X");
|
||||
const offsetYInput = firstShadowFields.getByLabel("Y");
|
||||
const offsetXInput = firstShadowFields.getByRole('textbox', { name: 'X' });
|
||||
const offsetYInput = firstShadowFields.getByRole('textbox', { name: 'Y' });
|
||||
const blurInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
@ -299,8 +299,8 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(thirdShadowFields).toBeVisible();
|
||||
|
||||
// User adds values for the third shadow
|
||||
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
||||
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
||||
const thirdOffsetXInput = thirdShadowFields.getByRole('textbox', { name: 'X' });
|
||||
const thirdOffsetYInput = thirdShadowFields.getByRole('textbox', { name: 'Y' });
|
||||
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
@ -328,10 +328,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Verify that the first shadow kept its values
|
||||
const firstOffsetXValue = await firstShadowFields
|
||||
.getByLabel("X")
|
||||
.getByRole('textbox', { name: 'X' })
|
||||
.inputValue();
|
||||
const firstOffsetYValue = await firstShadowFields
|
||||
.getByLabel("Y")
|
||||
.getByRole('textbox', { name: 'Y' })
|
||||
.inputValue();
|
||||
const firstBlurValue = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
@ -357,10 +357,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(newSecondShadowFields).toBeVisible();
|
||||
|
||||
const secondOffsetXValue = await newSecondShadowFields
|
||||
.getByLabel("X")
|
||||
.getByRole('textbox', { name: 'X' })
|
||||
.inputValue();
|
||||
const secondOffsetYValue = await newSecondShadowFields
|
||||
.getByLabel("Y")
|
||||
.getByRole('textbox', { name: 'Y' })
|
||||
.inputValue();
|
||||
const secondBlurValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
@ -410,10 +410,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Verify first shadow values are still there
|
||||
const restoredFirstOffsetX = await firstShadowFields
|
||||
.getByLabel("X")
|
||||
.getByRole('textbox', { name: 'X' })
|
||||
.inputValue();
|
||||
const restoredFirstOffsetY = await firstShadowFields
|
||||
.getByLabel("Y")
|
||||
.getByRole('textbox', { name: 'Y' })
|
||||
.inputValue();
|
||||
const restoredFirstBlur = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
@ -433,10 +433,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Verify second shadow values are still there
|
||||
const restoredSecondOffsetX = await newSecondShadowFields
|
||||
.getByLabel("X")
|
||||
.getByRole('textbox', { name: 'X' })
|
||||
.inputValue();
|
||||
const restoredSecondOffsetY = await newSecondShadowFields
|
||||
.getByLabel("Y")
|
||||
.getByRole('textbox', { name: 'Y' })
|
||||
.inputValue();
|
||||
const restoredSecondBlur = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
@ -518,7 +518,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await dimensionSMTokenPill.nth(1).click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
const dimensionTokenOptionXl = measuresSection.getByRole('option', { name: 'dimension.xl' })
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
@ -572,7 +572,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await dimensionSMTokenPill.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
const dimensionTokenOptionXl = measuresSection.getByRole('option', { name: 'dimension.xl' });
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
@ -626,7 +626,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await dimensionSMTokenPill.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
const dimensionTokenOptionXl = measuresSection.getByRole('option', { name: 'dimension.xl' });
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
@ -682,7 +682,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl =
|
||||
borderRadiusSection.getByLabel("dimension.xl");
|
||||
borderRadiusSection.getByRole('option', { name: 'dimension.xl' });
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
@ -751,7 +751,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
});
|
||||
await tokenDropdown.click();
|
||||
|
||||
const widthOptionSmall = firstStrokeRow.getByLabel("width-small");
|
||||
const widthOptionSmall = firstStrokeRow.getByRole('option', { name: 'width-small' });
|
||||
await expect(widthOptionSmall).toBeVisible();
|
||||
await widthOptionSmall.click();
|
||||
const StrokeWidthPillSmall = firstStrokeRow.getByRole("button", {
|
||||
@ -831,15 +831,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
});
|
||||
await detachButton.click();
|
||||
await expect(marginPillXL).not.toBeVisible();
|
||||
const horizontalMarginInput = layoutItemSectionSidebar.getByText(
|
||||
"Horizontal marginOpen token",
|
||||
);
|
||||
await expect(horizontalMarginInput).toBeVisible();
|
||||
|
||||
const tokenDropdown = horizontalMarginInput.getByRole("button", {
|
||||
const horizontalMarginInput = layoutItemSectionSidebar.getByRole("button", {
|
||||
name: "Open token list",
|
||||
});
|
||||
await tokenDropdown.click();
|
||||
await horizontalMarginInput.nth(1).click();
|
||||
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
@ -1024,7 +1024,7 @@ test.describe("Tokens - creation", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
const valueField = tokensUpdateCreateModal.getByRole("textbox", {name: "Font Size"});
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
@ -1716,12 +1716,12 @@ test.describe("Tokens tab - edition", () => {
|
||||
|
||||
// Fill font-family to verify to verify that input value doesn't get split into list of characters
|
||||
const fontFamilyField = tokensUpdateCreateModal
|
||||
.getByLabel("Font family")
|
||||
.getByRole("textbox", { name: "Font family" })
|
||||
.first();
|
||||
await fontFamilyField.fill("OneWord");
|
||||
|
||||
// Invalidate incorrect values for font size
|
||||
const fontSizeField = tokensUpdateCreateModal.getByLabel(/Font Size/i);
|
||||
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" });
|
||||
await fontSizeField.fill("invalid");
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(/Invalid token value:/),
|
||||
@ -1736,13 +1736,13 @@ test.describe("Tokens tab - edition", () => {
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight" });
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
||||
const lineHeightField = tokensUpdateCreateModal.getByLabel(/Line Height/i);
|
||||
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
||||
tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing" });
|
||||
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height" });
|
||||
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case" });
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
||||
tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration" });
|
||||
|
||||
// Capture all values before switching tabs
|
||||
const originalValues = {
|
||||
@ -1800,6 +1800,7 @@ test.describe("Tokens tab - edition", () => {
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
});
|
||||
|
||||
await expect(colorToken).toBeVisible();
|
||||
await colorToken.click({ button: "right" });
|
||||
|
||||
|
||||
@ -33,6 +33,8 @@
|
||||
(let [variant
|
||||
(d/nilv variant "primary")
|
||||
|
||||
button-ref (mf/use-ref nil)
|
||||
|
||||
tooltip-id
|
||||
(mf/use-id)
|
||||
|
||||
@ -47,10 +49,12 @@
|
||||
props
|
||||
(mf/spread-props props
|
||||
{:class [class button-class]
|
||||
:ref button-ref
|
||||
:aria-labelledby tooltip-id})]
|
||||
|
||||
[:> tooltip* {:content aria-label
|
||||
:class tooltip-class
|
||||
:trigger-ref button-ref
|
||||
:placement tooltip-placement
|
||||
:id tooltip-id}
|
||||
[:> :button props
|
||||
|
||||
@ -28,7 +28,8 @@
|
||||
{::mf/schema schema:token-option}
|
||||
[{:keys [id name on-click selected ref focused resolved] :rest props}]
|
||||
(let [internal-id (mf/use-id)
|
||||
id (d/nilv id internal-id)]
|
||||
id (d/nilv id internal-id)
|
||||
element-ref (mf/use-ref nil)]
|
||||
[:li {:value id
|
||||
:class (stl/css-case :token-option true
|
||||
:option-with-pill true
|
||||
@ -50,10 +51,12 @@
|
||||
:aria-hidden (when name true)}]
|
||||
[:span {:class (stl/css :icon-placeholder)}])
|
||||
[:> tooltip* {:content name
|
||||
:trigger-ref element-ref
|
||||
:id (dm/str id "-name")
|
||||
:class (stl/css :option-text)}
|
||||
;; Add ellipsis
|
||||
[:span {:aria-labelledby (dm/str id "-name")}
|
||||
;; Add ellipsis
|
||||
[:span {:aria-labelledby (dm/str id "-name")
|
||||
:ref element-ref}
|
||||
name]]
|
||||
(when resolved
|
||||
[:> :span {:class (stl/css :option-pill)}
|
||||
|
||||
@ -84,6 +84,7 @@
|
||||
:on-click on-icon-click}])
|
||||
(if aria-label
|
||||
[:> tooltip* {:content aria-label
|
||||
:trigger-ref (or ref input-ref)
|
||||
:class (stl/css :tooltip-wrapper)
|
||||
:id tooltip-id}
|
||||
[:> "input" props]]
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
(tr "ds.inputs.token-field.no-active-token-option"))
|
||||
default-id (mf/use-id)
|
||||
id (d/nilv id default-id)
|
||||
pill-ref (mf/use-ref nil)
|
||||
|
||||
focus-wrapper
|
||||
(mf/use-fn
|
||||
@ -53,6 +54,7 @@
|
||||
(dom/focus! (mf/ref-val token-wrapper-ref)))))]
|
||||
[:> tooltip* {:content property
|
||||
:class (stl/css :token-field-wrapper)
|
||||
:trigger-ref token-wrapper-ref
|
||||
:id (dm/str default-id "-input")}
|
||||
[:div {:class [class (stl/css-case :token-field true
|
||||
:with-icon (some? slot-start)
|
||||
@ -70,8 +72,10 @@
|
||||
|
||||
[:div {:class (stl/css :content-wrapper)}
|
||||
[:> tooltip* {:content content
|
||||
:trigger-ref pill-ref
|
||||
:id (dm/str id "-pill")}
|
||||
[:button {:on-click on-click
|
||||
:ref pill-ref
|
||||
:class (stl/css-case :pill true
|
||||
:no-set-pill (not set-active?)
|
||||
:pill-disabled disabled)
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
(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]
|
||||
@ -15,10 +14,10 @@
|
||||
[app.util.timers :as ts]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private ^:const arrow-height 12)
|
||||
(def ^:private ^:const half-arrow-height (/ arrow-height 2))
|
||||
(def ^:private ^:const overlay-offset 32)
|
||||
|
||||
(defonce active-tooltip (atom nil))
|
||||
|
||||
(defn- clear-schedule
|
||||
[ref]
|
||||
(when-let [schedule (mf/ref-val ref)]
|
||||
@ -29,20 +28,6 @@
|
||||
[ref delay f]
|
||||
(mf/set-ref-val! ref (ts/schedule delay f)))
|
||||
|
||||
(defn- show-popover
|
||||
[node]
|
||||
(when (.-isConnected ^js node)
|
||||
(.showPopover ^js node)))
|
||||
|
||||
(defn- hide-popover
|
||||
[node]
|
||||
(when (and (some? node)
|
||||
(fn? (.-hidePopover node)))
|
||||
(dom/unset-css-property! node "block-size")
|
||||
(dom/unset-css-property! node "inset-block-start")
|
||||
(dom/unset-css-property! node "inset-inline-start")
|
||||
(.hidePopover ^js node)))
|
||||
|
||||
(defn- calculate-placement-bounding-rect
|
||||
"Given a placement, calcultates the bounding rect for it taking in
|
||||
account provided tooltip bounding rect and the origin bounding
|
||||
@ -72,18 +57,18 @@
|
||||
:height tooltip-height}
|
||||
|
||||
"left"
|
||||
{:top (- (+ trigger-top (/ trigger-height 2) half-arrow-height) (/ tooltip-height 2))
|
||||
:left (- trigger-left tooltip-width arrow-height)
|
||||
{:top (- (+ trigger-top (/ trigger-height 2)) (/ tooltip-height 2))
|
||||
:left (- trigger-left tooltip-width)
|
||||
:right (+ (- trigger-left tooltip-width) tooltip-width)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2) half-arrow-height) (/ tooltip-height 2)) tooltip-height)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2)) (/ tooltip-height 2)) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
"right"
|
||||
{:top (- (+ trigger-top (/ trigger-height 2) half-arrow-height) (/ tooltip-height 2))
|
||||
{:top (- (+ trigger-top (/ trigger-height 2)) (/ tooltip-height 2))
|
||||
:left (+ trigger-right offset)
|
||||
:right (+ trigger-right offset tooltip-width)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2) half-arrow-height) (/ tooltip-height 2)) tooltip-height)
|
||||
:bottom (+ (- (+ trigger-top (/ trigger-height 2)) (/ tooltip-height 2)) tooltip-height)
|
||||
:width tooltip-width
|
||||
:height tooltip-height}
|
||||
|
||||
@ -153,22 +138,6 @@
|
||||
(recur (rest placements))
|
||||
#js [placement placement-brect])))))
|
||||
|
||||
(defn- update-tooltip-position
|
||||
"Update the tooltip position having in account the current window
|
||||
size, placement. It calculates the appropriate placement and updates
|
||||
the dom with the result."
|
||||
[tooltip placement origin-brect offset]
|
||||
(show-popover tooltip)
|
||||
(let [tooltip-brect (dom/get-bounding-rect tooltip)
|
||||
tooltip-brect (assoc tooltip-brect :height (:height tooltip-brect) :width (:width tooltip-brect))
|
||||
window-size (dom/get-window-size)]
|
||||
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
|
||||
(let [height (:height placement-rect)]
|
||||
(dom/set-css-property! tooltip "block-size" (dm/str height "px"))
|
||||
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px"))
|
||||
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px")))
|
||||
placement)))
|
||||
|
||||
(def ^:private schema:tooltip
|
||||
[:map
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
@ -176,19 +145,26 @@
|
||||
[:offset {:optional true} :int]
|
||||
[:delay {:optional true} :int]
|
||||
[:content [:or fn? :string map?]]
|
||||
[:trigger-ref {:optional true} [:maybe :any]]
|
||||
[:placement {:optional true}
|
||||
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]])
|
||||
|
||||
(mf/defc tooltip*
|
||||
{::mf/schema schema:tooltip}
|
||||
[{:keys [class id children content placement offset delay] :rest props}]
|
||||
[{:keys [class id children content placement offset delay trigger-ref aria-label] :rest props}]
|
||||
(let [internal-id
|
||||
(mf/use-id)
|
||||
trigger-ref (mf/use-ref nil)
|
||||
internal-trigger-ref (mf/use-ref nil)
|
||||
trigger-ref (or trigger-ref internal-trigger-ref)
|
||||
|
||||
tooltip-ref (mf/use-ref nil)
|
||||
|
||||
id
|
||||
(d/nilv id internal-id)
|
||||
|
||||
tooltip-id
|
||||
(mf/use-id)
|
||||
|
||||
placement*
|
||||
(mf/use-state #(d/nilv placement "top"))
|
||||
|
||||
@ -201,35 +177,35 @@
|
||||
schedule-ref
|
||||
(mf/use-ref nil)
|
||||
|
||||
visible*
|
||||
(mf/use-state false)
|
||||
visible (deref visible*)
|
||||
|
||||
on-show
|
||||
(mf/use-fn
|
||||
(mf/deps id placement offset)
|
||||
(fn [event]
|
||||
|
||||
(let [current (dom/get-current-target event)
|
||||
related (dom/get-related-target event)
|
||||
is-node? (fn [node] (and node (.-nodeType node)))]
|
||||
(when-not (and related (is-node? related) (.contains current related))
|
||||
(clear-schedule schedule-ref)
|
||||
(when-let [tooltip (dom/get-element id)]
|
||||
(let [origin-brect
|
||||
(dom/get-bounding-rect (mf/ref-val trigger-ref))
|
||||
|
||||
update-position
|
||||
(fn []
|
||||
(let [new-placement (update-tooltip-position tooltip placement origin-brect offset)]
|
||||
(when (not= new-placement placement)
|
||||
(reset! placement* new-placement))))]
|
||||
|
||||
(add-schedule schedule-ref delay update-position)))))))
|
||||
(mf/deps tooltip-id delay)
|
||||
(fn [_]
|
||||
(let [trigger-el (mf/ref-val trigger-ref)]
|
||||
(clear-schedule schedule-ref)
|
||||
(add-schedule schedule-ref (d/nilv delay 300)
|
||||
(fn []
|
||||
(prn tooltip-id)
|
||||
(when-let [active @active-tooltip]
|
||||
(when (not= (:id active) tooltip-id)
|
||||
(when-let [tooltip-el (dom/get-element (:id active))]
|
||||
(dom/set-css-property! tooltip-el "display" "none"))
|
||||
(reset! active-tooltip nil)))
|
||||
(reset! active-tooltip {:id tooltip-id :trigger trigger-el})
|
||||
(reset! visible* true))))))
|
||||
|
||||
on-hide
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(mf/deps tooltip-id)
|
||||
(fn []
|
||||
(when-let [tooltip (dom/get-element id)]
|
||||
(clear-schedule schedule-ref)
|
||||
(hide-popover tooltip))))
|
||||
(clear-schedule schedule-ref)
|
||||
(reset! visible* false)
|
||||
(when (= (:id @active-tooltip) tooltip-id)
|
||||
(reset! active-tooltip nil))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
@ -250,28 +226,62 @@
|
||||
:tooltip-bottom-left (identical? placement "bottom-left")
|
||||
:tooltip-top-left (identical? placement "top-left"))
|
||||
|
||||
content
|
||||
(if (fn? content)
|
||||
(content)
|
||||
content)
|
||||
props
|
||||
(mf/spread-props props
|
||||
{:on-mouse-enter on-show
|
||||
:on-mouse-leave on-hide
|
||||
:on-focus on-show
|
||||
:on-blur on-hide
|
||||
:ref internal-trigger-ref
|
||||
:on-key-down handle-key-down
|
||||
:ref trigger-ref
|
||||
:id id
|
||||
:class [class (stl/css :tooltip-trigger)]
|
||||
:aria-describedby id})
|
||||
content
|
||||
(if (fn? content)
|
||||
(content)
|
||||
content)]
|
||||
:aria-label (if (string? content)
|
||||
content
|
||||
aria-label)})]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps visible placement offset)
|
||||
(fn []
|
||||
(when visible
|
||||
(let [trigger-el (mf/ref-val trigger-ref)
|
||||
tooltip-el (mf/ref-val tooltip-ref)]
|
||||
(when (and trigger-el tooltip-el)
|
||||
(js/requestAnimationFrame
|
||||
(fn []
|
||||
(let [origin-brect (dom/get-bounding-rect trigger-el)
|
||||
tooltip-brect (dom/get-bounding-rect tooltip-el)
|
||||
window-size (dom/get-window-size)]
|
||||
(when-let [[new-placement placement-rect]
|
||||
(find-matching-placement
|
||||
placement
|
||||
tooltip-brect
|
||||
origin-brect
|
||||
window-size
|
||||
offset)]
|
||||
(dom/set-css-property! tooltip-el "inset-block-start"
|
||||
(str (:top placement-rect) "px"))
|
||||
(dom/set-css-property! tooltip-el "inset-inline-start"
|
||||
(str (:left placement-rect) "px"))
|
||||
|
||||
(when (not= new-placement placement)
|
||||
(reset! placement* new-placement)))))))))))
|
||||
|
||||
[:> :div props
|
||||
children
|
||||
[:div {:class (stl/css :tooltip)
|
||||
:id id
|
||||
:popover "auto"
|
||||
:role "tooltip"}
|
||||
[:div {:class tooltip-class}
|
||||
[:div {:class (stl/css :tooltip-content)} content]
|
||||
[:div {:class (stl/css :tooltip-arrow)
|
||||
:id "tooltip-arrow"}]]]]))
|
||||
(when visible
|
||||
(mf/portal
|
||||
(mf/html
|
||||
[:div {:class (stl/css :tooltip)
|
||||
:role "tooltip"
|
||||
:id tooltip-id
|
||||
:ref tooltip-ref}
|
||||
[:div {:class tooltip-class}
|
||||
[:div {:class (stl/css :tooltip-content)} content]
|
||||
[:div {:class (stl/css :tooltip-arrow)
|
||||
:id "tooltip-arrow"}]]])
|
||||
(.-body js/document)))]))
|
||||
|
||||
@ -6,17 +6,19 @@
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/z-index.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
|
||||
$arrow-side: 12px;
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
max-inline-size: $sz-352;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
inline-size: fit-content;
|
||||
block-size: fit-content;
|
||||
z-index: var(--z-index-notifications);
|
||||
}
|
||||
|
||||
.tooltip-content-wrapper {
|
||||
|
||||
@ -96,8 +96,9 @@
|
||||
image (:image background)
|
||||
format (if id? "rounded" "square")
|
||||
element-id (mf/use-id)
|
||||
has-opacity? (and (some? (:color background))
|
||||
(< (:opacity background) 1))
|
||||
has-opacity? (and (some? (:color background))
|
||||
(< (:opacity background) 1))
|
||||
element-ref (mf/use-ref nil)
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps background on-click)
|
||||
@ -120,7 +121,8 @@
|
||||
(mf/spread-props props {:class class
|
||||
:on-click on-click
|
||||
:type button-type
|
||||
:aria-labelledby element-id})
|
||||
:aria-labelledby element-id
|
||||
:ref element-ref})
|
||||
children (mf/html
|
||||
[:> element-type props
|
||||
(cond
|
||||
@ -147,6 +149,7 @@
|
||||
[:> tooltip* {:content (if tooltip-content
|
||||
tooltip-content
|
||||
(color-title background))
|
||||
:trigger-ref element-ref
|
||||
:id element-id}
|
||||
children]
|
||||
|
||||
|
||||
@ -23,11 +23,12 @@
|
||||
|
||||
(mf/defc property-detail-copiable*
|
||||
{::mf/schema schema:property-detail-copiable}
|
||||
[{:keys [color token copied on-click children]}]
|
||||
[{:keys [color token copied on-click children ref]}]
|
||||
[:button {:class (stl/css-case :property-detail-copiable true
|
||||
:property-detail-copied copied
|
||||
:property-detail-copiable-color (some? color))
|
||||
:on-click on-click}
|
||||
:on-click on-click
|
||||
:ref ref}
|
||||
(when color
|
||||
[:> swatch* {:background color
|
||||
:size "small"}])
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
color-image-name (:name color-image)
|
||||
color-image-url (when (some? color-image)
|
||||
(cfg/resolve-file-media color-image))
|
||||
row-ref (mf/use-ref nil)
|
||||
color-opacity (mf/use-memo
|
||||
(mf/deps color)
|
||||
#(dm/str (-> color
|
||||
@ -96,6 +97,7 @@
|
||||
(if token
|
||||
[:> tooltip* {:id (:name token)
|
||||
:class (stl/css :tooltip-token-wrapper)
|
||||
:trigger-ref row-ref
|
||||
:content #(mf/html
|
||||
[:div {:class (stl/css :tooltip-token)}
|
||||
[:div {:class (stl/css :tooltip-token-title)}
|
||||
@ -104,6 +106,7 @@
|
||||
(:resolved-value token)]])}
|
||||
[:> property-detail-copiable* {:color color
|
||||
:token token
|
||||
:ref row-ref
|
||||
:copied copied
|
||||
:on-click copy-attr} formatted-color-value]]
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
copiable-value (if (some? token)
|
||||
(:name token)
|
||||
property)
|
||||
row-ref (mf/use-ref nil)
|
||||
|
||||
copy-attr
|
||||
(mf/use-fn
|
||||
@ -54,6 +55,7 @@
|
||||
(let [token-type (:type token)]
|
||||
[:> tooltip* {:id (:name token)
|
||||
:class (stl/css :tooltip-token-wrapper)
|
||||
:trigger-ref row-ref
|
||||
:content #(mf/html
|
||||
[:div {:class (stl/css :tooltip-token)}
|
||||
[:div {:class (stl/css :tooltip-token-title)}
|
||||
@ -75,6 +77,7 @@
|
||||
(:resolved-value token))]])}
|
||||
[:> property-detail-copiable* {:token token
|
||||
:copied copied
|
||||
:ref row-ref
|
||||
:on-click copy-attr} detail]])
|
||||
[:> property-detail-copiable* {:copied copied
|
||||
:on-click copy-attr} detail])
|
||||
|
||||
@ -44,12 +44,15 @@
|
||||
(on-token-pill-click event token)))
|
||||
id-tooltip (mf/use-id)
|
||||
resolved (:resolved-value token)
|
||||
color-value (dwta/value->color resolved)]
|
||||
color-value (dwta/value->color resolved)
|
||||
item-ref (mf/use-ref nil)]
|
||||
[:> tooltip* {:id id-tooltip
|
||||
:style {:width "100%"}
|
||||
:trigger-ref item-ref
|
||||
:content (:name token)}
|
||||
[:button {:class (stl/css-case :color-token-item true
|
||||
:color-token-selected selected)
|
||||
:ref item-ref
|
||||
:aria-labelledby id-tooltip
|
||||
:on-click on-click}
|
||||
[:> swatch* {:background color-value
|
||||
|
||||
@ -94,6 +94,7 @@
|
||||
not-active (or (empty? active-tokens)
|
||||
(nil? token))
|
||||
id (dm/str (:id token) "-name")
|
||||
token-name-ref (mf/use-ref nil)
|
||||
swatch-tooltip-content (cond
|
||||
not-active
|
||||
(tr "ds.inputs.token-field.no-active-token-option")
|
||||
@ -126,8 +127,11 @@
|
||||
:size "small"}]]
|
||||
[:> tooltip* {:content name-tooltip-content
|
||||
:id id
|
||||
:aria-label (str (tr "workspace.tokens.token-name") ": " applied-token-name)
|
||||
:trigger-ref token-name-ref
|
||||
:class (stl/css :token-tooltip)}
|
||||
[:div {:class (stl/css :token-name)
|
||||
:ref token-name-ref
|
||||
:aria-labelledby id}
|
||||
(or token-name applied-token-name)]]
|
||||
[:div {:class (stl/css :token-actions)}
|
||||
|
||||
@ -4,17 +4,27 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/z-index.scss" as *;
|
||||
|
||||
.token-modal-wrapper {
|
||||
@extend .modal-container-base;
|
||||
@include deprecated.menuShadow;
|
||||
border-radius: $br-4;
|
||||
background-color: var(--color-background-primary);
|
||||
border: $b-2 solid var(--color-background-quaternary);
|
||||
min-width: $sz-364;
|
||||
min-height: $sz-192;
|
||||
max-width: $sz-512;
|
||||
max-height: $sz-512;
|
||||
box-shadow: 0px 0px $sz-12 0px var(--color-shadow-dark);
|
||||
position: absolute;
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
z-index: 11;
|
||||
z-index: var(--z-index-set);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: var(--sp-xxxl);
|
||||
&.token-modal-large {
|
||||
max-block-size: 95vh;
|
||||
}
|
||||
@ -22,6 +32,6 @@
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: deprecated.$s-6;
|
||||
right: deprecated.$s-6;
|
||||
top: px2rem(6);
|
||||
right: px2rem(6);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user